• Babel的使用


    本文参考文献(https://zhuanlan.zhihu.com/p/43249121

    babel 到底做了什么?怎么做的?

      简单来说,把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行。本文以 babel 6.x 为基准进行讨论。最近 babel 出了 7.x,放在最后聊。

    使用方法

    1.使用单体文件 (standalone script)
    2.命令行 (cli)
    3.构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。

      其中后面两种比较常见。第二种多见于 package.json 中的 scripts 段落中的某条命令;第三种就直接集成到构建工具中。

      这三种方式只有入口不同而已,调用的 babel 内核,处理方式都是一样的,所以我们先不纠结入口的问题。

    babel相关知识

    配置文件-plugin

    既然插件是 babel 的根本,那如何使用呢?总共分为 2 个步骤:

    1. 将插件的名字增加到配置文件中 (根目录下创建 .babelrc 或者 package.json 的 babel 里面,格式相同)
    2. 使用 npm install babel-plugin-xxx 进行安装

    具体书写格式就不详述了。

    配置文件-preset

    可以简单的把Babel Preset视为Babel Plugin的集合。

    preset 分为以下几种:

    • 官方内容,目前包括 env, react, flow, minify 等。这里最重要的是 env,后面会详细介绍。
    • stage-x,这里面包含的都是当年最新规范的草案,每年更新。
      这里面还细分为
      • Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
      • Stage 1 - 提案: 初步尝试。
      • Stage 2 - 初稿: 完成初步规范。
      • Stage 3 - 候选: 完成规范和浏览器初步实现。
      • Stage 4 - 完成: 将被添加到下一年度发布。

    例如 syntax-dynamic-import 就是 stage-2 的内容,transform-object-rest-spread 就是 stage-3 的内容。
    此外,低一级的 stage 会包含所有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。
    stage-4 在下一年更新会直接放到 env 中,所以没有单独的 stage-4 可供使用。

      • es201x, latest
        这些是已经纳入到标准规范的语法。例如 es2015 包含 arrow-functions,es2017 包含 syntax-trailing-function-commas。但因为 env 的出现,使得 es2016 和 es2017 都已经废弃。所以我们经常可以看到 es2015 被单独列出来,但极少看到其他两个。
        latest 是 env 的雏形,它是一个每年更新的 preset,目的是包含所有 es201x。但也是因为更加灵活的 env 的出现,已经废弃。

    执行顺序

    很简单的几条原则:

      • Plugin 会运行在 Preset 之前。
      • Plugin 会从前到后顺序执行。
      • Preset 的顺序则 刚好相反(从后向前)。

    插件和 preset 的配置项

    简略情况下,插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象。

    最需要配置的当属 env,如下:

    "presets": [
        // 带了配置项,自己变成数组
        [
            // 第一个元素依然是名字
            "env",
            // 第二个元素是对象,列出配置项
            {
              "module": false
            }
        ],
    
        // 不带配置项,直接列出名字
        "stage-2"
    ]

    @babel/preset-env(babel-env升级7.0之后)

    @babel/preset-env 主要干的事情呢,根据你的设置表示你要啥,它就给你啥,不多给也不少给。
    使用@babel/preset-env最主要的配置字段就是useBuiltInstarget

    //babel.config.js
    const presets = [
      [
        "@babel/env",
        {
          targets: {
            "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
          },
          useBuiltIns: "usage",
        },
      ],
    ];
    
    module.exports = { presets };

    target

    target表示所以编译的代码运行的环境,可以是浏览器,可以是node,只要设置相应的字段,它就能根据规则插入和转义相应的语法跟API

    例如以下的"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]表示兼容市场占有率>1%,浏览器的最新两个版本,ie8以下不兼容

    useBuiltIns

    useBuiltIns有三种取值,不同的取值会影响API的导入方式

    • false
      就是不用polyfill,如果在业务入口 import '@babel/polyfill', 会无视 .browserslist 将所有的 polyfill 加载进来。
    • entry
      需要手动import '@babel/polyfill',它会根据targets中的配置来过滤出polyfill
    • usage
      不需要手动import '@babel/polyfill'(加上也无妨,编译时会自动去掉), 且它会根据targets中的配置来过滤出polyfill + 业务代码使用到的新 API 按需进行 polyfill。
      useage并不会对第三方包做检测,所以如果某些第三包使用高级的API那么在低版本的浏览器上也是会报错的,例如:Array.from

    基本上的开发使用@babel/preset-env+@babel/polyfill已经完全很完美了

    babel-cli

    顾名思义,cli 就是命令行工具。安装了 babel-cli 就能够在命令行中使用 babel 命令来编译文件。

    在开发 npm package 时经常会使用如下模式:

    • 把 babel-cli 安装为 devDependencies
    • 在 package.json 中添加 scripts (比如 prepublish),使用 babel 命令编译文件
    • npm publish

    这样既可以使用较新规范的 JS 语法编写源码,同时又能支持旧版环境。因为项目可能不太大,用不到构建工具 (webpack 或者 rollup),于是在发布之前用 babel-cli 进行处理。

    babel-node

    babel-node 是 babel-cli 的一部分,它不需要单独安装。

    它的作用是在 node 环境中,直接运行 es2015 的代码,而不需要额外进行转码。例如我们有一个 js 文件以 es2015 的语法进行编写(如使用了箭头函数)。我们可以直接使用 babel-node es2015.js 进行执行,而不用再进行转码了。

    可以说:babel-node = babel-polyfill + babel-register。那这两位又是谁呢?

    babel-register

    babel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js.jsx.es 和 .es6 后缀名的文件,就会先用 babel 进行转码。

    使用时,必须首先加载 require('babel-register')

    需要注意的是,babel-register 只会对 require 命令加载的文件转码,而 不会对当前文件转码。

    另外,由于它是实时转码,所以 只适合在开发环境使用。

    babel-polyfill

    babel 默认只转换 js 语法,而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。

    举例来说,es2015 在 Array 对象上新增了 Array.from 方法。babel 就不会转码这个方法。如果想让这个方法运行,必须使用 babel-polyfill。(内部集成了 core-js 和 regenerator)

    使用时

    1: (浏览器环境)单独在html的<head>标签中引入babel-polyfill.js(CDN或本地文件均可)
    2: 在package.json中添加babel-polyfill依赖, 在webpack配置文件增加入口: 如entry: ["babel-polyfill",'./src/app.js'], polyfill将会被打包进这个入口文件中, 而且是放在文件最开始的地方
    3: 在package.json中添加babel-polyfill依赖, 在webpack入口文件顶部使用import/require引入,如`import 'babel-polyfill'``

    babel-polyfill 主要有两个缺点:

    1. 使用 babel-polyfill 会导致打出来的包非常大,因为 babel-polyfill 是一个整体,把所有方法都加到原型链上。比如我们只使用了 Array.from,但它把 Object.defineProperty 也给加上了,这就是一种浪费了。这个问题可以通过单独使用 core-js 的某个类库来解决,core-js 都是分开的。
    2. babel-polyfill 会污染全局变量,给很多类的原型链上都作了修改,如果我们开发的也是一个类库供其他开发者使用,这种情况就会变得非常不可控。

    因此在实际使用中,如果我们无法忍受这两个缺点(尤其是第二个),通常我们会倾向于使用 babel-plugin-transform-runtime

    但如果代码中包含高版本 js 中类型的实例方法 (例如 [1,2,3].includes(1)),这还是要使用 polyfill。

    babel-runtime 和 babel-plugin-transform-runtime (重点)

    我们时常在项目中看到 .babelrc 中使用 babel-plugin-transform-runtime,而 package.json中的 dependencies (注意不是 devDependencies) 又包含了 babel-runtime,那这两个是不是成套使用的呢?他们又起什么作用呢?

    先说 babel-plugin-transform-runtime

    babel 会转换 js 语法,之前已经提过了。以 async/await 举例,如果不使用这个 plugin (即默认情况),转换后的代码大概是:

    // babel 添加一个方法,把 async 转化为 generator
    function _asyncToGenerator(fn) { return function () {....}} // 很长很长一段
    
    // 具体使用处
    var _ref = _asyncToGenerator(function* (arg1, arg2) {
      yield (0, something)(arg1, arg2);
    });

    不用过于纠结具体的语法,只需看到,这个 _asyncToGenerator 在当前文件被定义,然后被使用了,以替换源代码的 await。但每个被转化的文件都会插入一段 _asyncToGenerator 这就导致重复和浪费了。

    在使用了 babel-plugin-transform-runtime 了之后,转化后的代码会变成

    // 从直接定义改为引用,这样就不会重复定义了。
    var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
    var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
    
    // 具体使用处是一样的
    var _ref = _asyncToGenerator3(function* (arg1, arg2) {
      yield (0, something)(arg1, arg2);
    });

    从定义方法改成引用,那重复定义就变成了重复引用,就不存在代码重复的问题了。

    但在这里,我们也发现 babel-runtime 出场了,它就是这些方法的集合处,也因此,在使用 babel-plugin-transform-runtime 的时候必须把 babel-runtime 当做依赖。

    再说 babel-runtime,它内部集成了

    1. core-js: 转换一些内置类 (PromiseSymbols等等) 和静态方法 (Array.from 等)。绝大部分转换是这里做的。自动引入。
    2. regenerator: 作为 core-js 的拾遗补漏,主要是 generator/yield 和 async/await 两组的支持。当代码中有使用 generators/async 时自动引入。
    3. helpers, 如上面的 asyncToGenerator 就是其中之一,其他还有如 jsxclassCallCheck 等等,可以查看 babel-helpers。在代码中有内置的 helpers 使用时(如上面的第一段代码)移除定义,并插入引用(于是就变成了第二段代码)。

    babel-plugin-transform-runtime 不支持 实例方法 (例如 [1,2,3].includes(1))

    此外补充一点,把 helpers 抽离并统一起来,避免重复代码的工作还有一个 plugin 也能做,叫做 babel-plugin-external-helpers。但因为我们使用的 transform-runtime 已经包含了这个功能,因此不必重复使用。而且 babel 的作者们也已经开始讨论这两个插件过于类似,正在讨论在 babel 7 中把 external-helpers 删除,讨论在 issue#5699 中。

    babel-loader

    前面提过 babel 的三种使用方法,并且已经介绍过了 babel-cli。但一些大型的项目都会有构建工具 (如 webpack 或 rollup) 来进行代码构建和压缩 (uglify)。理论上来说,我们也可以对压缩后的代码进行 babel 处理,但那会非常慢。因此如果在 uglify 之前就加入 babel 处理,岂不完美?

    所以就有了 babel 插入到构建工具内部这样的需求。以(我还算熟悉的) webpack 为例,webpack 有 loader 的概念,因此就出现了 babel-loader

    和 babel-cli 一样,babel-loader 也会读取 .babelrc 或者 package.json 中的 babel 段作为自己的配置,之后的内核处理也是相同。唯一比 babel-cli 复杂的是,它需要和 webpack 交互,因此需要在 webpack 这边进行配置。比较常见的如下:

    module: {
      rules: [
        {
          test: /.js$/,
          exclude: /(node_modules|bower_components)/,
          loader: 'babel-loader'
        }
      ]
    }
    

    如果想在这里传入 babel 的配置项,也可以把改成:

    // loader: 'babel-loader' 改成如下:
    use: {
      loader: 'babel-loader',
      options: {
        // 配置项在这里
      }
    }
    

    这里的配置项优先级是最高的。但我认为放到单独的配置文件中更加清晰合理,可读性强一些。

    babel-plugin-import   

    是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式

    "plugins": [["transform-runtime"],["import", {
          "libraryName": "antd",
          "libraryDirectory": "lib",
          "style": true // `style: true` 会加载 less 文件 或者'css'
        }],["import", {
          "libraryName": "cheui-react",
          "libraryDirectory": "lib/components",
          "camel2DashComponentName": true // default: true
      }]]

    总结

  • 相关阅读:
    es date_histogram强制补零
    macos下默认的调试工具是lldb
    test
    mybaity 代码自动生成器
    初始化的问题
    SQLServer常用语句
    PowerShell Install-Module 离线安装 .nupkg包
    .NET Core语句记录
    system design(how to design tweet)
    软件-开源
  • 原文地址:https://www.cnblogs.com/longlongdan/p/10614928.html
Copyright © 2020-2023  润新知