babel核心成员;AST解析;babel分层理念;babel-loader;自定义eslint;

摘录&心得

1 Babel是什么

  • Babel 不仅仅是一个工具,更是一个工具链(toolchain),是前端基建中绝对重要的一环。
  • Babel is a JavaScript compiler.
  • 编译是 Babel 的核心目标
    • 自身的实现基于编译原理,深入 AST(抽象语法树)来生成目标代码;
    • Babel 需要工程化协作,需要和各种工具(如 Webpack)相互配合。
  • Babel 是一个使用 Lerna 构建的 Monorepo 风格的仓库
  • 核心理念

    • 可插拔(Pluggable)
      • 要随时准备和第三方合作
    • 可调式(Debuggable)
      • 要提供一套 Source Map,来帮助使用者在编译结果和编译前源码之间建立映射关系,方便调试;
    • 基于协定(Compact)
      • 主要是指实现灵活的配置方式
      • 比如Babel 提供 loose 选项,帮助开发者在“尽量还原规范”和“更小的编译产出体积”之间,找到平衡。

        2 梳理Babel核心成员

        2.1 @babel/core

  • 是 Babel 实现转换的核心,提供了基础的编译能力

  • @babel/core 的能力由更底层的 @babel/parser、@babel/code-frame、@babel/generator、@babel/traverse、@babel/types等包提供。

    • @babel/parser
      • 用于生成AST
      • 是 Babel 用来对 JavaScript 语言解析的解析器。
      • require(“@babel/parser”).parse()方法可以返回给我们一个针对源码编译得到的 AST
      • 获取的AST 符合Babel AST 格式。
    • @babel/traverse:用于遍历AST
    • @babel/types:提供了对具体的 AST 节点的修改能力
    • @babel/generator:对新的 AST 进行聚合并生成 JavaScript 代码

      2.2 @babel/cli

  • 在终端中通过命令行方式运行

  • @babel/cli 负责获取配置内容,并最终依赖了 @babel/core 完成编译
  • @babel/core是@babel/cli的peerDependencies

    2.3 @babel/preset-env

  • 是直接暴露给开发者在业务中运用的包能力。

  • @babel/preset-env 允许我们配置需要支持的目标环境,利用 babel-polyfill 完成补丁的接入。
    • @babel/polyfill 其实就是 core-js 和 regenerator-runtime 两个包的结合,详见上一节
    • @babel/polyfill 目前已经计划废弃,新的 Babel 生态(@babel/preset-env V7.4.0 版本)鼓励开发者直接在代码中引入 core-js 和 regenerator-runtime。
  • 如何根据目标适配环境,按需引入业务中所需要的 polyfills 呢?

    • @babel/preset-env 通过 targets 参数,按照 browserslist 规范,结 合core-js-compat,筛选出适配环境所需的 polyfills(或 plugins)
    • 结合6 core-js及垫片polyfill思考。

      2.4 helpers 函数相关包

      @babel/plugin-transform-runtime结合@babel/runtime可以重复使用 Babel 注入的 helpers 函数,达到节省代码大小的目的:
      image.png
      在启用 @babel/plugin-transform-runtime 插件后,上述代码的编译结果可以变为:
      1. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
      2. var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
      3. var Person = function Person() {
      4. (0, _classCallCheck2.default)(this, Person);
      5. };
  • @babel/plugin-transform-runtime

    • 需要和 @babel/runtime 配合使用
      • 除了可以对产出代码瘦身以外,还能避免污染全局作用域。
    • 用于编译时,作为 devDependencies 使用;
    • 将业务代码编译,引用 @babel/runtime 提供的 helpers,达到缩减编译产出体积的目的;
  • @babel/runtime

    • 供业务代码引入模块化的 Babel helpers 函数
    • 提供了 regenerator-runtime,对 generator 和 async 函数进行编译降级。
    • 用于运行时,作为 dependencies 使用。

      2.5 非Node环境包

  • @babel/standalone

    • 可以在非 Node.js 环境自动编译含有 text/babel 或 text/jsx 的 type 值的 script 标签
  • @babel/standalone

    • 可以在浏览器中直接执行
    • 对于浏览器环境动态插入高级语言特性的脚本、在线自动解析编译非常有意义。

      2.6 插件包

  • @babel/plugin是 Babel 插件集合。

  • @babel/plugin-syntax-* 是 Babel 的语法插件。
    • 扩展 @babel/parser 的一些能力,提供给工程使用。
  • @babel/plugin-proposal-* 用于编译转换在提议阶段的语言特性。
  • @babel/plugin-transform-* 是 Babel 的转换插件。

    • 可以用来转换react相关代码

      2.7 其他

  • @babel/template

    • 封装了基于 AST 的模板能力,可以将字符串代码转换为 AST。
    • 在生成一些辅助代码(helper)时会用到这个库。
  • @babel/node
    • 类似 Node.js Cli,@babel/node 提供在命令行执行高级语法的环境
    • 运行时进行编译转换,因此运行时性能上会有影响
  • @babel/register

    • 实际上是为 require 增加了一个 hook,使用之后,所有被 Node.js 引用的文件都会先被 Babel 转码。
    • 运行时进行编译转换,因此运行时性能上会有影响

      3 Babel分层理念image.png

  • 基础层:提供了基础的编译能力,完成分词、解析 AST、生成产出代码的工作。

  • 辅助层:可以理解为各种utils,将一些抽象能力下沉为辅助层,这些抽象能力被基础层使用。
  • 胶水层:与corejs、polyfill协作的层,用来“把代码解释得大家都懂”
    • 最好结合6 core-js及垫片polyfill一起研究
    • (原文:完成了代码编译降级所需补丁的构建、运行时逻辑的模块化抽象等工作。)
  • 应用层:终端命令行、Webpack loader、浏览器端编译等应用级别的能力。

    3.1 特殊的babel-loader

    通过 Babel 对代码的编译过程,可以从微观上缩小为前端基建的一个环节,这个环节融入整个工程中,也需要和其他环节相互配合—— babel-loader 就是 Babel 结合 Webpack,融入整个基建环节的例子

    3.2 自定义eslint

  • ESLint 的解析工具只支持最终进入 ECMAScript 语言标准的特性

  • 如果想对试验性特性或者 Flow/TypeScript 进行代码检查,ESLint 提供了更换 parser 的能力。
    • @babel/eslint-parser 就是配合 ESLint 检验合法 Babel 代码的解析器。
    • 实现原理:ESLint 支持 custom-parser,它允许我们使用自定义的第三方编译器

