Babel
Babel 将转码的内容分为两部分。
一部分是 Syntax,也就是语法(const let 解构 箭头函数等等),这是 babel 默认支持的功能;
另一部分就是新 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法都不会转码,API 部分则需要配置 polyfill 来支持。
其实还有第三部分,在转译 Syntax 时,定义了一些辅助函数来帮忙,比如typeof,定义了一个_typeof函数,class 中定义了_classCallCheck,这部分称为 helpers,它的问题是,这些辅助函数在每个需要的文件中都被定义了一遍。
另外,babel 的 polyfill 机制是,对于例如 Array.from 等静态方法,直接在 global.Array 上添加;对于例如 includes 等实例方法,直接在 global.Array.prototype 上添加。这样直接修改了全局变量的原型,有可能会带来意想不到的问题。
这个问题在开发第三方库的时候尤其重要,因为我们开发的第三方库修改了全局变量,有可能和另一个也修改了全局变量的第三方库发生冲突,或者和使用我们的第三方库的使用者发生冲突。公认的较好的编程范式中,也不鼓励直接修改全局变量、全局变量原型。
helpers 和全局变量的问题,则是由插件@babel/plugin-transform-runtime 解决。
Babel preset
babel 的预设,最常用的就是 preset-env,有以下几个配置可以重点关注
- target
简单讲,该参数决定了我们项目需要适配到的环境,比如可以申明适配到的浏览器版本,这样 babel 会根据浏览器的支持情况自动引入所需要的 polyfill。
这个字段可以填写 browserslist 的查询字符串,官方推荐使用.browserslistrc 文件去指明编译的 target,这个配置文件还可以和 autoprefixer、stylelint 等工具一起共享配置。
不建议直接使用 target 进行配置,但如果需要的话,必须制定ignoreBrowserslistConfig: true忽略.browserslistrc 的配置项 - useBuiltIns 该参数指明的是 polyfill 的方案。
- false 默认值,在不主动 import 的情况下不使用 preset-env 来实现 polyfills,只使用其默认的语法转换功能。
- entry 根据浏览器目标环境(targets)的配置,引入全部浏览器暂未支持的 polyfill 模块,无论在项目中是否使用到。
依赖
core-js@3和regenerator-runtime两个包,需要在入口处引入 polyfill(PS,是不是很眼熟?之前 corejs2 升 3 的时候就加了这两句避免报错,因为项目使用的 usage, 没有识别到依赖包里更多的 polyfill 需求,所以需要手动引入全部 polyfill,usage 实际是无效的)import 'core-js/stable' import 'regenerator-runtime/runtime' - usage 不需要手动在入口文件引入 polyfill,Babel 将会根据我们的代码使用情况自动注入 polyfill,如此一来在打包的时候将会相对地减少打包体积。它的问题就是上面提到的,当项目中引入的第三方库有 polyfill 处理不当的情况下,将会出现引用异常的问题。(使用社区广泛使用的流行库能降低这个风险)
- corejs core-js 是完全模块化的 javascript 标准库。推荐 V3,V2 已经停止维护。
@babel/plugin-transform-runtime
使用前需要安装这两个库:
yarn add @babel/runtime // 默认 corejs为 false,如果使用 core-js v2 的 runtime,则需要安装 @babel/runtime-corejs2
yarn add -D @babel/plugin-transform-runtime
其中 @babel/plugin-transform-runtime 的作用是转译代码,转译后的代码中可能会引入 @babel/runtime 里面的模块。所以前者运行在编译时,后者运行在运行时。类似 polyfill,后者需要被打包到最终产物里在浏览器中运行。作为库时,一定要将 runtime 加入到 dependices 中。
实际上 preset-env 的 polyfill 会污染全局环境,作为项目开发无可厚非,但是如果我们在开发提供给其他开发者使用的 library,我想我们不应该污染全局,并且应该提供更好的打包体积和效率。而这个插件主要做了三件事:
- 当开发者使用异步或生成器的时候,自动引入@babel/runtime/regenerator,开发者不必在入口文件做额外引入
- 提供沙盒环境,避免全局环境的污染
- 移除 babel 内联的 helpers,统一使用@babel/runtime/helpers 代替,减小打包体积
当使用此方案时,不需要在入口文件处手动引入 core-js 和 regenerator-runtime。
babel-runtime主要包含
- core-js: 转换一些内置类 (Promise, Symbols等等) 和静态方法 (Array.from 等)。绝大部分转换是这里做的。自动引入。
- regenerator: 作为 core-js 的拾遗补漏,主要是 generator/yield 和 async/await 两组的支持。当代码中有使用 generators/async 时自动引入。
- helpers, 如上。
使用场景分析
@babel/preset-env 和 plugin-transform-runtime 二者都可以设置使用 corejs 来处理 polyfill,二者各有使用场景,在项目开发和类库开发的时候可以使用不同的配置。 不要同时为二者配置 core-js 的功能,以免产生复杂的不良后果。
项目开发
useBuiltIns 使用 usage,尽量使用社区广泛使用的优质库以优化打包体积,不使用暂未进入规范的特性。plugin-transform-runtime 只使用其移除内联复用的辅助函数的特性,减小打包体积。
{
"presets": [
[
"@babel/preset-env",
{
// targets 官方推荐使用 .browserslistrc 配置
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": false
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false // 默认值,即使如此依然需要 yarn add @babel/runtime
}
]
]
}
类库开发
类库开发尽量不使用污染全局环境的 polyfill,因此@babel/preset-env 只发挥语法转换的功能,polyfill 由 plugin-transform-runtime 来处理,推荐使用 core-js@3,并且不使用未进入规范的特性
{
"presets": [
[
"@babel/preset-env",
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
},
"useESModules": true
}
]
]
}
babel preset in vue
在 vue-cli 创建的项目中,使用的是 vue 自定义的预设@vue/app,这个预设包含了preset-env和transform-runtime,还有一些额外的如 jsx 的支持等。
针对preset-env的配置如下:
{
modules: false,
// target: '', // 不设置,使用browserlintrc.js作为target
useBuiltIns: 'usage',
polyfills: ['es.array.iterator', 'es.promise', 'es.object.assign', 'es.promise.finally'],
jsx: true,
loose: false,
entryFiles: []
}
transform-runtime 则不包含 polyfill 配置,只针对 helpers 优化。
版本回顾
在 babel@6 年代,我们使用的是 stage,那 stage 其实只会翻译 Syntax,而 API 则交给 babel-plugin-transform-runtime 或者 babel-polyfill 来实现。(这也是为什么大家在老项目中可以看到有引入 babel-polyfill 的原因) 在 babel@7 年代,我们废弃了 stage,使用的 preset-env,同时他也可以提供 polyfill 的能力