babel介绍及配置

451 阅读7分钟

babel

是什么

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

可以做什么

  • 语法转换
  • 通过 polyfill 方式在目标环境中添加缺失的特性(eg:coreJs)
  • 源码转换(codemods)

运行原理

Babel 的三个主要处理步骤分别是:解析(parse),转换(transform),生成(generate) babel 插件就是在转换过程中起作用的,即将解析完成的语法树对象按照自己的目的进行处理,然后再进行代码生成步骤。

src=http___c.lanmit.com_d_file_bc_ze5qwtpprkw.jpg&refer=http___c.lanmit.jpeg

配置文件分类

  • babel 接受的配置文件分很多种,包括

    • babel.config.json
    • babelrc.json
    • package.json

常用 options 字段说明

{
   "presets": ["@babel/preset-env", "@babel/preset-react"],
   "plugins": [
      "@babel/plugin-transform-runtime",
      "react-hot-loader/babel",
      "@babel/plugin-proposal-class-properties",
      "@babel/plugin-syntax-dynamic-import" ],
   "env":
      { "production":
          { "plugins": ["transform-remove-console"] }
      }
  }
  • env:指定在不同环境下使用的配置。比如 production 和 development 两个环境使用不同的配置,就可以通过这个字段来配置。env 字段的从 process.env.BABEL_ENV 获取,如果 BABEL_ENV 不存在,则从 process.env.NODE_ENV 获取,如果 NODE_ENV 还是不存在,则取默认值"development"
  • plugins:要加载和使用的插件列表,插件名前的 babel-plugin-可省略;plugin 列表按从头到尾的顺序运行
  • presets:要加载和使用的 preset 列表,preset 名前的 babel-preset-可省略;presets 列表的 preset 按从尾到头的逆序运行(为了兼容用户使用习惯) 同时设置了 presets 和 plugins,那么 plugins 的先运行;每个 preset 和 plugin 都可以再配置自己的 option 还有 stage-0 到 stage-4 的标准成形之前的各个阶段,这些都是实验版的 preset,建议不要使用。(具体每个阶段有些什么文末有简单介绍)

涉及到的 packages

  1. 核心包
  • @babel/core
    babel 转译器本身,提供了 babel 的转译 API,如 babel.transform 等,用于对代码进行转译。像 webpack 的 babel-loader 就是调用这些 API 来完成转译过程的。
  • babylon:js 的词法解析器
  • babel-traverse:用于对 AST(抽象语法树,想了解的请自行查询编译原理)的遍历,主要给 plugin 用
  • babel-generator:根据 AST 生成代码
  1. 功能包
  • babel-types:用于检验、构建和改变 AST 树的节点
  • babel-template:辅助函数,用于从字符串形式的代码来构建 AST 树节点
  • babel-plugin-xxx:babel 转译过程中使用到的插件,其中 babel-plugin-transform-xxx 是 transform 步骤使用的
  • babel-helpers:一系列预制的 babel-template 函数,用于提供给一些 plugins 使用
  • babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置
  • @babel/preset: 是一系列插件的集合,包含了我们在 babel6 中常用的 es2015,es2016, es2017 等最新的语法转化插件,允许我们使用最新的 js 语法,比如 let,const,箭头函数等等,但不包括 stage-x 阶段的插件。
  • babel-polyfill:JS 标准新增的原生对象和 API 的 shim,实现上仅仅是 core-js 和 regenerator-runtime 两个包的封装。使用 preset-env 能将最新的语法转换为 ecmascript5 的写法,当我们需要使用新增的全局函数(比如 promise, Array.from)和实例方法(比如 Array.prototype.includes )时就需要引入 polyfill
  • babel-runtime:功能类似 babel-polyfill,一般用于 library 或 plugin 中,因为它不会污染全局作用域
  1. 工具包
  • babel-cli:babel 的命令行工具,通过命令行对 js 代码进行转译
  • babel-register:通过绑定 node.js 的 require 来自动转译 require 引用的 js 代码文件
  1. @babel/polyfill 和 @babel/preset-env 的关系 @babel/preset-env 中与 @babel/polyfill 的相关参数有 targets 和 useBuiltIns 两个

    • targets:
      支持的目标浏览器的列表

    • useBuiltIns: 参数有 “entry”、”usage”、false 三个值。默认值是 false,此参数决定了 babel 打包时如何处理@babel/polyfilll 语句。

      • “entry”: 需要手动 import '@babel/polyfill',根据 browserlist 中浏览器版本的支持,将 polyfill 拆分引入浏览器不支持的 polyfill。这样会导致实际用不到的 polyfill 也会被打包到输出文件,导致文件比较大。

      • “usage”: 不需要手动在代码里写 import‘@babel/polyfilll’,打包时会自动根据实际代码的使用情况,结合 targets 引入代码里实际用到 部分 polyfilll 模块

      • false: 不启用 polyfill,如果 import '@babel/polyfill', 会无视 browserlist 将所有的 polyfill 加载进来。

      • 新版本的 Babel,会提示直接引入 core-js 或者 regenerator-runtime/runtime 来代替@babel/polyfill。

      需要注意的是在 webpack 打包文件配置的 entry 中引入的 @babel/polyfill 不会根据 useBuiltIns 配置任何转换处理。

      总结:在业务项目中需要用到 polyfill 时, 可以使用和 @babel/preset-env 的 targets 和 useBuiltIns: usage 来根据目标浏览器的支持情况,按需引入用到的 polyfill 文件。

