梳理一下混乱的Babel
了解 Babel 工程化
core-js
的五个相关包
core-js
core-js-bundle
core-js-pure
提供了不污染全局变量的垫片能力
import _form from "core-js-pure/features/array/from"
import _flat from "core-js-pure/features/array/flat"
core-js-compact
维护了按照 browserlist 规范的垫片需求数据。被babel
生态使用,由babel
分析出根据环境需要按需加载的垫片
const { list, targets } = require("core-js-compact")({
targets: ">2.5%",
})
core-js-builder
结合core-js-compace
以及core-js
并利用webpack
能力,根据需求打包出core-js
代码。被nodejs
服务使用,构建出处不同场景的垫片包
require("core-js-builder")({
targets: ">0.5%",
filename: "./my-core-js-bunddle.js",
})
.then(code => {})
.catch(error => {})
寻找“最完美”的 polyfill 方案
polyfill
:用社区提供的一段代码,在不兼容某些新特性的浏览器上,使用该特性。- “最完美”指的是侵入性最小,工程化、自动化程度最高,业务影响最低。
- 一个趋于完美的 polyfill 设计的核心原则是按需加载补丁(按照用户终端环境,按照业务代码使用情况)
上个世纪的 polyfill 方案:
- 手动打补丁,直接挂载方法到
window
对象上 - es5-shim 和 es6-shim
本世纪的 polyfill 方案:
我们从@babel/preset-env
的useBuiltIns
属性来分类阐述 babel 如何处理垫片的。
- 默认
false
:babel 会全量引入 polyfill ,我们需要在入口代码引入@babel/polyfill
或core-js
,其中@babel/polyfill
包含regenerator runtime模块,如果引入的是core-js
并且代码中使用到了 generators 和 async 函数,那么你需要手动引入 regenerator runtime 模块。提一嘴,regenerator runtime 模块在useBuiltIns
属性值为usage
或者使用到@babel/plugin-transform-runtime
的时候会自动引入,无需手动哈~ entry
:在入口代码手动引入@babel/polyfill
或core-js
,配置@babel/preset-env
的targets
属性引入需要支持的 polyfillusage
: ,根据代码使用到的方法引入垫片,无需在代码入口处手动引入 polyfill
在线动态打补丁网站:https://polyfill.io/v3/
babel 的核心库
@babel/cli
是 babel 提供的命令行,它可以在终端中通过命令行方式运行,编译文件或目录。其使用了commander
库搭建基本的命令行开发。@babel/core
是 babel 实现转换的核心,它可以根据配置,进行源码的编译转换,@babel/core 的能力由更底层的 @babel/parser、@babel/code-frame、@babel/generator、@babel/traverse、@babel/types 等包提供。
var babel = require("@babel/core")
babel.transform(code, options, function(err, result) {
result // => { code, map, ast }
})
@babel/standalone
可在非nodejs
环境,也就是浏览器环境实现对type="text/babel"
或type="text/jsx"
的代码进行编译,因此这个包对于浏览器环境动态插入高级语言特性的脚本、在线自动解析编译非常有意义。@babel/parser
它是 Babel 用来对 JavaScript 语言解析的解析器,负责将源码转换成 AST@babel/traverse
遍历 AST@babel/generator
对新的 AST 进行聚合并生成 JavaScript 代码@babel/preset-env
实现编译降级
@babel/polyfill 其实就是 core-js 和 regenerator-runtime 两个包的结合。
注意:@babel/polyfill 目前已经计划废弃,新的 Babel 生态(@babel/preset-env V7.4.0 版本)鼓励开发者直接在代码中引入 core-js 和 regenerator-runtime。但是不管直接导入 core-js 和 regenerator-runtime,还是直接导入 @babel/polyfill 都是引入了全量的 polyfills,@babel/preset-env 如何根据目标适配环境,按需引入业务中所需要的 polyfills 呢?
事实上,@babel/preset-env 通过 targets 参数,按照 browserslist 规范,结 合 core-js-compact,筛选出适配环境所需的 polyfills(或 plugins)
@babel/plugin-transform-runtime
它可以重复使用 Babel 注入的 helpers 函数,达到节省代码大小的目的。
1. @babel/plugin-transform-runtime 需要和 @babel/runtime 配合使用;
2. @babel/plugin-transform-runtime 用于编译时,作为 devDependencies 使用;
3. @babel/plugin-transform-runtime 用于编译时,作为 devDependencies 使用;
4. @babel/plugin-transform-runtime 将业务代码编译,引用 @babel/runtime 提供的 helpers,达到缩减编译产出体积的目的;
5. @babel/runtime 用于运行时,作为 dependencies 使用。
另外,@babel/plugin-transform-runtime 和 @babel/runtime 结合还有一个作用:它除了可以对产出代码瘦身以外,还能避免污染全局作用域。
@babel/plugin
是 Babel 插件集合@babel/plugin-syntax-\*
是 Babel 的语法插件。它的作用是扩展 @babel/parser 的一些能力,提供给工程使用。比如 @babel/plugin-syntax-top-level-await 插件,提供了使用 top level await 新特性的能力。@babel/plugin-proposal-\*
用于编译转换在提议阶段的语言特性。@babel/plugin-transform-\*
是 Babel 的转换插件。比如简单的 @babel/plugin-transform-react-display-name 插件,可以自动适配 React 组件 DisplayName,比如:
var foo = React.createClass({}) // React <= 15
var bar = createReactClass({}) // React 16+
// 上述调用,经过 @babel/plugin-transform-react-display-name,可以被编译为:
var foo = React.createClass({
displayName: "foo",
}) // React <= 15
var bar = createReactClass({
displayName: "bar",
}) // React 16+
@babel/node
类似 Node.js Cli,@babel/node 提供在命令行执行高级语法的环境,也就是说,相比于 Node.js Cli,它加入了对更多特性的支持。@babel/register
实际上是为 require 增加了一个 hook,使用之后,所有被 Node.js 引用的文件都会先被 Babel 转码。
这里请注意`@babel/node` 和 `@babel/register`,都是在运行时进行编译转换,因此运行时性能上会有影响。在生产环境中,我们一般不直接使用。
@babel/eslint-parser
配合 ESLint 检验合法 Babel 代码的解析器,实现 eslint 对试验性特性或者 Flow/TypeScript 进行代码检查。
// 实现原理也很简单,ESLint 支持 custom-parser,它允许我们使用自定义的第三方编译器,比如下面是一个使用了 espree 作为一个 custom-parser 的场景:
{
"parser": "./path/to/awesome-custom-parser.js"
}
var espree = require("espree");
// awesome-custom-parser.js
exports.parseForESLint = function(code, options) {
return {
ast: espree.parse(code, options),
services: {
foo: function() {
console.log("foo");
}
},
scopeManager: null,
visitorKeys: null
};
};