Babel 的组成

用于编译期(作为devDependcies

以下组件用于代码的编译,生成可发布的产品代码。

  • @babel/core:必须。一个可编程使用的工具。它依序做如下事:
    1. 将代码/代码文件生成 AST(依赖于底层的@babel/parser。它也会借助 syntax plugins 的能力以识别各种各样的语法,如 TS、JSX 等)
    2. 遍历该 AST(依赖于底层的@babel/traverse),生成新 AST(生成规则由 transform plugins 提供。插件通常会使用@babel/types中提供的辅助 AST 生成的小工具 )
    3. 将新的 AST 转化为代码(依赖于底层的@babel/generator
  • @babel/cli:若想以命令行的方式使用@babel/core暴露的接口,可使用该工具(除此之外 Babel 还支持其它方式)。除了以在命令行中给该工具传参,也可通过配置文件(JS 或 JSON)控制其行为
  • 各 plugin:插件的用途见上文。插件的行为通常可配置。可根据目的对插件进行分类:
    • syntax plugins:单纯扩展@babel/parser对代码的解析能力
    • transform plugins:描述如何根据原 AST 生成新 AST。一个 transform plugin 可能只负责某一条 ECMAScript 新语法的转译,也可能负责整个语言(如 JSX、TypeScript)到 JS 的转译
  • 各 preset:一个 preset 是一批插件的集合,该集合的行为通常可配置。最常用的是@babel/preset-env,它是一个动态的集合,所包含的插件由目标编译环境智能决定

需要注意,加载 plugins/presets 时的顺序是有意义的,例如,syntax plugins 应当在 transform plugins 前被加载,否则无法顺利进行 AST 生成。

用于运行时(作为dependencies

以下组件的定位是“库”(library)。它们的代码将全部或部分地被混入产品代码中以确保转译得到的产品代码能正确执行。注意:它们均不保证 Web API 能正常使用,而只保证 ES 语法/标准API 的正常运行。

  • @babel/runtime:语法转译通常使得原语法的部分语义由编译期来到运行时,因此需要提供额外的运行时代码(称为 Helper。依赖底层的@babel/helper生成)。该库提供了可复用的 helper(含为实现 ES2015 的 generator 和 ES2017 的 async function 的语义而引入的regenerator-runtime),需要搭配插件@babel/plugin-transform-runtime使用,从而避免重复生成 helper。如果并不想复用 helper,该库可以不安装
  • @babel/runtime-corejs2/@babel/runtime-corejs3:结合了@babel/runtimecore-js两个库的内容。需要搭配插件@babel/plugin-transform-runtime使用(配置了corejs选项),为前者提供 helper 和 polyfill。2和3的区别在于后者不污染全局
  • @babel/polyfill已废弃):JS 的很多新特性并非是新语法,而是新对象、新函数、新方法,语法转译对此无能为力,因此需要提供 polyfill。该库引用regenerator runtimecore-js两个库,做的事其实就是将它们引入(@babel/polyfill总共就十几行代码),在 Babel 7.4.0 版本之后,应当直接安装这两个库,而不用安装@babel/polyfill

附录:@babel/preset-env的智能插件加载和 polyfill 引入

@babel/preset-env可以:

  • 根据设定的编译目标(targets)智能选择加载哪些插件进行语法转译。不设定,则转译任何 ES2015+ 的代码
  • 以设定好的方式(useBuiltIns选项)往代码里插入require corejs 模块的语句(仅仅是require语句,并无任何 polyfill 的具体代码.因此你还是需要手动安装core-js)。具体见下文

附录:插件@babel/plugin-transform-runtime的行为

该插件通过自动插入某些require语句,起到优化 Babel 生成的产品代码的作用,包括:

  • 在产品代码中复用@babel/runtime@babel/runtime-corejs2/3(配置了corejs选项时)中的 helper 与 polyfill 代码(通过在各处生成require("@babel/runtime/...语句。因此必须安装@babel/runtime@babel/runtime-corejs2/3作为dependency)
  • (可选)根据源代码(含 helper 中的代码)中对新 API 的实际使用情况,往代码里插入require corejs 相应模块的语句。具体见下文)

附录:polyfill 的处理

使用 @babel/preset-env

与 polyfill 相关的选项包括:

  • targets:设定编译目标环境(含语法和 API)
  • useBuiltIns:设置插入require corejs 语句的方式
    • "usage"根据新 API 在源码里的实际使用情况和编译目标环境(targets),在代码中插入对相应 corejs 模块的require语句
    • "entry"根据编译目标环境(targets),把源码中程序员手动写的import core-js之类的代码(一般出现在入口代码处)转成对相应 corejs 模块的require语句
    • false(默认):啥也不做。程序员应当手动写对 regenerator runtime 和core-js 两个库(或者 @babel/polyfill)的导入语句
  • corejs:告知该插件你正在使用 corejs 的何种版本(一般使用 corejs3,它具有更多的功能
  • excludeinclude:强制排除/加上对指定 corejs 模块的引入

注意,无论如何设置,该插件require的 polyfill 代码都将污染全局的原型对象

使用 @babel/plugin-transform-runtime

通过该插件配置corejs选项,根据源代码中对新 API 的实际使用情况,插入对 corejs 相应模块的require语句。这将无效化@babel/preset-env中与 polyfill 相关的配置(因为 plugin 先于 preset 运行)。

该插件与上一个插件的区别是:

polyfill providers

polyfill providers 是一系列插件的合称(在一个项目中,通常选用其中的一个插件)。这类插件根据用户代码的实际情况和设置好的目标环境,自动生成require语句,引入刚好必要的 polyfill 代码。

这类插件支持污染全局/不污染全局,支持按需加载(根据用户代码的实际情况 + 目标编译环境)/全部加载(根据目标编译环境) polyfill,还支持用户自定义 polyfill 的实现库(而不是限制为 corejs)。因此可以取代@babel/preset-env@babel/plugin-transform-runtime在 polyfill 方面的功能。

附录:最佳实践

综上,可以得出配置 Babel 的最佳实践:

业务项目开发

  1. 安装core-js@3和@babel/runtime
  2. 安装并使用@babel/preset-env。设置useBuiltIns: 'usage'选项
  3. 安装并使用@babel/plugin-transform-runtime,但不要配置其corejs选项

类库开发

  1. 安装@babel/runtime-corejs3
  2. 安装并使用@babel/plugin-transform-runtime,配置其corejs: 3选项

附录:其它实用工具

  • @babel/register:在运行时增强 Node 中require函数的功能,使其在被调用时能够实时利用 Babel 转译目标代码。对require('@babel/register')对调用必须在程序的最前面。该功能通常用于在开发过程中提供方便
  • @babel/node:一个 Node.js CLI wrapper。使得通过命令行调用 Node.js 时令 Node.js 自动使用 babel 转译其加载的每个文件
  • @babel/code-frame:一个为文本生成带标记(例如行号,行指示符号,列指示符号等)的文本的工具。通常用于生成错误提示
  • @babel/template:待补充

附录:tsc vs. Babel

对于使用了 TypeScript 的项目,我们有两个选择:

使用 tsc 使用 babel with ts preset 评价
ES 语法 TS 通常只吸收成熟的 ES 语法 可以使用最前沿的 ES 语法 建议只使用成熟语法。打平
TS 语法 100% 支持 极个别的 TS 语法不支持 基本打平
polyfill 支持 无法自动按需加载 preset-env 支持按需加载 除非项目无须 polyfill,否则babel 胜
编译速度(不含 type check 时间) tsc 更占优 - tsc 胜
编译产物(不含 polyfill)的体积与运行时效率 tsc 更占优 - tsc 胜

使用 babel with typescript preset 时的注意事项:

  • 需要在构建流程中额外配置 type check 过程。例如与 Webpack 一同使用时,可使用 fork-ts-checker-webpack-plugin;或单独使用 tsc 的 type check 功能
  • 注意是否要开启 tsconfig.json 中的isolatedModules选项。开启该选项时,tsc type checker 将禁止你使用一些“可能引起非 tsc 的编译器(如 Babel)的错误行为”的语言特性,但其实你的编译器(如 Babel)可能是支持这些语言特性的
  • 若需要生成.d.ts 文件(通常作为库作者),则需额外使用 tsc。