coreJs

  • corejs 是什么
    • 是一个 js 标准库的 polyfill,其支持最新的 es 标准,es 标准的提案,web standard
    • 最大程度的模块化,可以只引入需要的部分
    • 可以被使用而不污染全局命名空间
    • 和 babel 紧密结合,做了很多相关优化
  • corejs 最新也是推荐版本为 core-js@3
  • core-js 怎么单独使用:
import 'core-js/features/array/from'; // <- at the top of your entry point
import 'core-js/features/array/flat'; // <- at the top of your entry point
import 'core-js/features/set';        // <- at the top of your entry point
import 'core-js/features/promise';    // <- at the top of your entry point

Array.from(new Set([1, 2, 3, 2, 1]));          // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2);                 // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
  1. babel 和 core-js 结合使用
test: /\.js|jsx$/, exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: [
                ['@babel/preset-env', {
                    useBuiltIns: 'usage', // 按需引入,使用polyfill
                    corejs: { version: 3 },// 不写报错,找不到core-js
                    targets: {	//兼容
                        "chrome": "58",
                        "ie": "9",
                    }
                    //["last 2 versions", "safari >= 7"]
                }
                ]
            ]
        }
    }

babel 影响 treeshaking 的问题

以前你可能需要用 babel 来将 ES6 的模块语法转换为 AMD、CommonJS、UMD 之类的模块化标准语法,但是现在 webpack 已经把这个事情做了,所以就不需要 babel 来做了,但是 babel 配置项中的 modules 默认值是 commonjs,所以你需要将 modules 设置为 false 才行,不然就冲突了。

// .babelrc - webpack v1.*
{
  "presets": [
    "env",
    "stage-0"
  ]
}
// .babelrc - webpack v2.* - v3.*
{
  "presets": [
    ["env", {
        "modules": false
    }],
    "stage-0"
  ]
}

很明显,一眼就能看出相对于 v1.*的版本,v2.*或者 v3.*版本多了"modules": false 这项配置。v1.*版本需要 babel 来将 ES6 的模块语法转换为 AMD、CommonJS、UMD 之类的模块化标准语法,但是现在 webpack 已经把这个事情做了,所以就不需要 babel 来做了,但是 babel 配置项中的 modules 默认值是 commonjs,所以需要将 modules 设置为 false 才行,不然就冲突了。并且,treeshaking 的前提是 esmodule 模块话,如果转成 commonjs 是不利于 treeshaking 的

如何区分 Babel 中的 stage-0,stage-1,stage-2 以及 stage-3

{
    "presets": [
      "es2015",
      "react",
      "stage-0"
    ],
    "plugins": []
  }

stage-x:stage-0、stage-1、stage-2、stage-3、stage-4 分别对应的就是进入标准之前的 5 个阶段,不同 stage-x 之间存在依赖关系,数字越小,阶段越靠后,靠后阶段包含前面阶段所有的功能,简单理解就是 stage-0 包含 stage-1/2/3 的内容,所以如果你不知道需要哪个 stage-x 的话,直接引入 stage-0 就好了。PS: babel-preset-stage-4 已经整合入 Presets 不单独发布了。

  1. 法力无边的 stage-0
    stage-0 包含 stage-1, stage-2 以及 stage-3 的所有功能,另外再添加了
  • transform-do-expressions,在 jsx 中我们一般用三元表达式来做条件判断,有了该插件之后我们就可以如下来使用 if/else 了 return ( <div > {do { if(name == 'a') { <AComponent/>; }else if(name == 'b') { <BComponent/>; }else { <CComponent/>; } } }} </div> )

  • transform-function-bind 它提供了 :: 操作符来简化上下文切换操作, 作为 bind , call 的替代方案

    obj::func
    // 等于
    func.bind(obj)
    
            obj::func(val)
            // 等于
            func.call(obj, val)
    
            ::obj.func(val)
            // 等于
            func.call(obj, val)
            ```
            在react里,你甚至可以这样来绑定自己
            ```
            <Components onClick={::this.change}>
            ```
    
  1. stage-1 stage-1 则是囊括了 2 和 3 插件,另外增加了
  • transform-class-constructor-call (已不建议使用,不做介绍)
  • transform-export-extensions:export 方法的扩展
    export * as ns from 'mod';
    export v from 'mod';
    
  1. stage-2
    同理,他拥有 3 的插件,还有
  • syntax-dynamic-import
    用于动态 import ,我们知道 import 语法只支持静态加载模块 import() 作为一个提案,只被 babel 内部使用

  • transform-class-properties
    ransform-class-properties 用与 class 的属性转化

  1. stage-3
    拥有的插件
  • transform-object-rest-spread 用来处理扩展运算符 let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
  • transform-async-generator-functions
    用来处理 async 和 await async function* agf() { await 1; yield 2; }

astexplore 地址:astexplorer.net/

参考资料

segmentfault.com/a/119000001… zhuanlan.zhihu.com/p/25961891 blog.csdn.net/SSLJY_YCFH/… www.jianshu.com/p/e9b94b2d5… www.jianshu.com/p/e9b94b2d5…