梳理一下混乱的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 方案:

  1. 手动打补丁,直接挂载方法到window对象上
  2. es5-shim 和 es6-shim

本世纪的 polyfill 方案:

我们从@babel/preset-envuseBuiltIns属性来分类阐述 babel 如何处理垫片的。

  1. 默认false:babel 会全量引入 polyfill ,我们需要在入口代码引入@babel/polyfillcore-js,其中@babel/polyfill包含regenerator runtime模块,如果引入的是core-js并且代码中使用到了 generators 和 async 函数,那么你需要手动引入 regenerator runtime 模块。提一嘴,regenerator runtime 模块在useBuiltIns属性值为usage或者使用到@babel/plugin-transform-runtime的时候会自动引入,无需手动哈~
  2. entry:在入口代码手动引入@babel/polyfillcore-js,配置@babel/preset-envtargets属性引入需要支持的 polyfill
  3. usage: ,根据代码使用到的方法引入垫片,无需在代码入口处手动引入 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
    };
};

Published under  on .

Last updated on .

pipihua

我是皮皮花,一个前后端通吃的前端攻城狮,如果感觉不错欢迎点击小心心♥(ˆ◡ˆԅ) star on GitHub!