使用了 espree 作为一个 custom-parser 的场景:

  1. {
  2. "parser": "./path/to/awesome-custom-parser.js"
  3. }
  4. // awesome-custom-parser.js
  5. var espree = require("espree");
  6. exports.parseForESLint = function(code, options) {
  7. return {
  8. ast: espree.parse(code, options),
  9. services: {
  10. foo: function() {
  11. console.log("foo");
  12. }
  13. },
  14. scopeManager: null,
  15. visitorKeys: null
  16. };
  17. };

@babel/eslint-parser源码的实现:

  1. export function parseForESLint(code, options = {}) {
  2. const normalizedOptions = normalizeESLintConfig(options);
  3. const ast = baseParse(code, normalizedOptions);
  4. const scopeManager = analyzeScope(ast, normalizedOptions);
  5. return { ast, scopeManager, visitorKeys };
  6. }
  • 返回值
    • ast 是 estree 兼容的格式,可以被 ESLint 理解。
    • visitorKeys 定义了自定义的编译 AST 能力
    • ScopeManager 定义了新(试验)特性自定义的作用域。

3.3 总结

由此可见,Babel 生态和前端工程中的各个环节都是打通开放的。它可以以 babel-loader 的形式和 Webpack 协作,也可以以 @babel/eslint-parser 的方式和 ESLint 合作。现代化的前端工程是一环扣一环的,作为工程链上的任意一环,插件化能力、协作能力将是设计的重点和关键。

这句话很好就原样摘抄了,babel因此具备很强的可拔插、可调试、灵活配置特性,我始终觉得这也是普通软件开发中应该参考的设计模式。