2022-2-24 About 36 min

本章从整体视角介绍 Babel 生态的全貌,并非详细地介绍其中的每一个细节,如希望了解详细的介绍,可根据需要查看本书其他章节。

# 1 Babel 简介

# 1.1 Babel 是什么?

Babel 是 JavaScript 转译器,通过 Babel,开发者可以自由使用下一代 ECMAScript 语法。高版本 ECMAScript 语法将被转译为低版本语法,以便顺利运行在各类环境,如低版本浏览器、低版本 Node.js 等。

Babel 是转译器,不是编译器。下面是转译和编译的区别:

编译,一般指将一种语言转换为另一种语法和抽象程度等都不同的语言,常见的比如 gcc 编译器。 转译,一般指将一种语言转换为不同版本或者抽象程度相同的语言,比如 Babel 可以把 ECMAScript 6 语法转译为 ECMAScript 5 语法。

利用 Babel,开发者可以使用 ECMAScript 的各种新特性进行开发,同时花极少的精力关注浏览器或其他JS运行环境对新特性的支持。甚至,开发者可以根据自身需要,创造属于自己的 JavaScript 语法。

Babel 在转译的时候,会对源码进行以下处理: 语法转译(Syntax)和添加 API Polyfill。

Babel概述/Babel转换过程.png

  • 语法(Syntax)部分

    Babel 支持识别高版本语法,并通过插件将源码从高版本语法转译为低版本语法,如:

    • 箭头函数 () => {} 转为普通函数 function() {}
    • const / let 转译为 var
  • API Polyfill

    有些运行时相关的 API,语法转译无法解决它们对低版本浏览器等环境的兼容性问题,因此 Babel 通过与 core-js 等工具的配合,实现 API 部分对目标环境(通常是低版本浏览器等)的兼容。

    例如[1, 2, 3].includePromise等 API,Babel 在处理时,如果目标环节可能不支持原生的 include / Promise 的话,Babel 会在转译结果中嵌入 include / Promise 的自定义实现。

有多种方式可以使用 Babel,如: 命令行(babel-cli、babel-node)、浏览器(babel-standalone)、API调用(babel-core)、webpack loader(babel-loader)等。

# 1.2 转译过程

和多数转译器相同,Babel 运行的生命周期主要是 3 个阶段: 解析、转换、代码生成。

这个过程涉及抽象语法树:

抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。

AST 是树形对象,以结构化的形式表示编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

Babel概述/AST-demo.png

源码字符串需要经转译器生成 AST,转译器有很多种,不同转译器,生成的AST对象格式细节可能有差异,但共同点为: 都是树形对象、该树形对象描述了节点特征、各节点之间的关系(兄弟、父子等)。

以下是 Babel 生命周期的三个过程:

  • 解析(Parsing): Code1 ==> 抽象语法树1

    解析过程包括 2 个环节: 词法解析、语法解析,最终生成抽象语法树。

    词法解析阶段,代码字符串被解析为 token 令牌数组,数组项是一个对象,包括: 代码字符碎片的值、位置、类型等信息。

    token 数组是平铺式的数组,没有嵌套的结构信息,它是为语法解析阶段做准备的。

    语法解析阶段,token 令牌数组被解析为结构化的抽放语法树对象(AST)。

    babel-parser 完成该阶段的主要功能。

    Babel概述/AST-tokens

  • 转换(Transformation): AST1 ==> AST2

    Babel 生成 AST 后,会对 AST 进行遍历,遍历过程中,各类插件对原 AST 对象进行增删改查等操作,AST 结构被修改。

    Babel概述/AST-转换

  • 代码生成(Generation): AST2 ==> Code2

    Babel 将修改后的 AST 对象转目标代码字符串。

    babel-generator 完成该阶段的主要功能。

    Babel概述/AST-生成代码

# 2 Babel 模块拆解

# 2.1 微内核架构

Babel 采用微内核架构,其内核保留核心功能,其余功能利用外部工具和插件机制实现,也体现了"开放-封闭"的设计原则。

Babel概述/微内核架构

除了微内核设计架构,Babel 的模块设计也可以做如下分类:

Babel概述/模块分层

# 2.2 转译模块

转译模块位于 Babel 微内核架构的"微内核"部分,该部分主要负责代码转译,也就是上面提到的"解析-转换-代码生成"过程。

该模块主要包括: babel-parser、babel-traverse、babel-generator。

  • babel-parser

    负责将代码字符串转为 AST 对象。

    有 2 点值得一提:

    • babel-parser 本身并不会对 AST 做转换操作,只是负责解析出 AST。AST 转换部分交由各类 plugins 和 presets 处理。
    • babel-parser 内置了对 ESNext/TypeScript/JSX/Flow 最新版本语法的支持,但很多默认是不开启的,目前没有开放插件机制扩展新语法。
  • babel-traverse

    在转译过程中,babel-traverse 负责遍历 AST 节点,并根据配置的 plugins/presets,在遍历过程中,对各个 AST 节点进行增删改查的操作。

    AST 是一个树形对象,遍历 AST 对象的过程也是一个深度优先遍历的过程。

  • babel-generator

    负责将 AST 节点,转为代码字符串,同时也可以生成 source-map。

# 2.3 插件模块

