2022-10-7 About 36 min

在本章,我们将全面了解 Babel 插件生态中,Babel 内置的各类 plugins 和 presets。由于插件数量众多,这里主要介绍各个插件的功能、核心实现思路、部分实现,本章中很多案例来自 Babel 官网。

如果对各个插件的实现细节感兴趣,可以继续阅读各个插件的源码,可以对 Babel 插件的开发过程有更清晰的了解。

# 7.1 配置

无论是在配置文件、还是作为 API 调用参数,Babel 的 plugins 和 presets 均以如下形式配置:

{
    "plugins": [
        "plugin-a", // 字符串形式
        [ 
            "plugin-b", 
            {
                desc: "hello plugin world"
            } 
        ], // 数组形式: [ plugin 名称, 插件配置参数 ]
    ],
    "presets": [
        "preset-a", // 字符串
        [ 
            "preset-b", 
            {
                desc: "hello preset world"
            } 
        ], // 数组形式: [ preset 名称, 插件配置参数 ]
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 7.2 内置 plugins

本节主要介绍 Babel 内置插件(plugins)。

Babel 以丰富的插件支持 JavaScript 代码的转换,在 Babel 的工程目录中,各个插件以 babel-plugin- 开头的文件夹命名。截止本书定稿时,内置的插件已有 95 个。

Babel 的内置插件分为如下几种:

  • 语法类插件

    语法插件以 babel-plugin-syntax- 开头,负责开启 babel-parser 对某个语法的支持。

    为什么会有语法插件呢?

    之前的章节提到,babel-parser 负责对源码进行词法解析、语法解析,它可以支持 ECMAScript 标准、提案、TypeScript、Flow 等语法。但除了基础语法,很多语法如 TypeScript、Flow、部分 ECMAScript 语法默认是不会开启支持的,这是为了减少不必要的转译消耗,同时方便使用者针对性地开启对某个语法的支持。

    简言之,语法类插件主要角色是对某个语法支持的开关,不涉及 AST 修改的工作。

    通常情况下,语法插件不需要单独引入,因为转换类和提案类的插件会引入语法插件以开启对特定语法的支持。

  • 转换类插件

    转换插件以 babel-plugin-transform- 开头,负责转换 JavaScript 代码,即我们熟悉的代码转换部分。

    如箭头函数 () => {} 转普通函数 function() {}

    转换类插件会引入语法插件以开启对当前语法的支持。

  • 对提案支持类插件

    其实也是转换类插件,只不过是针对提案(proposal)类特性的转换。

    对提案支持类插件以 babel-plugin-proposal- 开头,负责提供对 ECMAScript 提案的支持。

    转换类插件会引入语法插件以开启对目标语法的支持。

# 7.2.1 语法类插件

如上节所述,语法类插件主要是对某个语法支持的开关,不涉及代码转译工作。

截至本书编写时,Babel 中有 19 个语法类插件,以下是列举每个插件的作用:

  • babel-plugin-syntax-class-properties

    开启对类的属性(classProperties)、私有属性(classPrivateProperties)、私有方法(classPrivateMethods)的语法支持。

    举例:

    class Man {
        // 属性
        name: 'Jack',
    
        // 私有属性
        private age: 18,
    
        // 私有方法
        private say() {
            console.log('Hi!');
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • babel-plugin-syntax-class-static-block

    开启 classStaticBlock 语法的支持。

    class NewClass {
        static {
            // do something 
        }
    }
    
    1
    2
    3
    4
    5

    举例:

    // 没有使用 static block
    class C {
        static x = {};
        static y;
        static z;
    }
    
    try {
        const obj = doSomethingWith(C.x);
        C.y = obj.y;
        C.z = obj.z;
    } catch {
        C.y = ...;
        C.z = ...;
    }
    
    // 使用 static block
    class C {
        static x = {};
        static y;
        static z;
        static {
            try {
                const obj = doSomethingWith(this.x);
                this.y = obj.y;
                this.z = obj.z;
            }
            catch {
                this.y = ...;
                this.z = ...;
            }
        }
    }
    
    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

    利用 classStaticBlock,可以在类内部完成类的属性初始化等操作,无需在类的外部区域操作。

  • babel-plugin-syntax-decimal

    开启 Decimal 语法的支持,Decimal 意为十进制,该语法可以显著提升提升十进制数的可读性。

    • 为什么有这个提案

      Decimal 提案希望在 JavaScript 中添加一个内置的十进制类型。

      JavaScript 中经常需要存储和处理十进制数,但 JavaScript 中 Number 对数字精度的处理能力并不尽如人意,为了更好的精度,开发者还需使用其他技巧处理,比如第三方库、字符串等。

      这样在代码的可读性、结果精确度等方面均有很大提升空间。

    • 写法

      1000m

    • 该提案目标

      • 目标1:更好的数字可读性,比如针对货币的场景

        在处理货币数字的问题上会有以下问题:

        • 涉及金钱、且不同的人对特定数字表示的金钱的含义理解不同,导致开发者心理负担较重
        • 货币可以用不同数量的小数位描述,这需要一个精确的数据类型描述,如 $23.47、$23.4700

        举个例子:

        function calculateBill(items, tax) {
            let total = 0m;
        
            // 计算总数
            for (let { price, count } of items) {
                total += price * BigDecimal(count); // BigDecimal 内置类型转换普通数字
            }
        
            // 计算税
            return BigDecimal.round(
                total * (1m + tax),
                {
                    maximumFractionDigits: 2,
                    round: "up"
                }
            );
        }
        
        let items = [
            {
                price: 1.25m, // decimal 写法
                count: 5
            }, 
            {
                price: 5m, // decimal 写法
                count: 1
            }
        ];
        
        let tax = .0735m; // decimal 写法
        
        console.log(calculateBill(items, tax));
        
        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

        过去 JavaScript 很少直接参与数字的精确计算,更多地是将数字作为字符串做呈现,但现在的一些趋势,对于 JavaScript 数字方面的精确性、可读性的要求越来越高:

        • 更复杂的前端架构

          JavaScript 在本地即可完成各类复杂计算以便更好地交互。

        • Serverless

          JavaScript 已参与越来越多的 Serverless 服务,这对于本地化的计算需求越来越多。

        • 基于 JavaScript 的服务端编程

          JavaScript 也可以作为服务端开发语言,该领域对于数字精确计算的要求非常高。

      • 目标2:更高的浮点数精度

        在目标1的介绍中已经提到了对 JavaScript 数字计算精度的要求。从实际需求看,现在越来越多的场景需要客户端本地环境下的高精度 JavaScript 数字计算,比如航天器、物联网设备、游戏、甚至支付系统。

        而以前内置的 Number 类型在高精度数字计算方面并不理想,如果 JavaScript 中内置了新的高精度数字计算类型,开发者也就不再需要引入第三方工具或其他手段解决精度问题,代码量更少。

    • 用哪种新的数据类型?

      提案中提到了两种新的数据类型:BigDecimalDecimal128。二者主要是对数字精度方面的区别,各有利弊,用哪一种需要做权衡。

      • BigDecimal: 无限精度数字

      • Decimal128: 基于 IEEE 754-2008 标准的 128 位十进制数

        IEEE 754(2008)定义了三种基本的十进制浮点格式(32, 64 和 128位)。

  • babel-plugin-syntax-decorators

    开启对装饰器语法的支持。

    @decorator
    export class Foo {}
    
    1
    2

    对外的配置项:

    • legacy

      使用 state 1 阶段描述的装饰器写法。

      TODO

    • decoratorsBeforeExport

      是否要求装饰器位于 export 前,默认是 false

      // decoratorsBeforeExport: true
      @decorator
      export class Foo {}
      
      // decoratorsBeforeExport: false
      export @decorator class Bar {}
      
      1
      2
      3
      4
      5
      6
  • babel-plugin-syntax-do-expressions

    开启对 do 表达式语法的支持,但并不会启动对 do 语法的转换,可以使用 babel-plugin-proposal-do-expressions 同时启动语法和转换。

    do 表示可以认为是三元表达式的变种,在较复杂的情况下加,用 do 表达式更简洁。

    let x = 100;
    let y = 20;
    
    let a = do {
        if(x > 10) {
            if(y > 20) {
                'big x, big y';
            } else {
                'big x, small y';
            }
        } else {
            if(y > 10) {
                'small x, big y';
            } else {
                'small x, small y';
            }
        }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  • babel-plugin-syntax-export-default-from

    开启对 export-default-from 表达式语法的支持,不提供转译支持。可以使用 babel-plugin-proposal-export-default-from 同时启动语法和转译。

    export default { ... }
    
    1
  • babel-plugin-syntax-flow

    开启对 flow 语法的支持。

  • babel-plugin-syntax-function-bind

    开启对 functionBind 语法的支持。

    functionBind,顾名思义,就是为指定方法指定运行时的 this 值。

    obj::func
    // 相当于
    func.bind(obj)
    
    ::obj.func
    // 相当于
    obj.func.bind(obj)
    
    obj::func(val)
    // 相当于
    func.call(obj, val)
    
    ::obj.func(val)
    // 相当于
    obj.func.call(obj, val)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  • babel-plugin-syntax-function-sent

    开启对 functionSent 语法的支持,即开启对 function.sent 的语法支持。

    function* generator() {
        console.log("Sent", function.sent);
        console.log("Yield", yield);
    }
    
    const iterator = generator();
    iterator.next(1); // Logs "Sent 1"
    iterator.next(2); // Logs "Yield 2"
    
    1
    2
    3
    4
    5
    6
    7
    8

    相当于

    let generator = _skipFirstGeneratorNext(function* () {
        const _functionSent = yield;
        console.log("Sent", _functionSent);
        console.log("Yield", yield);
    });
    
    const iterator = generator();
    iterator.next(1); // Logs "Sent 1"
    iterator.next(2); // Logs "Yield 2"
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • babel-plugin-syntax-import-assertions

    开启对 Import assertions 语法的支持。

    该语法主要是为了更清晰地描述模块的一些特性信息,比如类型。

    举个例子,引入 ./config.json 文件时,声明其内容类型为 json,有几种写法:

    • 静态import

      import config from './config.json' assert { type: 'json' }
      
      1
    • 动态import

      import('./config.json', { assert: { type: 'json' } })
      
      1
    • export

      export { default as config } from './config.json' assert { type: 'json' }
      
      1
  • babel-plugin-syntax-jsx

    开启对 jsx 语法的支持。

  • babel-plugin-syntax-module-blocks

    开启对 moduleBlocks 语法的支持。

    moduleBlocks 语法简介如下(案例来自 tc39):

    let moduleBlock = module {
        export let y = 1;
    };
    let moduleExports = await import(moduleBlock);
    assert(moduleExports.y === 1);
    
    assert(await import(moduleBlock) === moduleExports);  // cached in the module map
    
    1
    2
    3
    4
    5
    6
    7

    这样,提供了非文件的方式引入模块。

    moduleBlocks 只能被 import() 引入,而非 import 表达式。

  • babel-plugin-syntax-module-string-names

    开启对 moduleStringNames 语法的支持,也就是模块别名的支持。

    export { hi as 'hello' } from './hi.js';
    
    1
  • babel-plugin-syntax-partial-application

    开启 partialApplication 语法的支持。

    根据提案的描述:

    const addOne = add(1, ?); // apply from the left
    addOne(2); // 3
    
    const addTen = add(?, 10); // apply from the right
    addTen(2); // 12
    
    1
    2
    3
    4
    5

    这也是函数科里化的一个简单式的写法。

  • babel-plugin-syntax-pipeline-operator

    开启对 pipelineOperator 语法的支持,也就是 |> 写法。

    这个写法挺实用的,举例:

    // 各类函数定义
    function doubleSay (str) {
        return str + ", " + str;
    }
    function capitalize (str) {
        return str[0].toUpperCase() + str.substring(1);
    }
    function exclaim (str) {
        return str + '!';
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    运行案例:

    // 写法一
    let result = exclaim(capitalize(doubleSay("hello")));
    result //=> "Hello, hello!"
    
    // 写法二
    let result = "hello"
        |> doubleSay
        |> capitalize
        |> exclaim;
    
    result //=> "Hello, hello!"
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • babel-plugin-syntax-record-and-tuple

    https://github.com/tc39/proposal-record-tuple

    开启对 recordAndTuple 语法的支持,对应的是 proposal-record-tuple 提案。

    proposal-record-tuple 提案为 JavaScript 新增了两种数据结构:Record(类似对象)和 Tuple(类似数组)。

    二者共同点均是不可变的(Immutable),且其成员不能包含引用类型。因此,可以用此特性对其进行值比较。如果 Record 之间和 Tuple 之间的成员完全一致,则被认为相同(=== 返回 true)。

    该特性使用 # 标记,且支持 RecordTuple 的互相嵌套式写法。

    例如:

    // Record
    const recordExample = #{
        a: 1,
        b: '2',
        c: #[ 'My', 'name', 'is', '...' ]
    };
    
    // Tuple
    const tupleExample = #[ 1, 2, '3' ];
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • babel-plugin-syntax-throw-expressions

    开启对 throwExpressions 的语法支持。

    举例:

    function save(filename = throw new TypeError("Argument required")) {}
    
    1
  • babel-plugin-syntax-top-level-await

    开启在一级作用域直接使用 await 语法的支持。

    const val = await promise;
    
    export { val };
    
    1
    2
    3
  • babel-plugin-syntax-typescript

    开启对 TypeScript 语法的支持。

# 7.2.2 标准特性转换类插件

截至本书编写时,Babel 有 52 个内置标准特性转换插件。

转换插件,顾名思义,就是对源码进行转换操作,确切地说,是对源码的 AST 进行转换操作,包括新增、删除、复制、校验节点等。本节介绍的转换插件针对已经标准化的特性,对于还处于提案状态的特性,下节的提案特性转换插件中详细介绍。

  • babel-plugin-transform-arrow-functions

    • 作用

      将箭头函数转为普通函数。

    • 目标节点

      ArrowFunctionExpression: 箭头函数表达式节点。

    • 插件配置

      该插件支持配置参数 spec(默认 false)。

      • spec: true

        • 转换后的普通函数绑定当前环境的 this
        • 阻止被 new 实例化
        • 为生成的普通函数添加 name
      • spec: false

        转译为普通函数,不做额外处理。

    • 转换原理

      待转换的源码:

      const fn = () => {
          console.log(1);
      }
      
      1
      2
      3

      spec分别为 truefalse 的转译结果:

      • spec: true

        var _this = this;
        
        // babel-helpers定义的一个帮助函数
        function _newArrowCheck(innerThis, boundThis) {
            // new 实例化生成的 this 优先级高于 bind
            if (innerThis !== boundThis) { 
                throw new TypeError("Cannot instantiate an arrow function"); 
            }
        }
        
        // 普通函数添加了名称 "fn"
        const fn = function fn() {
            // 检查是否被new实例化,如果是,则抛出错误
            _newArrowCheck(this, _this);
        
            console.log(1);
        }.bind(this); // 生成的普通函数绑定当前 this
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        生成的函数有以下特点:

        • 转换后的普通函数绑定当前环境的 this
        • 阻止被 new 实例化
        • 为生成的普通函数添加 name

        this 值的优先级:new 实例化生成的 this 优先级高于 bind 绑定的 this

      • spec: false

        const fn = function () {
            console.log(1);
        };
        
        1
        2
        3

        转译为普通函数,不做额外处理。

    • 插件源码细节

      • path.isArrowFunctionExpression: 判断是否是箭头函数表达式

        该方法定义自 babel/packages/babel-traverse/src/path/index.ts

      • path.arrowFunctionToExpression: 将当前箭头函数转为表达式节点

        该方法定义自 babel/packages/babel-traverse/src/path/conversion.ts

        对该方法的介绍可以参考章节 "Babel插件编写"。

  • babel-plugin-transform-async-to-generator

    • 作用

      async 标记的函数转为 generator 函数。

      如:

      const a = async () => {
          await 42
      }
      
      1
      2
      3

      可以被转译为:

      function _asyncToGenerator(fn) { ... }
      
      const a = function () {
          var _ref = _asyncToGenerator(function* () {
              yield 42;
          });
      
          return function a() {
              return _ref.apply(this, arguments);
          };
      }();
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
    • 目标节点

      Function: 函数节点。

    • 插件配置

      该插件支持配置参数:

      • module
      • method
    • 协程

      https://zhangchen915.com/index.php/archives/719/(《JS 中的协程》)

      在实现该转译时,用到了协程的概念。协程实现了控制流主动让出和恢复机制,让控制流更加流畅。

      • 什么是协程?

        协程是一个特殊的函数:是可以暂停执行、可从暂停点恢复的函数。

        JavaScript 中的协程,是用 * 标记的函数(function*(...) { ... }),函数内部用 yield 关键字暂停、然后可通过 done() 方法从暂停点恢复。

      • 协程/线程/进程

        • 进程

          进程是应用程序的启动实例,拥有代码、打开的文件资源、数据资源、独立的内存空间。

        • 线程

          线程从属于进程,是程序的实际执行者。一个进程包含一个或多个子线程。线程拥有独立的栈空间。

        • 协程

          协程是线程的子集,一个线程可拥有多个协程。

          协程不被操作系统控制,而是被应用程序自身控制。

          多个线程可以并行执行,但在一个线程内,协程函数是串行执行的。

        • 三者的区别于联系

          协程 线程 进程
          控制主体 程序自身控制 操作系统 操作系统
          共享堆 Y Y N
          共享栈 N N N
          独立上下文 Y Y Y
          切换上下文涉及点 只切换用户态 切换用户态和内核态 切换用户态和内核态
    • 转换原理

      该插件使用两种方式实现协程函数的自动执行。

      • promise/generaotr 实现 async/await

        下面是无配置情况下转换后的代码:

        function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { 
            try { 
                var info = gen[key](arg); 
                var value = info.value; 
            } catch (error) { 
                reject(error); 
                return; 
            } 
            
            if (info.done) { 
                resolve(value); 
            } else { 
                Promise.resolve(value).then(_next, _throw); 
            } 
        }
        
        function _asyncToGenerator(fn) { 
            return function () { 
                var self = this, args = arguments; 
                
                return new Promise(function (resolve, reject) { 
                    var gen = fn.apply(self, args); 
                    
                    function _next(value) { 
                        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); 
                    } 
                    
                    function _throw(err) { 
                        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); 
                    } 
                    
                    _next(undefined); 
                }); 
            }; 
        }
        
        const a = function () {
            var _ref = _asyncToGenerator(function* () {
                yield 42;
            });
        
            return function a() {
                return _ref.apply(this, arguments);
            };
        }();
        
        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

        从上面的代码可以看到几个特点:

        • async 标记的函数被转译为了普通函数,同时该普通函数返回 Promise 实例
        • await 所在的表达式被转译为了 yield 标记的表达式
        • 转换后的普通函数利用:Promiseyield 表达式的 done/value、递归调用等实现了一个自动执行方法

        其实,就是常说的:async/awaitpromise/generaotr的语法糖。

      • 用第三方工具实现 async/await

        添加配置项 modulemethod,使用第三方实现协程:

        {
            method: 'bluebird',
            module: 'coroutine'
        }
        
        1
        2
        3
        4

        源码会被转译为:

        import { bluebird as _bluebird } from "coroutine";
        
        const a = function () {
            var _ref = _bluebird(function* () {
                yield Promise.resolve(42);;
            });
        
            return function a() {
                return _ref.apply(this, arguments);
            };
        }();
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
    • 插件源码细节

      • path.node.async/path.node.generator

        关于如何识别 async* 标记的函数:

        path.node.async: true/false 可判断是否是 async 标记的函数。

        path.node.generator: true/false 可判断是否是 * 标记的函数。

        "Babel节点集" 有对 path.node 的详细介绍

      • state.methodWrapper

        这个方法决定了用哪种方式的"协程"来实现 async/awaitstate 对象定义在 babel-traverse 中,但并没有定义 state.methodWrapper 方法,笔者判断应该是一个临时的自定义方法,一旦确定,后续执行的插件逻辑会统一使用该方式定义的"协程"。

        • 自定义方法实现"协程": state.addHelper("asyncToGenerator")

          state.addHelperstate.file.addHelper 的快捷方式,关于 state 的使用方式,章节"Babel插件编写"有详细介绍,不再赘述。该方法会从 babel-helpers 模块中提取名称为 asyncToGenerator 的辅助代码片段,也就是上文中看到的用 Promise/Generaotr 实现的"协程"。

        • 第三方模块实现"协程":

          TODO

  • babel-plugin-transform-block-scoped-functions

    • 作用

      将块级作用域下的函数声明,转为函数表达式。

    • 目标节点

      • BlockStatement: 块声明节点
      • SwitchCase: switch 语句的 case 节点
    • 转换原理

      待转换的源码:

      {
          name(1)
          function name (n) {
              return n;
          }
      }
      
      1
      2
      3
      4
      5
      6

      会被转译为:

      {
          let name = function (n) {
              return n;
          };
      
          name(1);
      }
      
      1
      2
      3
      4
      5
      6
      7

      上面的转换过程有以下特点:

      • 转换过程发生在块级作用域下

        函数体(function run() { /* 内容 */ })和 export 表达式(export { /* 内容 */ })中的 { /* 内容 */ } 不会发生转换。

      • 函数声明被转为函数表达式,以 let 声明

      • 生成的函数表达式被提升到块级作用域靠前的位置

    • 插件源码细节

      • const { node, parent } = path;

        path 节点有 nodeparent 属性,二者均是 AST 节点对象,具体定义,在"Babel节点集"有详细介绍。

      • t.isFunctiont.isExportDeclaration

        babel-types 提供的校验方法。

      • path.get(key)

        path.get(key) 类似于 path.node[key] 的写法,用于获取子节点。

      • path.isFunctionDeclaration

        t.isFunctionDeclaration

      • t.variableDeclaration / t.variableDeclarator

        创建节点,其参数如何填写在"Babel插件编写"章节有详细介绍。

  • babel-plugin-transform-block-scoping

    https://babeljs.io/docs/en/babel-plugin-transform-block-scoping

    • 作用

      提供块级作用域相关的转换操作。

    • 插件配置

      • throwIfClosureRequired: boolean,默认值是 false,表示在转译过程中,如果发现闭包的存在,转译阶段是否抛错。

        比如,源码是:

        for (let i = 0; i < 5; i++) {
            setTimeout(() => console.log(i), 1);
        }
        
        1
        2
        3
        • throwIfClosureRequired: false

          转译结果:

          "use strict";
          
          var _loop = function _loop(i) {
              setTimeout(function () {
                  return console.log(i);
              }, 1);
          };
          
          for (var i = 0; i < 5; i++) {
              _loop(i);
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11

          可以看到,源码中 for 循环中的 let 声明的变量 i 位于块级作用域,转译后,变量 i 的值通过值传递的方式,存储在循环中的闭包函数中。

        • throwIfClosureRequired: true

          转译过程会检查是否含有闭包存在,如有,则转译报错。这对于性能要求极高的场景而言,可以减少闭包的数量,提高运行效率。

          上面待转译的源码,如果被转译是需要加上闭包的,所以在 throwIfClosureRequired: true 的情况下,转译过程会抛出一个语法错误:

          SyntaxError: unknown: Compiling let/const in this block would add a closure (throwIfClosureRequired).
          
          1
      • tdz: boolean,默认是 false,表示在转译过程中,如果发现暂时性死区,是否在转换后的代码的运行时抛出关于暂时性死区的错误。

        比如,源码是:

        i;
        let i;
        
        1
        2

        该源码含有一个暂时性死区的错误:在let i之前就使用了变量i

        • tdz: false

          转译后的结果是:

          "use strict";
          
          i;
          var i;
          
          1
          2
          3
          4
        • tdz: true

          转译后的结果是:

          "use strict";
          
          function _tdz(name) { throw new ReferenceError(name + " is not defined - temporal dead zone"); }
          
          _tdz("i");
          
          var i;
          
          1
          2
          3
          4
          5
          6
          7

          在转译时,插件如果发现存在暂时性死区相关的错误,会将 _tdz 方法加入转译后的代码,在运行时抛出错误。

    • 目标节点

      • VariableDeclaration: 变量声明节点
      • Loop: 循环节点
      • CatchClause: catch 节点
      • BlockStatement|SwitchStatement|Program
    • 转换原理

      • VariableDeclaration: 变量声明节点

        • 如果变量声明没有位于块级作用域,不会处理

        • let/const 的声明方式,转译为 var

          比如:{ let i; } 会被转译为{ var i; }

          而对于 for...offor...in 循环,该插件有个特殊的处理:

          {
              for (let i of [ 1, 2, 3 ]) {
                  let key
              }
          }
          
          1
          2
          3
          4
          5

          会被转译为:

          {
              for (var i of [1, 2, 3]) {
                  var key = void 0; // 初始化 key 的值为 undefined
              }
          }
          
          1
          2
          3
          4
          5

          可以看到,变量 key 的值在每次循环的时候,均会被初始化为 void 0,也就是 undefined。是因为有这样的场景:

          源码:

          for (let i = 0; i < 2; i++) {
              let k;
          
              if (k) {
                  alert('hi');
              }
          
              k = true;
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9

          转译后:

          for (var i = 0; i < 2; i++) {
              var k = void 0; // 如果不做值的初始化,会出现意料之外的问题
          
              if (k) {
                  alert('hi');
              }
          
              k = true;
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
        • 将位于当前块级作用域的绑定信息,移到上层最近的函数作用域或顶层作用域中

          源码:

          { // 顶层作用域
              (function() { // 函数作用域
                  for (let i of [ 1, 2, 3 ]) {
                      let key
                  }
              })()
          }
          
          1
          2
          3
          4
          5
          6
          7

          转译时,发现当前节点的 scope 中含有两个绑定信息:ikey。这两个绑定信息将被挪动到当前节点上层最近的一个函数作用域或顶层作用域。

          转译结果是:

          {
              (function () { // 函数作用域
                  for (var i of [1, 2, 3]) {
                      var key = void 0;
                  }
              })();
          }
          
          1
          2
          3
          4
          5
          6
          7

          可以看到,ikey 两个变量影响的作用域在最近的一层函数作用域。

    • 插件源码细节

      • VariableDeclaration处理逻辑

        • path.getBindingIdentifiers()

          获取和 path.node 节点绑定的标识符列表。"Babel插件编写"章节中 t.getBindingIdentifiers 有对该方法的详细介绍。

        • scope.getOwnBinding(name)

          获取当前节点中,uidnamescope.binding 对象。

        • scope.moveBindingTo(name, parentScope)

          移动作用域 TODO

  • babel-plugin-transform-classes

    • 作用

      class 转为普通函数。

    • 目标节点

      • ExportDefaultDeclaration: 默认导出声明节点,且针对 class 声明,如 export default class E {}
      • ClassDeclaration: 类声明节点,如 class [name] [extends] {[body]}
      • ClassExpression: 类表达式,如 const MyClass = class [className] [extends otherClassName] { ... }
    • 转换原理

      • ClassDeclaration

        针对类的声明转换。

        class Test {
            constructor(name) {
                this.name = name;
            }
        
            logger() {
                console.log("Hello", this.name);
            }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9

        会被转译为:

        function _classCallCheck(instance, Constructor) {
            if (!(instance instanceof Constructor)) {
                throw new TypeError("Cannot call a class as a function");
            }
        }
        
        var Test = (function() {
            function Test(name) {
                _classCallCheck(this, Test);
        
                this.name = name;
            }
        
            Test.prototype.logger = function logger() {
                console.log("Hello", this.name);
            };
        
            return Test;
        })();
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        可以看到,class 被转为了普通函数,且赋值给变量 Test

        同时要求 class 不能被直接作为普通函数调用。通过在普通函数运行时,判断当前 this 的值是否是 Test 的实例,判断类是否被作为普通函数调用。

      • ExportDefaultDeclaration

        针对 export default {类} 形式使用的 class 节点。

        这部分处理逻辑需要和 ClassDeclaration 配合使用,ClassDeclaration 处理函数将 class 类转为普通函数的形式,ExportDefaultDeclaration 处理函数负责处理 export default

        export default class E {
        
        }
        
        1
        2
        3

        转译结果是:

        // 校验类 E 是否被直接调用,如 E()
        function _classCallCheck(instance, Constructor) { 
            if (!(instance instanceof Constructor)) { 
                throw new TypeError("Cannot call a class as a function"); 
            } 
        }
        
        let E = function E() {
            _classCallCheck(this, E);
        };
        
        export { E as default };
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
      • ClassExpression

        类表达式处理逻辑,如 const MyClass = class [className] [extends otherClassName] { ... }

        有两种形式的源码:

        其一,只有类定义表达式:

        const MyClass = class {}
        
        1

        转译结果是:

        function _classCallCheck(instance, Constructor) { 
            if (!(instance instanceof Constructor)) { 
                throw new TypeError("Cannot call a class as a function"); 
            } 
        }
        
        const MyClass = function MyClass() {
            _classCallCheck(this, MyClass);
        };
        
        1
        2
        3
        4
        5
        6
        7
        8
        9

        其二,含类定义表达式,并立即执行:

        (class {
        
        })()
        
        1
        2
        3

        转译结果是:

        function _classCallCheck(instance, Constructor) { 
            if (!(instance instanceof Constructor)) { 
                throw new TypeError("Cannot call a class as a function"); 
            } 
        }
        
        /*#__PURE__*/
        (function () {
            function _class() {
                _classCallCheck(this, _class);
            }
        
            return _class;
        })()();
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        其中,class 类先转为箭头函数,然后转为普通函数。

  • babel-plugin-transform-computed-properties

    • 作用

      该插件会转译计算属性。

      举例:

      var obj = {
          ["x" + foo]: "heh",
          ["y" + bar]: "noo",
          foo: "foo",
          bar: "bar"
      };
      
      1
      2
      3
      4
      5
      6

      会被转译为:

      var _obj;
      
      function _defineProperty(obj, key, value) {
          if (key in obj) {
              Object.defineProperty(obj, key, {
                  value: value,
                  enumerable: true,
                  configurable: true,
                  writable: true
              });
          } else {
              obj[key] = value;
          }
      
          return obj;
      }
      
      var obj = (
          _obj = {},
          _defineProperty(_obj, "x" + foo, "heh"),
          _defineProperty(_obj, "y" + bar, "noo"),
          _defineProperty(_obj, "foo", "foo"),
          _defineProperty(_obj, "bar", "bar"),
          _obj
      );
      
      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
    • 目标节点

      ObjectExpression: Object表达式节点。

    • 原理

      • _defineProperty

        该方法负责设置对象属性,内部优先使用 Object.defineProperty 方法,其次直接通过 obj[key] = value 设置。

      • 用逗号运算符在进行一系列计算后返回目标值

        用逗号运算符完成变量的定义、初始化等一系列操作后,再返回变量值,是一个可以借鉴的技巧。

  • babel-plugin-transform-destructuring

    • 作用

      该插件实现对解构的转译。

    • 目标节点

      • ExportNamedDeclaration: 导出具名声明

        如: export const [a, b, ...rest] = [10, 20, 30, 40, 50]

      • ForXStatement

        ForXStatement 是一个别名,包括 ForInStatementForOfStatement

        如:for ({ length: k } in { abc: 3 }) {}

      • CatchClause: catch从句。

        如:try { } catch(...) { ... } 中的 catch(...) {...} 部分。

      • AssignmentExpression: 赋值表达式

        如:[ nums[1], nums[0] ] = [ nums[0], nums[1] ]

      • VariableDeclaration: 变量声明

        如:const { k } = { k: 1 }

    • 原理

      const { x, y } = obj;
      const [ a, b, ...rest ] = arr;
      
      1
      2

      插件要做的事情,就是将上述类型的解构写法,转译为普通的赋值写法。

      const _obj = obj,
          x = _obj.x,
          y = _obj.y;
      
      let _arr = arr,
          _arr2 = _toArray(_arr),
          a = _arr2[0],
          b = _arr2[1],
          rest = _arr2.slice(2);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      对于各个目标节点,转换过程分别如下:

      • ExportNamedDeclaration

        如:export const [a, b, ...rest] = [10, 20, 30, 40, 50]

        会被转译为:

        const a = 10,
            b = 20,
            rest = [30, 40, 50];
        export { a, b, rest };
        
        1
        2
        3
        4

        TODO

      • ForXStatement

        如:for ({ length: k } in { abc: 3 }) {}

        会被转译为:

        for (var _ref in { abc: 3 }) {
            k = _ref.length;
            void 0;
        }
        
        1
        2
        3
        4
      • CatchClause

        如:try { } catch({ message }) {}

        会被转译为:

        try {} catch (_ref) {
            var message = _ref.message;
        }
        
        1
        2
        3
      • AssignmentExpression

        如:[ nums[1], nums[0] ] = [ nums[0], nums[1] ]

        数组交换的写法,会被转译为:

        var _ref = [nums[0], nums[1]];
            nums[1] = _ref[0];
            nums[0] = _ref[1];
            _ref;
        
        1
        2
        3
        4
      • VariableDeclaration

        如: const { ...a } = b;

        会被转译为:

        function _extends() { 
            _extends = Object.assign || function (target) { 
                for (var i = 1; i < arguments.length; i++) { 
                    var source = arguments[i]; 
        
                    for (var key in source) { 
                        if (Object.prototype.hasOwnProperty.call(source, key)) { 
                            target[key] = source[key]; 
                        } 
                    } 
                } 
                
                return target; 
            }; 
            
            return _extends.apply(this, arguments); 
        }
        
        const _b = b,
            a = _extends({}, _b); // 复制 _b
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

        可以看到,在转换的实现上,有以下细节:

        • 优先使用 Object.assign,其次自行实现 extends
        • 自行实现中,for...in 遍历源对象,会引入原型链上的属性,利用 Object.prototype.hasOwnProperty 过滤掉原型链上的属性
  • babel-plugin-transform-dotall-regex

    • 作用

      转换正则表达式的dotAll(s)标志。

    • 目标节点

      RegExpLiteral: 曾泽表达式字面量。

    • 原理

      babel/packages/babel-helper-create-regexp-features-plugin/src/index.js

      截至目前,Babel 支持的正则标志有如下几个:"i" | "g" | "m" | "s" | "u" | "y"

      内部实现上,该插件依赖 regexpu-core 对正则写法进行转换。

      https://www.npmjs.com/package/regexpu-core

      TODO

  • babel-plugin-transform-duplicate-keys

    • 作用

      转译对象中重复的 key。

      var x = { a: 5, a: 6 };
      
      1

      配合插件 babel-plugin-transform-computed-properties,上述代码将被转译为:

      function _defineProperty(obj, key, value) { 
          if (key in obj) { 
              Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); 
          } else { 
              obj[key] = value; 
          } 
          return obj; 
      }
      
      var x = _defineProperty({
          a: 5
      }, "a", 6);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
    • 目标节点

      ObjectExpression: Object表达式。

    • 原理

      从转换后的代码可以看到,var x = { a: 5, a: 6 }; 在转译后,相当于为对象 { a: 5 } 重新设置 a 的值为 6。

  • babel-plugin-transform-exponentiation-operator

    • 作用

      转换取幂运算符 **

      如:

      let x = 10 ** 2;
      x **= 3;
      
      1
      2

      被转译为:

      let x = Math.pow(10, 2);
      x = Math.pow(x, 3);
      
      1
      2
    • 目标节点

      • AssignmentExpression: 赋值表达式
      • BinaryExpression: 二元运算表达式
    • 原理

      这个转化过程比较直白,就是用 Math.pow 替代 **

  • babel-plugin-transform-flow-comments

    TODO

  • babel-plugin-transform-flow-strip-types

    TODO

  • babel-plugin-transform-for-of

    • 作用

      转换 for...of

      for (let key of obj) {}
      
      1

      会被转译为:

      // _createForOfIteratorHelper 定义
      function _createForOfIteratorHelper(...) { ... }
      
      var _iterator = _createForOfIteratorHelper(obj),
          _step;
      
      try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
              let key = _step.value;
          }
      } catch (err) {
          _iterator.e(err);
      } finally {
          _iterator.f();
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
    • 目标节点

      ForOfStatement: for...of 语句。

    • 原理

      for...of 会被转译为 for 循环,同时因为 for...of 支持 generator 对象,这里做了特殊处理。TODO

  • babel-plugin-transform-function-name

    • 作用

      function 添加名称。

      比如:

      const obj = {
          fn: function() {
      
          }
      }
      
      let number = function(x) {
          return x;
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      会被转译为:

      const obj = {
          fn: function fn() {}
      }
      
      let number = function number(x) {
          return x;
      };
      
      1
      2
      3
      4
      5
      6
      7
    • 目标节点

      • Function: 函数节点
      • ObjectProperty: 对象属性节点
    • 原理

      • ObjectProperty 处理逻辑

        为作为对象属性的匿名函数添加名称。

      • Function 处理逻辑

        先排除该函数是对象的属性,然后为匿名函数添加名称。

  • babel-plugin-transform-instanceof

    • 作用

      foo instanceof Bar;
      
      1

      会被转译为:

      function _instanceof(left, right) {
          if (
              right != null &&
              typeof Symbol !== "undefined" &&
              right[Symbol.hasInstance]
          ) {
              return right[Symbol.hasInstance](left);
          } else {
              return left instanceof right;
          }
      }
      
      _instanceof(foo, Bar);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
    • 目标节点

      BinaryExpression: 二元运算表达式节点。

    • 原理

      当对象 foo 使用 instanceof 判断是否为另一个对象 Foo 的实例时,会调用对象 b 的内置方法 Symbol.hasInstance,也就是说,foo instanceof Foo 在运行的时候,实际调用的是 Foo[Symbol.instanceof](foo)

      那么,Babel在转换代码时,会优先使用 Bar[Symbol.hasInstance](foo),如环境不支持,降级为直接调用 instanceof

  • babel-plugin-transform-jscript

    • 作用

      var foo = function bar() {};
      
      1

      会被转译为:

      var foo = (function() {
          function bar() {}
      
          return bar;
      })();
      
      1
      2
      3
      4
      5
    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-transform-literals

    • 作用

      转换字面量。

      举例:

      var b = 0b11; // binary integer literal
      var o = 0o7; // octal integer literal
      const u = "Hello\u{000A}\u{0009}!"; // unicode string literals, newline and tab
      
      1
      2
      3

      会被转译为:

      var b = 3; // binary integer literal
      var o = 7; // octal integer literal
      const u = "Hello\n\t!"; // unicode string literals, newline and tab
      
      1
      2
      3
    • 目标节点

      • NumericLiteral: 数字字面量
      • StringLiteral: 字符串字面量
    • 原理

      该插件的写法和babel-generator有配合的关系,插件中识别:

      • /^0[ob]/i.test(node.extra.raw)trueNumericLiteral 节点
      • /\\[u]/gi.test(node.extra.raw)trueStringLiteral 节点

      然后设置该节点 node.extra = undefined

      在重新生成代码的 babel-generator中,babel/packages/babel-generator/src/generators/types.ts 文件针对不同类型的节点有不同的处理,其中 NumericLiteralStringLiteral 节点在重新生成代码字符串时,会判断 node.raw 是否非空,决定是否使用 node.value 的值。

  • babel-plugin-transform-member-expression-literals

    • 作用

      成员表达式中,如果名称为关键词的话,会被转为字符串形式的成员。

      obj.foo = "isValid";
      
      obj.const = "isKeyword";
      obj["var"] = "isKeyword";
      
      1
      2
      3
      4

      会被转译为:

      obj.foo = "isValid";
      
      obj["const"] = "isKeyword";
      obj["var"] = "isKeyword";
      
      1
      2
      3
      4
    • 目标节点

      MemberExpression: 成员表达式节点

    • 原理

      对于 MemberExpression 节点,如果成员表达式满足如下条件:

      • 非计算属性
      • 是标识符
      • 非ES3的标识符

      会将属性修改为字符串形式的计算属性。

  • babel-plugin-transform-modules-amd

    • 作用

      import/export 表达式,转译为 amd 格式。

      举例:

      export default 42;
      
      1

      会被转译为:

      define(["exports"], function (_exports) {
          "use strict";
      
          Object.defineProperty(_exports, "__esModule", {
              value: true
          });
          _exports.default = void 0;
          var _default = 42;
          _exports.default = _default;
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
    • 目标节点

      • CallExpression: 函数调用表达式
      • Program: 根节点
    • 原理

      该转换插件通过对 Program 节点和 CallExpression 节点的转换,配合实现转换目标。

      比如,待转换的源码为:

      run(1 + 1);
      
      import('./module');
      
      1
      2
      3

      转译结果是:

      define(["require"], function (_require) {
      "use strict";
      
          function _getRequireWildcardCache() {...}
      
          function _interopRequireWildcard(obj) {...}
      
          run(1 + 1);
          new Promise((_resolve, _reject) => _require(['./module'], imported => _resolve(_interopRequireWildcard(imported)), _reject));
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      待转换的源码有两种函数:普通函数调用 run() 和动态 import 调用。

      转换后的结果有以下特点:

      • 新增了 define 的调用模块,这是amd模块的典型特征
      • run(1 + 1) 并没有改变
      • import('./module') 被转译为 Promise 调用,同时新增了一些辅助函数

      基于此,插件的转换逻辑为:

      • 针对 CallExpression 节点,只解析动态 import() 函数调用;额外地,需要引入 babel-plugin-proposal-dynamic-import 以支持动态 import 代码的转换
      • 针对 Program 节点,在节点访问的 exit 阶段,对 Program 进行改造,提供 define() 调用
  • babel-plugin-transform-modules-commonjs

    • 作用

      import/export 表达式,转译为 commonjs 格式。

      举例:

      export default 42;
      
      1

      会被转译为:

      "use strict";
      
      Object.defineProperty(exports, "__esModule", {
          value: true
      });
      exports.default = void 0;
      var _default = 42;
      exports.default = _default;
      
      1
      2
      3
      4
      5
      6
      7
      8
    • 目标节点

      • CallExpression: 函数调用
      • Program 节点的 exit 时刻
    • 原理

      TODO

  • babel-plugin-transform-modules-systemjs

    • 作用

      import/export 表达式,转译为 systemjs 格式。

      举例:

      export default 42;
      
      1

      会被转译为:

      System.register([], function (_export, _context) {
          "use strict";
      
          return {
              setters: [],
              execute: function () {
                  _export("default", 42);
              }
          };
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      对于动态 import,如 import('./lazy.js').then(m => ...),需要先开启 babel-plugin-proposal-dynamic-import

    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-transform-modules-umd

    • 作用

      import/export 表达式,转译为 umd 格式。

      举例:

      export default 42;
      
      1

      会被转译为:

      (function (global, factory) {
          if (typeof define === "function" && define.amd) {
              define(["exports"], factory);
          } else if (typeof exports !== "undefined") {
              factory(exports);
          } else {
              var mod = {
              exports: {}
              };
              factory(mod.exports);
              global.unknown = mod.exports;
          }
          })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) {
          "use strict";
      
          Object.defineProperty(_exports, "__esModule", {
              value: true
          });
          _exports.default = void 0;
          var _default = 42;
          _exports.default = _default;
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-transform-named-capturing-groups-regex

    TODO

  • babel-plugin-transform-new-target

    • 作用

      转换 new.target 表达式。

      function Foo() {
          console.log(new.target);
      }
      
      Foo(); // => undefined
      new Foo(); // => Foo
      
      1
      2
      3
      4
      5
      6

      会被转译为:

      function Foo() {
          console.log(this instanceof Foo ? this.constructor : void 0);
      }
      
      Foo(); // => undefined
      
      new Foo(); // => Foo
      
      1
      2
      3
      4
      5
      6
      7
    • 目标节点

      MetaProperty: 元属性节点,即 new.target

    • 原理

      代码被转译为,当 Foo 运行时的 thisFoo 的实例的话,new.target 会返回构造器 Foo,否则返回 undefined

  • babel-plugin-transform-object-assign

    • 作用

      Object.assign(a, b);
      
      1

      会被转译为:

      function _extends() { 
          _extends =  Object.assign 
                      || 
                      function (target) { 
                          for (var i = 1; i < arguments.length; i++) { 
                              var source = arguments[i]; 
                              for (var key in source) { 
                                  if (Object.prototype.hasOwnProperty.call(source, key)) { 
                                      target[key] = source[key]; 
                                  } 
                              } 
                          } 
                          
                          return target; 
                      }; 
          return _extends.apply(this, arguments); 
      }
      
      _extends(a, b);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
    • 目标节点

      CallExpression: 函数调用表达式。

    • 原理

      转换后,会优先用 Object.assign,其次自行实现一个 extend 方法,用循环赋值模拟。

  • babel-plugin-transform-object-set-prototype-of-to-assign

    • 作用

      Object.setPrototypeOf(bar, foo);
      
      1

      会被转译为:

      function _defaults(obj, defaults) { 
          var keys = Object.getOwnPropertyNames(defaults); 
      
          for (var i = 0; i < keys.length; i++) { 
              var key = keys[i]; 
              var value = Object.getOwnPropertyDescriptor(defaults, key); 
              if (value && value.configurable && obj[key] === undefined) { 
                  Object.defineProperty(obj, key, value); 
              } 
          } 
          
          return obj; 
      }
      
      _defaults(bar, foo);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      Object.setPrototypeOf(obj, prototype) 是 ECMAScript 6之后提出的的新方法,相对于 obj.__proto__ = prototype 这种非标准写法,它被认为是修改对象原型更合适的方法。

    • 目标节点

      CallExpression: 函数调用表达式。

    • 原理

      该插件源码比较简单:

      CallExpression(path, file) {
          if (path.get("callee").matchesPattern("Object.setPrototypeOf")) {
              path.node.callee = file.addHelper("defaults");
          }
      }
      
      1
      2
      3
      4
      5

      如果是 Object.setPrototypeOf 方法的调用,则将该调用替换为 _defaults 方法的调用,_defaults 方法来自辅助模块(babel-helpers、babel-runtime/helpers、babel-runtime-corejs2/helpers、babel-runtime-corejs3/helpers)。

      _defaults 方法内部将 Object.setPrototypeOf(bar, foo); 中的 foo 上的属性直接给了 bar 对象(如果 bar 对象上没有该属性的话)。

      当然也有其他实现,比如:

      if (!Object.setPrototypeOf) {
          // 仅适用于Chrome和FireFox,在IE中不工作:
          Object.prototype.setPrototypeOf = function(obj, proto) {
              if(obj.__proto__) {
                  obj.__proto__ = proto;
                  return obj;
              } else {
                  // 如果你想返回 prototype of Object.create(null):
                  var Fn = function() {
                      for (var key in obj) {
                          Object.defineProperty(this, key, {
                              value: obj[key],
                          });
                      }
                  };
                  Fn.prototype = proto;
                  return new Fn();
              }
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
  • babel-plugin-transform-object-super

    • 作用

      let obj = {
          say() {
              return super.say() + "World!";
          },
      };
      
      1
      2
      3
      4
      5

      会被转译为:

      function _get(...) {...}
      function _getPrototypeOf(...) {...}
      
      var obj = _obj = {
          say: function say() {
              return _get(_getPrototypeOf(_obj), "say", this).call(this) + "World!";
          }
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
    • 目标节点

      ObjectExpression: 对象表达式。

    • 原理

      从转换后的结果来看,针对 super.say() 类的调用,babel-plugin-transform-object-super 将其转为了_get(_getPrototypeOf(_obj), "say", this).call(this)

      这里又定义了一个变量 _obj,应该是为了防止 say() 执行时,obj 被修改而设置的缓存变量。

  • babel-plugin-transform-parameters

    • 作用

      该插件会处理参数的转换。

      function test(x = "hello", { a, b }, ...args) {
          console.log(x, a, b, args);
      }
      
      1
      2
      3

      会被转译为:

      function test() {
          let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "hello";
          let {
              a,
              b
          } = arguments.length > 1 ? arguments[1] : undefined;
      
          for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
              args[_key - 2] = arguments[_key];
          }
      
          console.log(x, a, b, args);
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
    • 目标节点

      Function: 函数。

    • 原理

      针对 Function节点,且有 restassignmentPattern 类型的参数时,做转换操作。

      在满足上述条件时,针对箭头函数做特殊处理,箭头函数内不能使用 arguments 获取运行时的参数,需要先将箭头函数转为普通函数表达式。

      如果是 rest 类型的节点,会识别目标参数名称,生成数组对其赋值。

      如果是 assignmentPattern 类型的,也就是有默认值的参数,会在运行时优先使用传入的新值,否则使用默认值。

  • babel-plugin-transform-property-literals

    • 作用

      var foo = {
          // changed
          const: function() {},
          var: function() {},
          default: 1,
      
          // not changed
          1: 2,
          [a]: 2,
          foo: 1,
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      会被转译为:

      var foo = {
          // changed
          "const": function () {},
          "var": function () {},
          "default": 1,
      
          // not changed
          1: 2,
          [a]: 2,
          foo: 1
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
    • 目标节点

      ObjectProperty: 对象属性节点。

    • 原理

      源码也比较简单,针对满足如下条件的 ObjectProperty 节点:

      • 非计算属性
      • 标识符
      • ES3中的保留字

      会将这些属性节点转为字符串形式的节点。

  • babel-plugin-transform-property-mutators

    • 作用

      var foo = {
          get bar() {
              return this._bar;
          },
          set bar(value) {
              this._bar = value;
          },
      };
      
      1
      2
      3
      4
      5
      6
      7
      8

      会被转译为:

      var foo = Object.defineProperties({}, {
          bar: {
              get: function () {
                  return this._bar;
              },
              set: function (value) {
                  this._bar = value;
              },
              configurable: true,
              enumerable: true
          }
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
    • 目标节点

      ObjectExpression: 对象表达式。

    • 原理

      针对对象表达式节点,每个属性(property)均有一个kind属性,标记 get/set 等类型信息。

      对于含这些信息的节点做转换处理,有以下特点:

      • 创建一个对象
      • 对象上设置属性bar
      • Object.defineProperty 设置属性bar的描述信息
  • babel-plugin-transform-proto-to-assign

    • 作用

      将非标准用法 __proto__ 转为 assign 式的写法。

      以下案例来自Babel官网:

      var foo = { a: 1 };
      var bar = { b: 2 };
      bar.__proto__ = foo;
      bar.a; // 1
      bar.b; // 2
      
      1
      2
      3
      4
      5

      转译结果:

      function _defaults(obj, defaults) { 
          var keys = Object.getOwnPropertyNames(defaults); 
          
          for (var i = 0; i < keys.length; i++) { 
              var key = keys[i]; 
              var value = Object.getOwnPropertyDescriptor(defaults, key); 
              
              if (value && value.configurable && obj[key] === undefined) { 
                  Object.defineProperty(obj, key, value); 
              } 
          } 
          
          return obj; 
      }
      
      var foo = {
          a: 1
      };
      var bar = {
          b: 2
      };
      
      _defaults(bar, foo);
      
      bar.a; // 1
      
      bar.b; // 2
      
      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
    • 目标节点

      • AssignmentExpression: 赋值表达式
      • ExpressionStatement: 表达式语句
      • ObjectExpression: 对象表达式
    • 原理

      Object.getOwnPropertyNames() 方法返回一个由指定对象的所有自身可枚举和不可枚举属性的属性名(不包括 Symbol 值作为名称的属性)组成的数组。

      从转换后的代码可以看出:

      • 会遍历 foo 节点所有可枚举和不可枚举的属性
      • 如果 obj 没有 foo 中的属性,会将 foo 该属性的描述符设置到 obj 的该属性上

      也就是说,转换后,obj 自身会具备 foo 上的属性。

  • babel-plugin-transform-react-constant-elements

  • babel-plugin-transform-react-display-name

  • babel-plugin-transform-react-inline-elements

  • babel-plugin-transform-react-jsx

  • babel-plugin-transform-react-jsx-compat

  • babel-plugin-transform-react-jsx-development

  • babel-plugin-transform-react-jsx-self

  • babel-plugin-transform-react-jsx-source

  • babel-plugin-transform-react-pure-annotations

  • babel-plugin-transform-regenerator

    • 作用

      以下案例来自 Babel 官网:

      function* a() {
          yield 1;
      }
      
      1
      2
      3

      会被转译为:

      var _marked = /*#__PURE__*/regeneratorRuntime.mark(a);
      
      function a() {
          return regeneratorRuntime.wrap(function a$(_context) {
              while (1) switch (_context.prev = _context.next) {
              case 0:
                  _context.next = 2;
                  return 1;
      
              case 2:
              case "end":
                  return _context.stop();
              }
          }, _marked);
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-transform-reserved-words

    • 作用

      该插件针对转换目标要求兼容 ES3 的场景,ES3 中有一些保留字,在 ES5 及后续版本并非保留字了,该插件识别出这些保留字,对其重命名,避免 ES3 环境中运行出现问题。

      var abstract = 1;
      var x = abstract + 1;
      
      1
      2

      会被转译为:

      var _abstract = 1;
      var x = _abstract + 1;
      
      1
      2
    • 目标节点

      • BindingIdentifier: TODO
      • ReferencedIdentifier: TODO
    • 原理

      对于 BindingIdentifierReferencedIdentifier 节点,如果是 ES3 中定义的关键词,会对其重命名。

      ES3 的未来保留字如下:

      abstract / enum / int / short / boolean / export / interface / static / byte / extends / long / super / char / final / native / synchronized / class / float / package / throws / const / goto / private / transient / debugger / implements / protected / volatile / double / import / public
      
      1
  • babel-plugin-transform-runtime

    TODO

  • babel-plugin-transform-shorthand-properties

    • 作用

      转换对象属性简写。

      var o = { 
          a, 
          b() {}
      };
      
      1
      2
      3
      4

      会被转译为:

      var o = {
          a: a,
          b: function () {}
      };
      
      1
      2
      3
      4
    • 目标节点

      • ObjectMethod: 对象的方法
      • ObjectProperty: 对象的属性
    • 原理

      对象的方法和属性,均有简写式写法:

      var o = { 
          a, 
          b() {}
      };
      
      1
      2
      3
      4

      在转换对象的简写方法时,会创建一个普通的函数表达式替代,并设置各种原来具备的属性。

      在转换对象的简写属性时,会转译为非简写写法。

  • babel-plugin-transform-spread

    • 作用

      转换扩展运算符。

      var a = ["a", "b", "c"];
      var b = [...a, "foo"];
      var c = foo(...a);
      
      1
      2
      3

      会被转译为:

      var a = ["a", "b", "c"];
      var b = [].concat(a, ["foo"]);
      var c = foo.apply(void 0, a);
      
      1
      2
      3
    • 目标节点

      • ArrayExpression: 数组表达式
      • CallExpression: 函数调用表达式
      • NewExpression: new 表达式
    • 原理

      • ArrayExpression的处理

        如:[...a, "foo"];

        对于这样的数组表达式,内部实现上有这样的逻辑:

        • 如果 a 数组只有一个元素,则直接 ...a 替换为 a[0],不再用 concat 这种相对“重”的操作

        • 如果 a 是一个数组表达式,则转译为 a.concat(["foo"])

        • 如果 a 不是一个数组表达式,比如 [...{}, "foo"];,会被转译为 [].concat(_toConsumableArray({}), ["foo"]);

          _toConsumableArray 详解TODO

      • CallExpression的处理

        如:var c = foo(...a);

        函数调用表达式中参数含有扩展符的话,最终被转译为 foo.apply 式调用:

        var c = foo.apply(void 0, _toConsumableArray(a));
        
        1
      • NewExpression 的处理

        NewExpression 就是 new 表达式,如 new Date(...params)

        该表达式中的扩展运算符转译操作不能简单的使用 apply 进行,因为 apply 只是执行方法,而缺少 new 的操作。

        插件对 new Date(...params) 的转译结果是:

        function _construct(Parent, args, Class) {
            if (_isNativeReflectConstruct()) {
                _construct = Reflect.construct;
            } else {
                _construct = function _construct(Parent, args, Class) { 
                    var a = [null]; a.push.apply(a, args); 
                    var Constructor = Function.bind.apply(Parent, a); 
                    var instance = new Constructor(); 
                    
                    if (Class) _setPrototypeOf(instance, Class.prototype); 
                    
                    return instance; 
                }; 
            } 
            
            return _construct.apply(null, arguments);     
        }
        
        _construct(Date, _toConsumableArray(params));
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        TODO,详解

  • babel-plugin-transform-sticky-regex

    • 作用

      const a = /o+/y;
      
      1

      会被转译为:

      const a = new RegExp("o+", "y");
      
      1
    • 目标节点

      RegExpLiteral: 正则字面量。

    • 原理

      针对 RegExpLiteral 节点中含有 y 的flag的,会转译为 RegExp 实例式写法:new RegExp(xx, 'y')

  • babel-plugin-transform-strict-mode

    • 作用

      添加严格模式指令。

      foo();
      
      1

      会被转译为:

      "use strict";
      
      foo();
      
      1
      2
      3
    • 目标节点

      Program: 根节点。

    • 原理

      遍历 Program 的指令中是否含有 "use strict",如果没有,向节点的指令数组中添加 "use strict" 指令。

  • babel-plugin-transform-template-literals

    • 作用

      转换模板字符串。

      `foo${bar}`;
      
      1

      会被转译为:

      "foo".concat(bar);
      
      1
    • 目标节点

      • TaggedTemplateExpression

        带标签的模板字符串表达式。

        比如:

        function tagFunc(...args) {
            return args;
        }
        
        const setting = 'dark mode';
        
        const str = tagFunc`Setting ${ setting } is ${ value }!`;
        
        console.log(str);
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
      • TemplateLiteral

        模板字面量,如 Hello ${name}

    • 原理

      • TaggedTemplateExpression

        源码:tagFunc\Setting ${ setting } is ${ value }!`;`

        转译后的结果是:

        function _taggedTemplateLiteral(strings, raw) { 
            if (!raw) { 
                raw = strings.slice(0); 
            }
        
            return Object.freeze(
                Object.defineProperties(strings, {
                    raw: {
                        value: Object.freeze(raw)
                    }
                })
            );
        }
        
        tagFunc(
            _taggedTemplateLiteral( ["Setting ", " is ", "!"] )), 
            setting, 
            value
        );
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        可以看到,带标签的模板字符串被转译为了 tagFunc 的函数调用,并且调用的参数依次是:

        • 带有属性 raw 的数组 ["Setting ", " is ", "!"]

          TODO

        • setting 的值

        • value 的值

      • TemplateLiteral

        源码:

        const content = 'world';
        console.log(\`Hello \${content}\`);
        
        1
        2

        转译后的结果是:

        var content = 'world';
        console.log("Hello ".concat(content));
        
        1
        2
  • babel-plugin-transform-typeof-symbol

    • 作用

      typeof Symbol() === "symbol";
      
      1

      会被转译为:

      function _typeof(obj) { 
          "@babel/helpers - typeof"; 
          
          if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 
              _typeof = function (obj) { 
                  return typeof obj; 
              }; 
          } else { 
              _typeof = function (obj) { 
                  return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
              }; 
          } 
          
          return _typeof(obj);     
      }
      
      _typeof(Symbol()) === "symbol";
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
    • 目标节点

      • Scope: 作用域节点
      • UnaryExpression: 一元表达式
    • 原理

      • Scope

        该处理逻辑关注作用域中是否含有 Symbol 的绑定信息,如 const Symbol = xxx 这样的变量定义,如有,对其重命名。

        例如:

        const Symbol = () => {}
        typeof Symbol() === "symbol";
        
        1
        2

        被转译为:

        function _typeof(...) { ... }
        
        const _Symbol = () => {};
        _typeof(_Symbol()) === "symbol";
        
        1
        2
        3
        4
      • UnaryExpression

        针对 typeof xx 一元表达式做各种处理。

  • babel-plugin-transform-typescript

    TODO

  • babel-plugin-transform-unicode-escapes

    • 作用

      var \u{1d49c} = "\u{Babe1}";
      
      console.log(\u{1d49c});
      
      1
      2
      3

      会被转译为:

      var _ud835_udc9c = "\uDAAA\uDFE1";
      
      console.log(_ud835_udc9c);
      
      1
      2
      3
    • 目标节点

    • 原理

  • babel-plugin-transform-unicode-regex

    • 作用

      var string = "foo💩bar";
      var match = string.match(/foo(.)bar/u);
      
      1
      2

      会被转译为:

      var string = "foo💩bar";
      var match = string.match(/foo((?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))bar/);
      
      1
      2
    • 目标节点

      TODO

    • 原理

      TODO

Last update: October 10, 2022 17:36
Contributors: hoperyy