Babel 的关键概念及初步使用

42 阅读7分钟

主要基于 Babel 7(更新于2018年08月)。

Babel 文档

Babel 中文文档

是什么?有什么用途?

是一个 Javascript 编译器。

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 代码转换为当前版本和旧版浏览器或环境中的向后兼容版本的 JavaScript。包括但不限于:

  • 转换语法。 如箭头函数、let 等 ES2015+ 语法,JSX 语法。
  • 通过 Polyfill 支持新 API,如 Promise、Array.prototype.includes。(可以通过第三方 polyfill,例如 core-js)
  • 源代码转换 (codemods) 。如:去除 Flow / TypeScript 代码中的类型注释。

相似的技术

  • SWC(Speedy Web Compiler):用 Rust 编写。兼具编译和打包功能。
  • esbuild:用 Go 编写。兼具编译和打包功能。
  • tsc:只能用于 TypeScript 编译。

关键概念 plugin / preset / polyfill

概念作用示例
plugin负责转译特定的语法特性@babel/plugin-transform-arrow-functions
presetPlugin 的集合,简化多个语法的兼容配置@babel/preset-env
@babel/preset-typescript
@babel/preset-react
polyfill兼容性代码,为旧环境提供现代 API 支持core-js

三者关系与协同工作

  • plugin 与 preset:plugin 是 Babel 的最小转译单元,而 preset 是 plugin 的集合。preset 会根据目标环境的配置决定包含哪些 plugin。
  • preset 与 polyfill:@babel/preset-env 等 preset 可以根据目标环境判断代码中是否需要 polyfill,并自动加载 core-js 中的 polyfill,保证兼容性。
  • 协同工作流程:当我们使用 @babel/preset-env 配置了 targetsuseBuiltIns: "usage" 时,Babel 会通过插件转译不支持的语法,同时通过 polyfill 实现缺失的 API,使项目在目标环境中无缝运行。

介绍一个常见 preset:@babel/preset-env

@babel/preset-env 文档

@babel/preset-env 是一个“聪明”的预设(preset),它能让你使用最新的 JavaScript 语法而无需操心对目标环境所支持的语法设置相应的语法转换插件(以及可选的 polyfills)。

@babel/preset-env 不包含任何未进入 Stage 3 阶段的 JavaScript 语法提案,因为在 TC39 的流程中,未进入 Stage 3 阶段的提案是不会被任何浏览器所实现的。

三个关键参数:target / useBuiltIns / corejs

target

Describes the environments you support/target for your project.

简单讲,该参数决定了我们项目需要适配到的环境,比如需要适配的浏览器版本。不过官方推荐使用 .browserslistrc 配置。

useBuiltIns: "usage" | "entry" | false, defaults to false

This option configures how @babel/preset-env handles polyfills.

这个参数决定了 preset-env 如何处理 polyfills。

选项值含义其他处理
false不会引入 polyfills在入口文件处自行引入相关 polyfill:
import 'core-js/stable';
import 'regenerator-runtime/runtime';
"usage"babel 会根据用户代码的使用情况,并根据 targets 自行注入相关 polyfills。在项目的入口文件处不需要 import 对应的 polyfills 相关库。
"entry"需要在项目的入口文件处 import 对应的 polyfills 相关库。babel 会根据当前 targets 描述,把需要的所有的 polyfills 全部引入到你的入口文件(注意是全部,不管你是否有用到高级的 API)。

如果担心在自己的项目中第三方包未引入对应的 polyfill,可以使用 "entry"。

corejs

string or { version: string, proposals: boolean }, defaults to "2.0". The version string can be any supported core-js versions. For example, "3.33" or "2.0".

This option only has an effect when used alongside useBuiltIns: usage or useBuiltIns: entry, and ensures @babel/preset-env injects the polyfills supported by your core-js version.

It is recommended to specify the minor version otherwise "3" will be interpreted as "3.0" which may not include polyfills for the latest features.

By default, only polyfills for stable ECMAScript features are injected: ...

用于指定 core-js 的版本,以及是否启用 proposals(支持提案,默认不开启,即只使用稳定版本的 ECMAScript 功能/特性)。

该设置项仅对于 useBuiltIns 为 "usage" | "entry" 时有效。

最常用的 polyfill:core-js

是什么?

是 JavaScript 标准库的 polyfill(垫片/补丁)。

如何使用

As of Babel 7.4.0, this package has been deprecated in favor of directly including core-js/stable (to polyfill ECMAScript features):