插件模块包括 plugins、presets。

  • plugins

    丰富的插件,帮助 Babel 成为一个非常成功的转译工具。

    对 AST 的遍历、转换是 Babel 转译的核心功能,但 Babel 本身并不参与该过程,将这些功能作为插件引入到运行时。

    具体来说,babel-core 作为核心工具,不提供对 AST 的修改逻辑,通过调用各类插件,实现对 AST 的修改。

    Babel的插件分为语法插件和转换插件。

    • 语法插件

      值得注意的是,babel-parser 负责将 JavaScript 代码解析出抽象语法树(AST),它支持全面识别 ESNext/TypeScript/JSX/Flow 等语法,目前由 Babel 团队开发维护,不支持插件化。

      Babel 插件生态中的语法插件,其功能就是作为"开关",配置是否开启 babel-parser 的某些语法转译功能。

      语法插件在 Babel 源码中,以 babel-plugin-syntax 开头。

      举个例子:

      • babel-plugin-syntax-decorators

        负责开启 babel-parser 对装饰器的语法支持。

      • babel-plugin-syntax-dynamic-import

        负责开启 babel-parser 对 import 语句的语法支持。

      • babel-plugin-syntax-jsx

        负责开启 babel-parser 对 jsx 语法的支持。

    • 转换插件

      转换插件就是社区里常说的 Babel 插件,负责转换 AST 节点。

      在介绍 babel-traverse 时提到,它负责遍历 AST 对象,每个 AST 节点会被访问到,等待转换,转换的部分,由"转换插件"负责。

      转换插件会提供一个叫做"Visitor"的对象,该对象的 Key 为节点名称,Value 部分提供进入该节点时、离开该节点时的回调函数,在回调函数里,可以对该节点进行一系列操作。

      "Visitor" 又称为 "访问者"。

      // plugin 提供 visitor,在 visitor 中对 AST 节点操作
      const visitor = {
          Program: {
              enter() {},
              exit() {},
          },
      
          CallExpression: {
              enter() {},
              exit() {},
          },
      
          NumberLiteral: {
              enter() {},
              exit() {},
          }
      };
      
      traverse(ast, visitor);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

      转换插件在 Babel 源码中,以 babel-plugin-transform 开头。

      举个例子:

      • babel-plugin-transform-strict-mode

        该插件拦截 Program 节点,也就是整个程序的根节点,添加 "use strict" 指令。

        visitor 节点值为函数时,是 enter 回调的快捷方式。

        {
            name: "transform-strict-mode",
        
            visitor: {
              Program(path) {
                const { node } = path;
        
                for (const directive of (node.directives: Array<Object>)) {
                  if (directive.value.value === "use strict") return;
                }
        
                path.unshiftContainer(
                  "directives",
                  t.directive(t.directiveLiteral("use strict")),
                );
              },
            },
          };
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
      • babel-plugin-transform-object-assign

        该插件负责拦截函数调用表达式节点 CallExpression,将 Object.assign 转为 extends 写法。

        {
            name: "transform-object-assign",
        
            visitor: {
              CallExpression(path, file) {
                if (path.get("callee").matchesPattern("Object.assign")) {
                  path.node.callee = file.addHelper("extends");
                }
              },
            },
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
  • presets

    Babel 插件的功能是细粒度的,大部分插件承担了单一功能。

    而在实际开发过程中,往往需要支持对各类语法的支持。此时,有两种做法:

    1. 需要支持哪些特性,就分别引入支持该特性的插件
    2. 直接引入一个插件集合,涵盖所需的各类插件功能

    很显然,第一种做法是相对麻烦的。针对第二种做法,Babel 提供了插件集 preset。

    preset 在 Babel 源码中,以 babel-preset 开头。

    例如,Babel 已经提供了几种常用的 preset 供开发者使用:

    • babel-preset-env
    • babel-preset-react
    • babel-preset-flow
    • babel-preset-typescript
  • 插件运行顺序

    Babel 配置项中,plugins 和 presets 均以数组的形式配置,执行时有先后顺序。

    • plugins 在 presets之前运行
    • plugins 按照数组正序执行
    • presets 按照数组倒序执行

    Babel概述/插件运行顺序

# 2.4 工具模块

工具模块提供 Babel 相关模块所需的各类工具,以下一一简要介绍:

  • babel-core

    babel-core 对外提供了 Babel 多项功能的 API,如转译文件、转译代码、创建/获取配置等,在 Babel 官方文档介绍的比较详细,不再赘述。

    值得注意的是,转译类的 API 均提供了同步和异步版本,如 transformSync/transfomAsyncparseSync/parseAsync

  • babel-cli

    Babel 的命令行工具,可以直接转译文件夹/文件,它也提供了很多配置项做其他工作,官方文档介绍的比较详细,感兴趣的同学可以去 Babel 官网查看详细配置。

  • babel-standalone

    Babel 对外服务的很多包是基于 node 环境下使用的,babel-standalone 提供了浏览器下转译的方案。

    babel-standalone 内置了所有 Babel 插件,所以体积还是比较大的,而且在浏览器端转译需要时间,比较适合开发、学习使用,不适合在生产环境使用。

    举个例子:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>test babel-standalone</title>
        <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
        <script type="text/babel">
          const arr = [1, 2, 3];
          console.log(...arr);
        </script>
      </head>
      <body>
      </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    在浏览器运行该 html,可以看到,页面结构变成了:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>test babel-standalone</title>
        <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
        <script type="text/babel">
          const arr = [1, 2, 3];
          console.log(...arr);
        </script>
        <script>
            "use strict";
            var _console;
            var arr = [1, 2, 3];
            (_console = console).log.apply(_console, arr);
            //# sourceMappingURL=data:application/json;charset=utf-8;base64...
        </script>
      </head>
      <body>
      </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
  • babel-node

    提供在命令行执行高级语法的环境。

    例如:

    // index.js 里可以使用高级语法
    babel-node -e index.js
    
    1
    2

    index.js 文件以及被其引入的其他文件均可以使用高级语法了。

    和 babel-cli 不同的是,babel-cli 只负责转换,不在 node 运行时执行;babel-node 会在 node 运行时执行转换,不适合生产环境使用。

  • babel-register

    在源文件中,引入babel-register,如 index.js:

    index.js

    require('babel-register');
    require('./run');
    
    1
    2

    run.js

    import fs from 'fs';
    console.log(fs);
    
    1
    2

    执行 node index 时,run.js 就不需要被转码了。

    babel-register 通过拦截 node require 方法,为 node 运行时引入了 Babel 的转译能力。

  • babel-loader

    babel-loader 并不是 Babel 官方提供的对外服务工具,它是独立的另一个项目,利用 babel-core 的 API 封装的 webpack loader,用于 webpack 构建过程。

  • babel-types

    babel-types 是一个非常强大的工具集合,它集成了节点校验、增删改查等功能,是 Babel 核心模块开发、插件开发等场景下不可或缺的工具。

    例如:

    const t = require('@babel/types');
    const binaryExpression = t.binaryExpression('+', t.numericLiteral(1), t.numericLiteral(2));
    
    1
    2
  • babel-template

    模板引擎,负责将代码字符串转为 AST 节点对象。

    import { smart as template } from '@babel/template';
    import generate from '@babel/generator';
    import * as t from '@babel/types';
    
    const buildRequire = template(`
      var %%importName%% = require(%%source%%);
    `);
    
    const ast = buildRequire({
        importName: t.identifier('myModule'),
        source: t.stringLiteral("my-module"),
    });
    
    const code = generate(ast).code
    
    console.log(code)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    运行结果:

    var myModule = require("my-module");
    
    1
  • babel-code-frame

    负责打印出错的代码位置,例如:

    const { codeFrameColumns } = require('@babel/code-frame');
    
    const testCode = `
    class Run {
        constructor() {}
    }
    `;
    
    const location = {
        start: {
            line: 2,
            column: 2,
        }
    };
    
    const result = codeFrameColumns(testCode, location);
    
    console.log(result);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      1 | class Run {
    > 2 |     constructor() {}
        |  ^
      3 | }
      4 |
    
    1
    2
    3
    4
    5
  • babel-highlight

    向控制台输出有颜色的代码片段。该工具可以识别 JavaScript 中的操作符号、标识符、保留字等类型的词法单元,并在终端环境下显示不同的颜色。

# 2.5 运行时相关模块

Babel 配合其插件可以对静态代码进行转译,但有一些遗漏点:

  • 对于运行时涉及的一些高版本 API,并没有提供兼容目标环境的 Polyfill
  • 转译产物代码可能有些臃肿

为此,运行时模块(runtime)关注的是转译产物的运行时环境,对运行时提供 API polyfill、代码优化等,该模块涉及几个子包:

  • babel-preset-env
  • babel-plugin-transform-runtime
  • babel-runtime
  • babel-runtime-corejs2/3
  • core-js

接下来以案例解释 runtime 模块的作用。

源码文件 index.js 的内容:

const a = 1; // const 为语法部分
class Base {} // class 为语法部分
new Promise() // Promise 为 API 部分
1
2
3

这段源码包含了语法和 API 部分:

  • constclass 为语法部分
  • Promise 为 API 部分

如果希望这段源码转为 ES5 版本,使构建产物可以运行在不支持 ES6 和 Promise 的环境里,该怎么做呢?

用 babel 命令行执行转译,其中源文件为 index.js,转译产物文件为 index-compiled.js。

npx babel index.js --out-file index-compiled.js
1

安装 node 或 npm 后,可直接使用 npx 唤起各类命令行执行

需要配置 .babelrc 帮助 Babel 完成语法和 API 部分的转译:

.babelrc:

{
    "presets": [
        [ 
            "@babel/preset-env"
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3
            }
        ]
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

简要解释下该配置的原理:

  • babel-preset-env 可以完成语法部分转译,即 const 转译为var

    但构建产物中,有些辅助代码如 _classCallCheck 是以硬编码的形式直接写入转译产物的:

    "use strict";
    
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    var a = 1;
    
    var Base = function Base() {
        _classCallCheck(this, Base);
    };
    
    new Promise();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    这样的后果就是构建产物比较臃肿。

  • babel-plugin-transform-runtime 可以将上述 _classCallCheck 置于通用包中,以引用的形式写入转译产物:

    "use strict";
    
    var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
    
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
    
    var a = 1;
    
    var Base = function Base() {
        (0, _classCallCheck2["default"])(this, Base);
    };
    
    new Promise();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • babel-plugin-transform-runtime 的配置参数 corejs 用于转译 API 部分,如 Promsie

    "use strict";
    
    var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
    
    var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
    
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
    
    var a = 1;
    
    var Base = function Base() {
        (0, _classCallCheck2["default"])(this, Base);
    };
    
    new _promise["default"]();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

Babel 转译过程的运行时优化是一个繁琐的过程,为此将单独用一章讲解运行时优化,感兴趣的同学可以直接阅读 "Babel Runtime" 章节详细了解。

# 3 使用 Babel

# 3.1 对外工具集

在介绍 Babel 微内核架构时,面向用户使用的工具,有这些:

  • babel-cli
  • babel-node
  • babel-register
  • babel-standalone
  • babel-parser
  • babel-loader

由此看出,Babel 官方和社区提供了众多工具以满足开发者在不同场景下使用 Babel 的需要,下图是 Babel 可以使用的场景分类:

Babel概述/对外工具集

# 3.2 配置 Babel

# 多种配置方式

Babel 支持多种配置方式。

# 文件式配置

  • 配置文件列表

    • babel.config. 为前缀的文件

      • babel.config.json
      • babel.config.js
      • babel.config.cjs
      • babel.config.mjs
    • .babelrc 为前缀的文件

      • .babelrc.json
      • .babelrc.js
      • .babelrc.cjs
      • .babelrc.mjs

      其他工具如 ESLint、Prettier 也有类似的配置文件: .eslintrc、.prettierrc。

    • package.json 中

      package.json 中的 "babel" 字段。

  • 配置文件格式

    不同后缀名的配置文件,其配置内容需遵循不同的格式规范。

    • .cjs 文件,内容是 CommonJS 规范

      const presets = [ ... ];
      const plugins = [ ... ];
      
      module.exports = { presets, plugins };
      
      1
      2
      3
      4
    • .mjs 文件,内容是 ECMAScript modules 格式

      可以在 Node.js 13.2 及以上版本试用,也可以在较老版本 Node.js 的 --experimental-modules 标记下使用。

    • .js 文件内容,可以是 CommonJS 规范,也可以是 ECMAScript modules 格式

      如果在 package.json 中指定 "type": "module"babel.config.js.babelrc.js 文件即可使用 ECMAScript modules。

      否则,babel.config.js.babelrc.js 文件需要使用 CommonJS 规范。

    • .json 文件,内容可以是: JSON5 规范、CommonJS 规范

      JSON5 规范的写法:

      {
          "name": "my-package",
          "version": "1.0.0",
          "babel": {
              "presets": [ ... ],
              "plugins": [ ... ],
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8

# 命令行式配置

Babel 支持执行 babel 命令行时指定配置。

babel --plugins @babel/plugin-transform-arrow-functions script.js
1

# API 式配置

通过调用 Babel 提供的 API,并为其传参。

require("@babel/core").transformSync("code", {
    plugins: ["@babel/plugin-transform-arrow-functions"]
});
1
2
3

# 项目级配置与局部配置

Babel7 新增了项目级配置和局部配置的概念,其配置文件有两种作用范围: 完整的项目范围、子项目范围。

babel.config. 系列的配置文件,影响范围是整个项目。

.babelrc 系列的配置文件,影响范围是子项目。

官方文档对此有详细的描述,以下是笔者整理后的配置文件作用范围描述:

  • 项目级配置文件(babel.config. 系列)

  • 局部配置文件(.babelrc 系列)

    对于当前正在被转译的文件,Babel 会逐级向上查找 .babelrc 系列的配置文件,直到第一个有 package.json 的目录、或当前项目的 root 目录。

    找到局部配置文件后,会将其内容和 babel.config.json 之类的配置内容合并。

# 配置项

  • Primary options

    这些配置在 API 中使用,相对于文件形式(.babelrc 等)的配置,它们的优先级更高些,因此成为 Primary options

    • cwd: string

      默认值是: process.cwd()

      Babel 运行过程中,所有子路径的相对地址,应用于 API 调用时。

    • caller: CallerData

      interface CallerData {
          name: string;
          supportsStaticESM?: boolean;
          supportsDynamicImport?: boolean;
          supportsTopLevelAwait?: boolean;
          supportsExportNamespaceFrom?: boolean;
      }
      
      1
      2
      3
      4
      5
      6
      7

      提供给 Babel 自身配置项、pluginspresets 的一些快捷灵活配置项。

    • filename: string

      这是个非必填项。可用于 API 调用时,一些其他配置项依赖 filename 的配置。

      这里有一些场景可能需要配置 filename:

      • filename 的值可以暴露给插件做进一步处理
      • testexcludeignore 等配置项需要 filename 有值用于匹配
      • 不提供 filename 的情况下,查找 .babelrc.json.babelrc 这些配置文件时,会相对于被转译的目标文件;如果提供了 filename,会默认执行 babelrc: false 的逻辑
    • filenameRelative: string

      默认值: path.relative(opts.cwd, opts.filename)

      filenameRelative 的值会是配置项 sourceFileName 的默认值。

    • code: boolean

      默认是 true

      默认情况下,api 如 transformSync 在转译后会返回转译后的代码,也就是 code。但可能存在不需要 code 的时候,比如在多级转译时,前后逻辑仅需要 AST 传递即可。

    • ast: boolean

      默认是 false,api 如 transformSync 并不会返回转移后代码的 AST 对象,也是出于性能考虑,因为 Babel 会优先为转译结果添加本地缓存文件,而 AST 对象需要占用挺大空间的。

      如果需要在多级转译时,利用中间过程产生的 AST 对象,就可以设置为 true 了:

      const filename = "example.js";
      const source = fs.readFileSync(filename, "utf8");
      
      // Load and compile file normally, but skip code generation.
      const { ast } = babel.transformSync(source, {
          filename,
          ast: true,
          code: false,
      });
      
      // Minify the file in a second pass and generate the output code here.
      const { code, map } = babel.transformFromAstSync(ast, source, {
          filename,
          presets: ["minify"],
          babelrc: false,
          configFile: false,
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
    • cloneInputAst: boolean

      默认值是 true

      例如:

      const { code, map } = babel.transformFromAstSync(ast, source, {
          filename,
          presets: ["minify"],
          babelrc: false,
          configFile: false,
      });
      
      1
      2
      3
      4
      5
      6

      babel.transformFromAstSync 会接受一个 ast 对象进行各种操作,内部会复制一个新的 ast 对象避免修改传入的 ast 对象。当然开发者可以决定是否需要复制,毕竟复制的开销是比较大的。

  • Config Loading options

    • root

      仅用于 API 调用环境。类型是 string,默认值是 opts.cwd 的值,opts.cwd 的默认值是 process.cwd()

      这是一个基准路径,与 rootMode 配合使用,它决定了当前 Babel 作用项目的根路径(root folder)是什么。

      这个基准值可以用来:

      • 作为查找 configFile 配置项的基准路径
      • 作为 babelrcRoots 配置项的默认值
    • rootMode

      仅用于 API 调用环境。类型是 "root" | "upward" | "upward-optional"。默认值是 "root"

      rootModeroot 配合使用,它决定了当前 Babel 作用项目的根路径。

      • "root": 和默认值一样,描述当前 Babel 作用项目的根路径
      • "upward": 沿着 root 配置项持续向上查找,发现 babel.config.json 文件后停止向上遍历,否则会一直遍历到系统根目录
      • "upward-optional": 和 "upward" 一样,但如果遍历到系统根目录也没有发现 babel.config.json 文件,会退回到 "root"
    • envName

      仅用于 API 调用环境。类型是 string。默认值是 process.env.BABEL_ENV || process.env.NODE_ENV || "development"

      可以在配置文件的 "env" 配置项里,或 api.env() 方法使用。

    • configFile

      仅用于 API 调用环境。类型是 string | boolean。默认值是 path.resolve(opts.root, "babel.config.json"),但内部会判断该路径的文件是否存在,如果不存在,值会被调整为 false

    • babelrc

      仅用于 API 调用环境。类型是 boolean。默认值是 true

    • babelrcRoots

  • Plugin and Preset options

    • plugins/presets

      plugin/preset 配置规范相同。

      对于 plugin/preset,如下配置方式的含义是一致的:

      {
          "plugins": [
              "pluginA",
      
              [ "pluginA" ],
      
              [ "pluginA", {} ]
          ]
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      [ "pluginA", {} ] 这种写法可以为 plugin/preset 描述更具体的配置,如:

      {
          "plugins": [
              [
                  "transform-async-to-module-method",
                  {
                      "module": "bluebird",
                      "method": "coroutine"
                  }
              ]
          ],
          "presets": [
              [
                  "env",
                  {
                      "loose": true,
                      "modules": false
                  }
              ]
          ]
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
    • passPerPreset

  • Output targets

    • targets

      类型 string | Array<string> | { [string]: string }。默认值 {}

      • 举例

        {
            "targets": "> 0.25%, not dead"
        }
        
        1
        2
        3

        要适配的目标环境的最小版本:

        {
            "targets": {
                "chrome": "58",
                "ie": "11"
            }
        }
        
        1
        2
        3
        4
        5
        6

        目标环境可以是: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron

      • 没有设置 targets

        如果没有设置 targets,Babel 默认会假设你要适配最古老的浏览器版本,当然这样是不推荐的,因为转译后的文件会比较大。

        截至本书截稿时,如果使用者没有指定 targets 的值,Babel 也不会像 browserslist 那样提供一个 "defaults" 的默认值。

      • targets.esmodules

        类型 boolean。默认值 ??

        可以指定支持 esmodules 的目标浏览器。

      • targets.node

        类型 string | "current" | true

      • targets.safari

        类型 string | "tp"

        可以通过设置为 "tp" 来指定 safari 的 technology preview 版本。

      • targets.browsers

        类型 string | Array<string>。它的语法是 browserslist 的查询语法。

    • browserslistConfigFile

      类型 boolean。默认值是 true。用于 browserslist 查询配置用。

    • browserslistEnv

      类型是 string。默认 undefined

  • Config Merging options

    • extends

      类型是 string

    • env

      类型是 { [envKey: string]: Options }

    • overrides

      类型是 Array<Options>

    • test

      类型是 MatchPattern | Array<MatchPattern>

    • include

      类型是 MatchPattern | Array<MatchPattern>

      test

    • exclude

      类型是 MatchPattern | Array<MatchPattern>

    • ignore

      类型 Array<MatchPattern>

      ignore: ["./lib"];
      
      1
    • only

      only: ["./src"];
      
      1
  • Source Map options

    • inputSourceMap

      类型 boolean | SourceMap。默认值 true

      • true: 如果存在注释 //# sourceMappingURL=...,会尝试在 Babel 要转译的文件中寻找 sourceMap 的信息,如果加载失败的话就忽略
      • SourceMap: 也可以传入一个 sourceMap 对象
    • sourceMaps

      类型 boolean | "inline" | "both"。默认值 false

      • true: 转译时会生成 sourceMap,并在转译结果对象中添加 sourceMap 结果对象
      • "inline": 转译时会生成 sourceMap,并在转译结果代码中添加以 data 开头的 sourceMap 代码
      • "both": 综合了 true"inline"
    • sourceMap

      sourceMaps 配置项,推荐使用 sourceMaps

    • sourceFileName

      类型 string。默认值 path.basename(opts.filenameRelative),如果不可用,会被重置为 unknown

      用于 sourceMap 对象内部。

    • sourceRoot

      类型 string

      设置生成的 sourceMap 中的 sourceRoot 字段。

  • Misc options

    • sourceType

      类型 "script" | "module" | "unambiguous"。默认 "module"

      • "script": 使用 ECMAScript Script 语法转译。不允许出现 import/export 语句的写法。
      • "module": 使用 ECMAScript Module 语法转译。允许出现 import/export 语句的写法,并默认转译为严格模式。
      • "unambiguous": 自动化判断,如果源码中出现了 import/export 语句,就用 "module" 模式,否则用 "script" 模式。
    • assumptions

      类型 { [assumption: string]: boolean }。默认 {}

      允许使用在 API 使用、配置文件、presets 中。

      它可以用来减少转译结果大小。

      {
          "assumptions": {
              "iterableIsArray": true
          },
          "presets": ["@babel/preset-env"]
      }
      
      1
      2
      3
      4
      5
      6
    • highlightCode

      类型 boolean。默认 true

      在错误提示里对源码高亮展示。

    • wrapPluginVisitorMethod

      类型 (key: string, nodeType: string, fn: Function) => Function

      该方法可用于 AST 节点被内部访问等处理时,开发者做拦截处理。

      • key: ??
      • nodeType: AST 节点类型
      • fn: AST 节点内部处理函数

      开发者需要返回一个新的函数,并在新函数里执行旧的处理函数 fn

    • parserOpts

      类型 {}。用于给 babel-parser 提供配置项。

    • generatorOpts

      类型 {}。用于给 babel-generator 提供配置项。

  • Code Generator options

    • retainLines

      类型 boolean。默认值 false

      ??

    • compact

      类型 boolean | "auto"。默认值 "auto"

      ??

    • minified

      类型 boolean。默认值 false

      如果设置为 true,会省略块级代码结尾的分号、省略 new Foo() 中的 ()、生成更短形式的字面量描述方式等。

    • auxiliaryCommentBefore

      类型 string

    • auxiliaryCommentAfter

    • comments

      类型 boolean。默认 true

    • shouldPrintComment

      类型 (value: string) => boolean

      如果同时设置了 minified,那么其默认值是 (val) => opts.comments || /@license|@preserve/.test(val)

      如果没有设置 minified,那么其默认值是 () => opts.comments

      用于决定哪些注释可以在最终输出的转译结果中保留(通常注释是会被删除的)。

  • AMD / UMD / SystemJS module options

    • moduleIds

      类型 boolean。默认 !!opts.moduleId

      是否开启 module ID 的转译。

    • moduleId

      类型 string

      用于描述当前转译的 module ID,这是硬编码的。不能与 getModuleId 同时使用。

    • getModuleId

      类型 (name: string) => string

      ??

    • moduleRoot

      类型 string

      为生成的 module 名字上添加根路径。

  • Options Concepts

    • MatchPattern

      类型 string | RegExp | (filename: string | void, context: { caller: { name: string } | void, envName: string, dirname: string ) => boolean

# 配置合并规则

Babel 配置的合并遵循以下方式:

  • Array#concat

    针对 plugins/presets,用 Array#concat 合并,因为它们均为数组。

  • Object.assign

    plugins/presets 之外的配置,使用 Object.assign 做覆盖式合并。

下面是官方文档的案例:

const config = {
    plugins: [
        [
            "plugin-1a", { loose: true }
        ], 
        "plugin-1b"
    ],
    presets: [
        "preset-1a"
    ],
    sourceType: "script"
}

const newConfigItem = {
    plugins: [
        [
            "plugin-1a", { loose: false }
        ], 
        "plugin-2b"
    ],
    presets: [
        "preset-1a", 
        "preset-2a"
    ],
    sourceType: "module"
}

BabelConfigMerge(config, newConfigItem);

// returns
({
    plugins: [
        [
            "plugin-1a", { loose: true }
        ],

        "plugin-1b",

        [
            "plugin-1a", { loose: false }  // concat 推入的配置
        ],

        "plugin-2b" // concat 推入的配置
    ],

    presets: [
        "preset-1a",
        "preset-1a",  // concat 推入的配置
        "preset-2b"  // concat 推入的配置
    ],

    sourceType: "module" // sourceType: "script" 值被新值覆盖
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# Plugins/Presets 配置

Babel 支持对 Plugins 和 Presets 多种形式的配置,可配置的方式有:

  • EntryTarget
  • [EntryTarget, EntryOptions]
  • [EntryTarget, EntryOptions, string]
  • ConfigItem
plugins: [
    // EntryTarget
    '@babel/plugin-transform-classes',

    // [EntryTarget, EntryOptions]
    ['@babel/plugin-transform-arrow-functions', { spec: true }],

    // [EntryTarget, EntryOptions, string]
    ['@babel/plugin-transform-for-of', { loose: true }, "some-name"],

    // ConfigItem
    babel.createConfigItem(require("@babel/plugin-transform-spread")),
],
1
2
3
4
5
6
7
8
9
10
11
12
13
  • EntryTarget: string | {} | Function

    • string: 要求是 Babel plugin/preset 的路径、名字(如 env / @babel/preset-env,会经过格式化的)。
    • {} | Function: 要求是 Babel plugin/preset 对象或函数。
  • EntryOptions: undefined | {} | false

    • undefined: 没有设置,内部会重置为空对象 {}

    • false: 禁用该 plugin/preset,这个可用于提升转译速度等情况下,由开发者决定是否开启。

      但是 overrides 配置项的优先级高于它:

      plugins: [
          'one',
          [
              'two',
              false
          ],
          'three',
      ],
      overrides: [
          {
              test: "./src",
              plugins: [
                  'two',
              ]
          }
      ]
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      以上配置的效果是:

      • two 会参与对 ./src 路径下文件的转译,其他路径不参与
      • two 的转译依然发生在 onethree 之间
  • 对 plugin/preset 名称的格式化

    Babel 支持多种对 plugin/preset 名称的简写,当然要遵循一定的规则:

    • 绝对路径
    • ./ 开头的相对路径
    • moudle: 开头的名称
    • @babel 开头的名称内,会默认为添加 plugin-preset- 前缀,如 @babel/env 会被格式化为 @babel/preset-env

    | 输入 | 格式化结果 | 规则 | | "/dir/plugin.js" | "/dir/plugin.js" | 绝对路径不处理 | | "./dir/plugin.js" | "./dir/plugin.js" | 相对路径不处理 | | "mod" | "babel-plugin-mod" | 未声明命名空间 @babel,且没有前缀的,会新增 babel-plugin 前缀 | | "mod/plugin" | "mod/plugin" | | | "babel-plugin-mod" | "babel-plugin-mod" | 有 babel-plugin 前缀,不处理 | | "@babel/mod" | "@babel/plugin-mod" | 以 @babel 命名空间开头,会添加 plugin 前缀 | | "@babel/plugin-mod" | "@babel/plugin-mod" | 以 @babel 命名空间开头,且有 plugin 前缀,不处理 | | "@babel/mod/plugin" | "@babel/mod/plugin" | | | "@scope" | "@scope/babel-plugin" | 在 @ 开头的某个命名空间上,添加 babel-plugin 前缀 | | "@scope/babel-plugin" | "@scope/babel-plugin" | | | "@scope/mod" | "@scope/babel-plugin-mod" | 在 @ 开头的某个命名空间上,添加 babel-plugin 前缀 | | "@scope/babel-plugin-mod" | "@scope/babel-plugin-mod" | | | "@scope/prefix-babel-plugin-mod" | "@scope/prefix-babel-plugin-mod" | | | "@scope/mod/plugin" | "@scope/mod/plugin" | | | "module:foo" | "foo" | 以 module: 开头,会移除 module: |

# 4 Babel 的项目管理

# 4.1 monorepo

如果一个大型项目由多个子工程组成,该如何管理呢?

通常来说,有以下方式:

  • monorepo

    monorepo 指的是,该项目所有工程统一在一个仓库中,维护、发布等操作统一进行。

    目前 Babel、React、Angular 等均采用该方式。

    monorepo 的代码结构为:

    ├── packages
    |   ├── pkg1
    |   |   ├── package.json
    |   ├── pkg2
    |   |   ├── package.json
    ├── package.json
    
    1
    2
    3
    4
    5
    6
  • multirepo

    和 monorepo 相反,该项目的每个子工程独立运行,每个子工程相互独立,自由选择构建工具,独立发布。

    multirepo 的代码结构为:

    ├── project1
    |   ├── package.json
    ├── project2
    |   ├── package.json
    ├── project3
    |   ├── package.json
    
    1
    2
    3
    4
    5
    6
  • submodules

    submodules 是借助 git 的实现,在 .gitmodules 中写明引用的仓库,在主仓库中只保留必要的索引。

在此主要关注 monorepo 和 multirepo。二者对比如下:

monorepo multirepo
所有项目在同一个分支内开发 所有项目独立开发
统一的 issue 管理 分散的 issue 管理
统一的开发环境 分散的开发环境
统一管理所有项目的版本 各项目难以统一版本
项目所需存储空间大 项目分散,存储空间较小

Babel 项目涉及的子工程很多,其采用了 monorepo 方式开发,并且沉淀出了自动化工具 lerna。

# 4.2 lerna

  • 两种模式

    monorepo 有两种模式: fixedindependent,默认为 fixed 模式。

    • fixed 模式

      该模式强制所有的包都使用在根目录 lerna.json 中指定的版本号。

      lerna 默认使用该模式。

      该模式下,每个 package 呈现"黑盒"状态,所有包统一迭代。

    • independent模式

      该模式允许各个包独立指定自己的版本号。

      可以使用 lerna init --independent 声明该模式,也可以将 lerna.jsonversion 字段指定为 independent

      对于需要暴露内部细节、或者迭代频率显著不一致的包,比较适合该模式。

  • 命令集

    以下是 lerna 的部分命令及功能:

    • lerna init: 初始化一个项目
    • lerna create: 创建一个子 package
    • lerna import: 将已有项目导入为子 package
    • lerna list: 列举当前项目所有子 package
    • lerna add: 添加公共依赖
    • lerna clean: 删除子 package 的 node_modules
    • lerna bootstrap: 安装依赖、创建子 package 间的 symlink
    • lerna diff: 类似 git diff
    • lerna changed: 列出子 package 的所有变更
    • lerna exec: 在所有子 package 下执行 shell
    • lerna run: 在所有子 package 下执行 npm scripts 命令
    • lerna link: 将彼此依赖的子 package 关联起来
    • lerna publish: 发布子 package
    • lerna version: 确认发布的版本号

    后面的章节对 lerna 有更详细的介绍。

# 5 标准化

Babel 生态涉及的一些标准化组织。无论是 JavaScript、HTML、DOM、URL 等领域,均需要统一的标准,才能在不同的运行环境下有统一的表现。Babel 转译也需要遵循这些标准,包括 ECMAScript、web 标准等。

// JavaScript 20年: https://cn.history.js.org/part-2.html

https://zhuanlan.zhihu.com/p/22557749

# 5.1 ECMAScript

# JavaScript诞生

1995 年,JavaScript 的第一个版本发布。用时间线的方式描述 JavaScript 的诞生过程会更清晰:

标准化/JavaScript诞生

# ECMAScript发布

1996 年,微软模仿 JavaScript 实现了 JScript 并内置在 IE3.0,随后,Netscape 公司着手推动 JavaScript 标准化。

这里涉及几个组织:

  • Ecma International

    Ecma International 是一家国际性会员制度的信息和电信标准组织。1994年之前,名为欧洲计算机制造商协会(European Computer Manufacturers Association)。因为计算机的国际化,组织的标准牵涉到很多其他国家,因此组织决定改名表明其国际性。

    Ecma International 的任务包括与有关组织合作开发通信技术和消费电子标准、鼓励准确的标准落实、和标准文件与相关技术报告的出版。

    Ecma International 负责多个国际标准的制定:

    • CD-ROM 格式(之后被国际标准化组织批准为ISO 9660)
    • C# 语言规范
    • C++/CLI 语言规范
    • 通用语言架构(CLI)
    • Eiffel 语言
    • 电子产品环境化设计要素
    • Universal 3D 标准
    • OOXML
    • Dart 语言规范
    • ECMAScript 语言规范(以 JavaScript 为基础)ECMA-262

    其中就包括 JavaScript 标准语言规范 ECMAScript。

    Ecma International 拥有 ECMAScript 的商标。

  • ECMA TC39

    「TC39」全称「Technical Committee 39」译为「第 39 号技术委员会」,是 Ecma International 组织架构中的一部分。

    TC39 负责迭代和发展 ECMAScript,它的成员由各个主流浏览器厂商的代表组成,通常每年召开约 6 次会议来讨论未决提案的进展情况,会议的每一项决议必须得到大部分人的赞同,并且没有人强烈反对才可以通过。

    TC39 负责:

    • 维护和更新 ECMAScript 语言标准
    • 识别、开发、维护 ECMAScript 的扩展功能库
    • 开发测试套件
    • 为 ISO/IEC JTC 1 提供标准
    • 评估和考虑新添加的标准
  • ISO

    国际标准化组织(英语: International Organization for Standardization,简称: ISO)成立于 1947 年 2 月 23 日,制定全世界工商业国际标准的国际标准建立机构。

    ISO 总部设于瑞士日内瓦,现有 164 个会员国。该组织定义为非政府组织,官方语言是英语、法语和俄语。参加者包括各会员国的国家标准机构和主要公司。

    ISO 的国际标准以数字表示,例如: "ISO 11180:1993" 的 "11180" 是标准号码,而 "1993" 是出版年份。

    ISO/IEC JTC 1 是国际标准化组织和国际电工委员会联合技术委员会。其目的是开发、维护和促进信息技术以及信息和通信技术领域的标准。JTC 1 负责了许多关键的 IT 标准,从 MPEG 视频格式到 C++ 編程語言。

    标准化/ECMA-ISO

  • ECMAScript 发展过程中的关键节点

    标准化/JavaScript标准化

# ECMAScript 各版本

ECMAScript 经历了多个版本,每个版本有自己的特点,简单列举如下:

标准化/ECMA版本

# ECMAScript 迭代过程

https://erasermeng.github.io/2017/07/12/ECMAScript%E6%B5%81%E7%A8%8B%E8%A7%84%E8%8C%83%E7%AE%80%E4%BB%8B/

一个 ECMAScript 标准的制作过程,包含了 Stage 0 到 Stage 4 共 5 个阶段,每个阶段提交至下一阶段都需要 TC39 审批通过。

标准化/ECMA迭代流程

特性进入 Stage-4 后,才有可能被加入标准中,还需要 ECMA General Assembly 表决通过才能进入下一次的 ECMAScript 标准中。

# 5.2 如何阅读 ECMAScript

# ECMAScript 文档结构

https://timothygu.me/es-howto/

http://www.ruanyifeng.com/blog/2015/11/ecmascript-specification.html

ECMAScript 的规格,可以在 ECMA 国际标准组织的官方网站免费下载和在线阅读。ECMAScript 不同版本的规格有独立的网址,网址格式为: www.ecma-international.org/ecma-262/{version}/。比如 ECMAScript 6.0 版本的网址为 www.ecma-international.org/ecma-262/6.0/。截止本书截稿时,官方网站支持的版本有: 6.0/7.0/8.0/9.0/10.0/11.0

ECMAScript 6.0ECMAScript 7.0 有 26 章,之后的版本有 27 章,虽然章节数量不同,规格章节的分布是保持一定规律的,以 ECMAScript 11.0 版本为例:

  • Introduction: 介绍部分

    该章节简要描述了: JavaScript 和 ECMAScript 的发展历史、不同 ECMAScript 规格的主要更新内容。

  • 第 1 章到第 3 章: 描述了规格文件本身,而非语言

    第 1 章用一句话描述了该规格的描述范围。

    第 2 章描述了基于规格的"实现"的一致性要求:

    • "实现"必须支持规格中描述的所有类型、值、对象、属性、函数以及程序的语法和语义
    • "实现"必须按照 Unicode 标准和 ISO/IEC 10646 的最新版本处理文本输入
    • "实现"如果提供了应用程序编程接口(API),那么该 API 需要适应不同的人文语言和国家差异,且必须实现最新版本的 ECMA-402 所定义的与本规范相兼容的接口
    • "实现"可以支持该规格中没有提及的类型、值、对象、属性、函数、正则表达式语法以及其他编程写法
    • "实现"不能实现该规格中禁止的写法

    第 3 章描述了该规格的一些参考资料:

    • ISO/IEC 10646
    • ECMA-402
    • EMCA-404 JSON 数据交换格式规范
  • 第 4 章: 对这门语言总体设计的描述。

  • 第 5 章到第 8 章: 语言宏观层面的描述。

    第 5 章是规格的名词解释和写法的介绍。

    第 6 章介绍数据类型。

    第 7 章介绍语言内部用到的抽象操作。

    第 8 章介绍代码如何运行。

  • 第 9 章到第 27 章: 介绍具体的语法。

一般而言,除非写编译器,开发者无需阅读 ECMAScript 的规格,规格的内容非常多,如无必要也无需通读。只是在遇到一些奇怪的问题时,阅读官方规格,是最稳妥的办法。

# 通过阅读规格解决一些问题

  • 识别关键词和保留字,并高亮

    Babel 工具集中的 babel-highlight,可以实现在终端对代码块中的目标字符单元显示不同的颜色。这里需要识别不同字符单元的类型,如关键字、保留字、标识符、数字、字符串等。

    标识符、数字、字符串都很好理解和识别,但哪些字符应该被识别为关键字、保留字,而不是标识符呢?

    此时可以阅读 ECMAScript 规格了,ECMAScript 11.0 规格的 11.6.2 节介绍了关键词和保留字列表。

    • 关键词(keywords)

      关键词首先是标识符,同时有语义,包括 if、while、async、await...,个别关键词是可以用作变量名的。

    • 保留字(reserved word)

      保留字首先是标识符,但不能用作变量名。

      部分关键词是保留字,但部分不是: if、while 是保留字;await 只有在 async 方法和模块中才是保留字;async 不是保留字,它可以作为普通的变量名使用。

    • 保留字列表

      await break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null returns uper switch this throw true try typeof var void while with yield
      
      1

    读完上述规格,也就知道哪些字符单元是需要识别为保留字与关键词,并高亮的了。

  • 识别全局对象,并高亮

    继续使用 babel-highlight 实现代码块中的全局对象高亮,那么,我们需要知道哪些是规格中描述的全局变量。

    规格的 18 章介绍了全局对象,通过该章的描述,可以知道:

    • 全局属性

      全局属性有: globalThisInfinityNaNundefined

    • 全局方法

      全局方法有: eval(x)isFiniteisNaNparseFloatparseIntdecodeURIComponentencodeURIComponent 等。

    • 全局构造函数

      全局的构造函数有: ArrayArrayBufferBigIntBigInt64ArrayBigUnit64ArrayBooleanDataViewDateErrorEvalErrorFloat32ArrayFloat64ArrayFunctionInt8ArrayInt16ArrayInt32ArrayMapNumberObjectPromiseProxyRangeErrorReferenceErrorRegExpSetSharedArrayBufferStringSymbolSyntaxErrorTypeErrorUint8ArrayUint8ClampedArrayUint16ArrayUint32ArrayURIErrorWeakMapWeakSet

    • 其他的全局属性

      AtomicsJSONMathReflect

    很显然,当字符单元的名称是上述名称中的一员时,我们可以对其进行高亮处理了(若上下文中无重新定义的同名变量)。

  • 自定义 Error

    在介绍 Babel 工具集中的 babel-loader 时,babel-loader 自身维护了私有的 LoaderError 对象,该对象继承自原生 Error 类,并且订制了部分实例属性。代码如下:

    class LoaderError extends Error {
        constructor(err) {
            super();
    
            const { name, message, codeFrame, hideStack } = format(err);
    
            this.name = "BabelLoaderError";
    
            this.message = `${name ? `${name}: ` : ""}${message}\n\n${codeFrame}\n`;
    
            this.hideStack = hideStack;
    
            Error.captureStackTrace(this, this.constructor);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    可以看到,babel-loader 自定义了错误实例的 namemessagehideStack 属性,那么,问题是,原生的 Error 类有哪些属性和方法,哪些是开发者可以自定义的呢?

    规格的 19.5 章节,详细介绍了 Error 的各类规范:

    • Error 作为函数被调用时(Error(...)),表现和 new Error(...) 一致,均会创建并返回 Error 的新实例
    • Error 可以被继承,比如通过 extends 的方式,子类必须提供 constructor 方法,且该方法内必须提供 super() 调用
    • Error 构造函数必须有 prototype 属性
    • Error.prototype 属性需有以下属性
      • Error.prototype.constructor: 指向构造函数
      • Error.prototype.message: 描述错误信息,默认是空字符串
      • Error.prototype.name: 描述错误名称,默认值是 Error
      • Error.prototype.toString:

    LoaderError 的源码可以看到,LoaderError 做了以下几件事情:

    • LoaderError 继承自 Error
    • 实例自定义了 namemessage 属性,明确 babel-loader 的信息
    • 实例自定义的 hideStack 属性是非标准属性,用于 babel-loader 内部

# 5.3 web标准

是在解决 API Polyfil 的时候,Babel 配合使用的 core-js 除了提供 ECMAScript 标准下的 JavaScript API 实现,也提供了 DOM/URL 等实现。而 DOM/URL 所属的 web 标准,由 W3C/WHATWG 制定。

标准化组织 介绍
W3C W3C(World Wide Web Consortium) 致力于实现所有的用户都能够对 web 加以利用(不论其文化教育背景、能力、财力以及其身体残疾)。
W3C 包含了众多标准,包括 HTML/CSS/DOM/WebAssembly/WebDriver/IndexedDB 等等。
WHATWG WHATWG(Web Hypertext Application Technology Working Group) 是浏览器厂商(包括苹果、谷歌、微软、Mozilla)于 2004 年组成的行业组织。
专注于浏览器领域的标准制定、测试等工作,如 HTML/DOM/streams/fetch/url/encoding 等标准的起草和发布。

经过多年发展,WHATWG 和 W3C 目前是合作关系,其中,WHATWG 维护 HTML 和 DOM 标准,W3C 使用 WHATWG 存储库中的 HTML 和 DOM 标准描述,W3C 在 HTML 部分的工作集中在 XHTML/XML 上。

标准化/web标准

# 6 总结

本章从整体层面介绍了 Babel 的转译过程及其生态中各个模块的作用。

其中涉及的部分知识点,后续章节有详细解析,读者可以根据自身需要查看相应内容。

Last update: October 10, 2022 14:26
Contributors: hoperyy , hoperyy