import "core-js/stable";

If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0, you must also load the regenerator runtime package. It is automatically loaded when using @babel/preset-env's useBuiltIns: "usage" option or @babel/plugin-transform-runtime.

出自 www.babeljs.cn/docs/babel-…

另外,core-js 的文档也有提及如何结合 Babel 使用。

从上面这段描述可以看出,结合 Babel 使用 core-js 的方式有这 3 种:

  • @babel/polyfill

    • 从 Babel 7.4.0 开始,此软件包已被弃用。
    • 取而代之的是直接引入 core-js/stable。如果您正在将生成器异步函数编译为 ES5,并且您使用的 @babel/core 或 @babel/plugin-transform-regenerator 版本早于 7.18.0,则还必须加载 regenerator runtime。需要在入口文件主动引入相关 polyfill:(❓core-js 不包含 regenerator 吗?)
    import 'core-js/stable';
    import 'regenerator-runtime/runtime';
    
  • 通过 preset @babel/preset-env 实现 core-js 的按需引入

    • 需要将设置项 useBuiltIns 设为 "usage"(如果设为"entry"则表示直接引入整个 core-js),另外可以通过设置项 corejs 来设置使用的 core-js 版本。
    • 需要注意,preset-env 的 polyfill 会污染全局环境
  • 通过 plugin @babel/plugin-transform-runtime 实现 core-js 的按需引入

    • 安装依赖:

      • 开发环境依赖 @babel/plugin-transform-runtime

      • 生产环境依赖 @babel/runtime / @babel/runtime-corejs2 / @babel/runtime-corejs3

        corejs optionInstall command
        falsenpm install --save @babel/runtime
        2npm install --save @babel/runtime-corejs2
        3npm install --save @babel/runtime-corejs3

简单介绍 @babel/plugin-transform-runtime

A plugin that enables the re-use of Babel's injected helper code to save on codesize.

该插件的出现就是复用 babel 注入的关联代码。

主要做了三件事:

  • Automatically requires @babel/runtime/regenerator when you use generators/async functions (toggleable with the regenerator option).
  • Can use core-js for helpers if necessary instead of assuming it will be polyfilled by the user (toggleable with the corejs option)
  • Automatically removes the inline Babel helpers and uses the module @babel/runtime/helpers instead (toggleable with the helpers option).
  • 当您使用生成器/异步函数时自动引入@babel/runtime/regenerator(通过选项 regenerator 进行设置)。 (❓为什么 regenerator 没有包含在 core-js 里面?)
  • 如果需要,可以使用 core-js 作为辅助程序,而不是假设它会被用户填充(通过选项 corejs 进行设置)
  • 自动删除内联 Babel 帮助程序并改用模块 @babel/runtime/helpers(通过选项 helpers 进行设置)。

使用场景

@babel/preset-env@babel/plugin-transform-runtime 二者都可以通过设置 corejs 选项来处理 polyfill,二者各有使用场景,在项目开发和类库开发的时候可以使用不同的配置。

不要同时为二者配置 core-js 的功能,以免产生复杂的不良后果。

场景一:项目开发

对于 @babel/preset-env,useBuiltIns 使用 "usage",尽量使用社区广泛使用的优质库以优化打包体积,不使用暂未进入规范的特性。对于 @babel/plugin-transform-runtime 只使用其移除内联复用的辅助函数的特性,减小打包体积。

// babel.config.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3,
          // 个人觉得默认情况下不推荐使用 proposals 功能,只使用最新规范中的特性。
          "proposals": false
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        // 默认值,即使如此依然需要安装 @babel/runtime 作为生产环境的依赖项。
        "corejs": false
      }
    ]
  ]
}

场景二:类库开发

类库开发尽量不使用污染全局环境的 polyfill,因此 @babel/preset-env 只发挥语法转换的功能,polyfill 由 @babel/plugin-transform-runtime 来处理,推荐使用 core-js@3,并且不使用未进入规范的特性。

// babel.config.json
{
  "presets": [
    ["@babel/preset-env"]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": {
          "version": 3,
          "proposals": true
        }, // 默认值为 false。设为 true 表示不需要添加支持 CommonJS 的代码,这可以减小构建产物体积。
        "useESModules": true
      }
    ]
  ]
}

以上示例中,选择了使用 core-js v3,并通过设置 proposals 为 true,选择了使用支持提案语法的功能。

参考资料