提案特性转换插件

2022-10-7 About 11 min

# 提案特性转换插件

截至本书编写时,Babel 有 24 个内置的提案特性转换插件。

提案特性转换插件,就是对 ECMAScript 的提案语法进行语法和转译支持,这些插件可能同时有语法开关和代码转译的功能。

  • babel-plugin-proposal-async-generator-functions

    • 作用

      async function* agf() {
          await 1;
          yield 2;
      }
      
      1
      2
      3
      4

      会被转译为:

      // 各种函数定义
      
      function agf() {
          return _agf.apply(this, arguments);
      }
      
      function _agf() {
          _agf = _wrapAsyncGenerator(function* () {
              yield _awaitAsyncGenerator(1);
              yield 2;
          });
          return _agf.apply(this, arguments);
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
    • 目标节点

      Function: 函数。

    • 原理

      针对带有 async 标记的函数处理。

      转换函数内部的 await for...of 语句,转为 generator写法。

      然后转换上述过程产生的 yield 标记的语句。

      TODO

  • babel-plugin-proposal-class-properties

    • 作用

      转换 class 中的原型和静态属性/方法。

      下例来自 Babel 官网。

      class Bork {
          // Property initializer syntax
          instanceProperty = "bork";
          boundFunction = () => {
              return this.instanceProperty;
          };
      
          // Static class properties
          static staticProperty = "babelIsCool";
          static staticFunction = function() {
              return Bork.staticProperty;
          };
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      会被转译为:

      // 定义 obj 对象的 key 属性值为 value
      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; 
      }
      
      class Bork {
          constructor() {
              _defineProperty(this, "instanceProperty", "bork");
      
              _defineProperty(this, "boundFunction", () => {
                  return this.instanceProperty;
              });
          }
      }
      
      _defineProperty(Bork, "staticProperty", "babelIsCool");
      
      _defineProperty(Bork, "staticFunction", function () {
          return Bork.staticProperty;
      });
      
      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
    • 目标节点

      • classProperties: TODO
      • classPrivateProperties: TODO
      • classPrivateMethods: TODO
    • 原理

      TODO

  • babel-plugin-proposal-class-static-block

    • 作用

      转换 class 类中的静态 block。

      下例来自 Babel 官网:

      下例的转换需要在该插件前引入插件 babel-plugin-proposal-class-properties,也就是支持对 class 属性的语法支持和代码转换。

      class C {
          static #x = 42;
          static y;
          static {
              try {
                  this.y = doSomethingWith(this.#x);
              } catch {
                  this.y = "unknown";
              }
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      会被转译为(以下代码简化了一些逻辑,非真实转换的结果):

      var C = function C() {
          // 检查 C 被调用的方式是否是 new C() 的形式,而非普通函数调用
          _classCallCheck(this, C);
      };
      
      var _x = {
          writable: true,
          value: 42
      };
      
      // _defineProperty 的功能类似设置 C.y = undefined
      _defineProperty(C, "y", void 0);
      
      var _ = {
          writable: true,
          value: function () {
              try {
                  C.y = doSomethingWith(_x.value);
              } catch (_unused) {
                  C.y = "unknown";
              }
          }()
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
    • 目标节点

      Class: 类节点

    • 原理

      该插件源码部分只处理 static block 部分的代码,上述转译后的代码包括插件 babel-plugin-proposal-class-properties 的转译结果。

      和 babel-plugin-proposal-class-static-block 相关的转译后代码:

      var _x = {
          writable: true,
          value: 42
      };
      
      var _ = {
          writable: true,
          value: function () {
              try {
                  C.y = doSomethingWith(_x.value);
              } catch (_unused) {
                  C.y = "unknown";
              }
          }()
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      插件内部将 static block 内的代码放入了一个新的立即执行函数体内。

  • babel-plugin-proposal-decorators

    • 作用

      @annotation
      class MyClass {}
      
      function annotation(target) {
          target.annotated = true;
      }
      
      1
      2
      3
      4
      5
      6

      会被转译为:

      // 其他函数定义
      
      let MyClass = _decorate([annotation], function (_initialize) {
          class MyClass {
              constructor() {
                  _initialize(this);
              }
      
          }
      
          return {
              F: MyClass,
              d: []
          };
      });
      
      function annotation(target) {
          target.annotated = true;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-proposal-do-expressions

    • 作用

      会开启对 doExpressions 语法的支持。

      在转换时,该插件会转为三目运算符。

      let a = do {
          if (x > 10) {
              ("big");
          } else {
              ("small");
          }
      };
      
      1
      2
      3
      4
      5
      6
      7

      会被转译为:

      let a = x > 10 ? "big" : "small";
      
      1
    • 目标节点

      DoExpression: do 表达式。

    • 原理

      在进入 DoExpression 节点的 exit 阶段时,获取 do { ... }{ ... } 部分代码,并根据以下情况做不同转换:

      • { ... } 含有至少一行代码

        会使用 path.replaceExpressionWithStatements(body) 替换 DoExpression 节点,将块作用域内部的代码转为表达式,内部调用链路是:

        // babel/packages/babel-traverse/src/path/replacement.ts
        replaceExpressionWithStatements 
        
        --> 
        
        // babel/packages/babel-types/src/converters/toSequenceExpression.ts
        t.toSequenceExpression 
        
        --> 
        
        // babel/packages/babel-types/src/converters/gatherSequenceExpressions.ts
        gatherSequenceExpressions
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        各个方法内部有众多的判断逻辑,在此案例下,当块作用域内部的代码是if表达式时,会转为三目运算表达式。

      • {}没有代码

        会直接生成 void 0

        如:let a = do {} 会被转译为 var a = undefined

  • babel-plugin-proposal-dynamic-import

    • 作用

      支持动态import语法,并对其转译。

    • 目标节点

      基于 babel-plugin-transform-modules-commonjsbabel-plugin-transform-modules-amdbabel-plugin-transform-modules-systemjs 做转译,所以目标节点为三者之一的目标节点。

    • 原理

      TODO

  • babel-plugin-proposal-export-default-from

    • 作用

      export v from "mod";
      
      1

      会被转译为:

      import _v from "mod";
      export { _v as v };
      
      1
      2
    • 目标节点

    • 原理

  • babel-plugin-proposal-export-namespace-from

    • 作用

      export * as ns from "mod";
      
      1

      会被转译为:

      import * as _ns from "mod";
      export { _ns as ns };
      
      1
      2
    • 目标节点

      ExportNamedDeclaration

    • 原理

      TODO

  • babel-plugin-proposal-function-bind

    • 作用

      开启对functionBind语法的支持,并执行代码转换。

      functionBind,顾名思义,就是 fn.bind(context) 的变种写法。

      • 案例1

        obj::func
        
        1

        会被转译为:

        var _context;
        (_context = obj, func).bind(_context);
        
        1
        2
      • 案例2

        ::obj.func
        
        1

        会被转译为:

        var _context;
        (_context = obj).func.bind(_context);
        
        1
        2
      • 案例3

        obj::func(val)
        
        1

        会被转译为:

        var _context;
        (_context = obj, func).call(_context, val);
        
        1
        2
      • 案例4

        ::obj.func(val)
        
        1

        会被转译为:

        var _context;
        (_context = obj).func.call(_context, val);
        
        1
        2
    • 目标节点

      • CallExpression: 函数调用表达式
      • BindExpression: bind 表达式,如 obj::func
    • 原理

      插件内部实现上,针对 BindExpressionCallExpression 节点分别处理。

      从转译产物也可以看到,主要是生成新的 func.bind 函数调用节点,完成对当前节点的替换。

  • babel-plugin-proposal-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

      会被转译为:

      function _skipFirstGeneratorNext(fn) { 
          return function () { 
              var it = fn.apply(this, arguments); 
              it.next(); 
              return it; 
          }; 
      }
      
      function generator() {
          var _generator = _skipFirstGeneratorNext(function* () {
              let _functionSent = yield;
      
              console.log("Sent", _functionSent);
              console.log("Yield", _functionSent = yield);
          });
          return _generator.apply(this, arguments);
      }
      
      const iterator = generator();
      iterator.next(1); // Logs "Sent 1"
      
      iterator.next(2); // Logs "Yield 2"
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
    • 目标节点

      MetaProperty: 元属性。

    • 原理

      TODO

  • babel-plugin-proposal-json-strings

    • 作用

      const ex = "before
      after";
      // ^ There's a U+2028 char between 'before' and 'after'
      
      1
      2
      3

      会被转译为:

      const ex = "before\u2028after";
      // ^ There's a U+2028 char between 'before' and 'after'
      
      1
      2
    • 目标节点

      • DirectiveLiteral
      • StringLiteral
    • 原理

      TODO

  • babel-plugin-proposal-logical-assignment-operators

    • 作用

      转换逻辑运算符(&&||??)和复制表达式组成的复合赋值运算符。

      以下是转换的几个案例:

      • a ||= b; 会被转译为 a || (a = b);

      • a &&= b; 会被转译为 a && (a = b);

      • obj.a.b ||= c;

        会被转译为

        var _obj$a;
        
        (_obj$a = obj.a).b || (_obj$a.b = c);
        
        1
        2
        3
      • obj.a.b &&= c;

        会被转译为

        var _obj$a;
        
        (_obj$a = obj.a).b && (_obj$a.b = c);
        
        1
        2
        3
    • 目标节点

      AssignmentExpression: 赋值表达式。

    • 原理

      该插件源码的逻辑:

      • 解析 AssignmentExpression 节点,并拆解出 left/operator/right部分

        对于 a &&= b 这样的表达式,operator 值为 &&=,插件内部会取出 && 做下一步的处理。

      • 判断 left 节点是否是对象成员表达式,如果是,复制 left 节点为 lhs,对 lhs 节点进行调整

      • 当前 AssignmentExpression 节点被替换为逻辑表达式 logicalExpression

  • babel-plugin-proposal-nullish-coalescing-operator

    • 作用

      转换空值处理操作符(Nullish coalescing operator)。

      var foo = object.foo ?? "default";
      
      1

      会被转译为:

      var _object$foo;
      var foo = (_object$foo = object.foo) !== null && _object$foo !== void 0 ? _object$foo : "default";
      
      1
      2

      空值合并操作符(??)是一个逻辑操作符,当左侧的操作数严格等于 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

      与逻辑或操作符(||)不同,逻辑或操作符会在左侧操作数为假值(falsy)时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值并不演进,比如遇到假值(''0)时。

    • 目标节点

      LogicalExpression: 逻辑运算表达式。

    • 原理

      针对 node.operator === '??' 的逻辑运算表达式。

      function (a, x = a.b ?? c) {} 会被转译为 function (a, x = (() => a.b ?? c)() ){},也就是将 a.b ?? c 置于立即执行函数(IIFE)中,有效避免了作用域污染的问题。

      插件其余逻辑就是将 ?? 转为三元运算符 ? :

  • babel-plugin-proposal-numeric-separator

    • 作用

      数字分隔符。它可以在数字间创建可视化分隔符(_)来分割数字,提高数字的可读性。用于数字式的表达式:整数(integer)、大数(bigint)、浮点数(floating-point)、小数(fractional)、指数(exponent parts)。

      1_000_000_000 // 十进制数
      0b1010_0100 // 二进制数
      101_123_34.11 // 浮点数
      0.000_001 // 小数部分
      1e10_000 // 指数部分
      
      1
      2
      3
      4
      5

      下述代码:

      let budget = 1_000_000_000_000;
      let nibbles = 0b1010_0001_1000_0101;
      let message = 0xa0_b0_c0;
      
      1
      2
      3

      会被转译为:

      let budget = 1000000000000;
      let nibbles = 0b1010000110000101;
      let message = 0xa0b0c0;
      
      1
      2
      3
    • 目标节点

      • NumericLiteral: 数字字面量
      • BigIntLiteral: bigInt 字面量
    • 原理

      语法方面,该插件开启了 numericSeparator 的语法支持。

      转换方面,源码非常简单:

      function remover({ node }: NodePath<BigIntLiteral | NumericLiteral>) {
          const { extra } = node;
          if (extra?.raw?.includes("_")) {
              extra.raw = extra.raw.replace(/_/g, "");
          }
      }
      
      // 省略了无关代码
      visitor: {
          NumericLiteral: remover,
          BigIntLiteral: remover,
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      当遍历到相关节点时,将 _ 替换为空字符即可。

  • babel-plugin-proposal-object-rest-spread

    • 作用

      let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
      console.log(x); // 1
      console.log(y); // 2
      console.log(z); // { a: 3, b: 4 }
      
      1
      2
      3
      4

      会被转译为:

      let _x$y$a$b = {
              x: 1,
              y: 2,
              a: 3,
              b: 4
              },
                  {
              x,
              y
          } = _x$y$a$b,
          z = _objectWithoutProperties(_x$y$a$b, ["x", "y"]);
      
      console.log(x); // 1
      
      console.log(y); // 2
      
      console.log(z); // { a: 3, b: 4 }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-proposal-optional-catch-binding

    • 作用

      try {
          throw 0;
      } catch {
          doSomthing();
      }
      
      1
      2
      3
      4
      5

      会被转译为:

      try {
          throw 0;
      } catch (_unused) {
          doSomthing();
      }
      
      1
      2
      3
      4
      5
    • 目标节点

      CatchClause: catch 从句。

    • 原理

      CatchClause 节点可以对应 catch { ... }catch(...) { ... } 两种写法,所以需排除后者写法(通过判断 path.node.param 是否为真值)。

      在解析为 CatchClause 节点时,会携带 param 属性,这里将param 的值设置为 _unused 即可。

  • babel-plugin-proposal-optional-chaining

    • 作用

      转换可选链。

      const result = a?.b?.c;
      
      1

      会被转译为(转换后的代码做了格式化处理,便于阅读):

      var _a, _a$b;
      
      const result = 
          (_a = a) === null || _a === void 0 
      
          ? 
      
          void 0 
      
          : 
      
          (_a$b = _a.b) === null || _a$b === void 0 
      
          ? 
          
          void 0 
      
          : 
      
          _a$b.c;
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
    • 目标节点

      • OptionalCallExpression: 可选链执行方法表达式,如 f?.()()
      • OptionalMemberExpression: 可选链表达式,如 a?.b?.c
    • 原理

      从转换产物可以看到,与 || 可能判断 null/undefined/0 等不同,a?.b 在实现上是判断 a 的值是否严格等于 nullundefined 两个值。

      对于OptionalCallExpression节点:

      f?.()()

      转换结果是:

      var _f;
      
      (_f = f) === null || _f === void 0 ? void 0 : _f()();
      
      1
      2
      3

      判断 f 是否是 nullundefined,若是,返回 undefined 作为表达式的值,否则正常执行。

  • babel-plugin-proposal-partial-application

    • 作用

      f(x, ?)           // partial application from left
      f(?, x)           // partial application from right
      f(?, x, ?)        // partial application for any arg
      
      1
      2
      3

      会被转译为:

      var _x,
          _f;
      
      _f = f,
      _x = x, 
      
      // f(x, ?)
      function f(_argPlaceholder) {
          return _f(_x, _argPlaceholder); 
      };
      
      // f(?, x)
      function f(_argPlaceholder) {
          return _f(_argPlaceholder, _x); 
      };
      
      // f(x, ?)
      function f(_argPlaceholder, _argPlaceholder2) {
          return _f(_argPlaceholder, _x, _argPlaceholder2);
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      o.f(x, ?)         // partial application from left
      o.f(?, x)         // partial application from right
      o.f(?, x, ?)      // partial application for any arg
      super.f(?)        // partial application allowed for call on |SuperProperty|
      
      1
      2
      3
      4

      会被转译为:

      var _x, 
          _o$f, 
          _o;
      
      _o = o, 
      _o$f = _o.f, 
      _x = x, 
      
      function f(_argPlaceholder) {
          return _o$f.call(_o, _x, _argPlaceholder);
      };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
    • 目标节点

      CallExpression: 函数调用节点。

    • 原理

      如果函数调用时,没有描述 ArgumentPlaceholder,也就是 ?,不做处理。

      处理时判断当前节点是 o.f() 还是 f() 形式的调用,分别处理。

      二者的最终处理逻辑类似,都是为 f 创建一个新的函数,并提供占位的参数变量。对于 o.f() 的调用形式,还需通过 call 绑定 this 上下文。

  • babel-plugin-proposal-pipeline-operator

    • 作用

      管道运算符 |> 本质上是带有单个参数的函数调用中的一个有用的语法糖。换句话说,sqrt(64) 等价于 64 |> sqrt

      当把多个函数连接在一起时,这样可以提供更好的可读性:

      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

      下面的调用是等价的:

      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
    • 目标节点

      TODO

    • 原理

      TODO

  • babel-plugin-proposal-private-methods

    • 作用

      配合babel-plugin-proposal-class-properties使用。

      class Counter extends HTMLElement {
          #xValue = 0;
      
          get #x() {
              return this.#xValue;
          }
          set #x(value) {
              this.#xValue = value;
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      会被转译为:

      var _xValue = new WeakMap();
      
      var _x = new WeakMap();
      
      class Counter {
          constructor() {
              _x.set(this, {
                  get: _get_x,
                  set: _set_x
              });
      
              _xValue.set(this, {
                  writable: true,
                  value: 0
              });
          }
      }
      
      function _get_x() {
          return _classPrivateFieldGet(this, _xValue);
      }
      
      function _set_x(value) {
          _classPrivateFieldSet(this, _xValue, value);
      }
      
      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
    • 目标节点

      TODO

    • 作用

      TODO

  • babel-plugin-proposal-private-property-in-object

    • 作用

      配合 babel-plugin-proposal-class-properties 一起使用。

      class Foo {
          #bar = "bar";
      
          test(obj) {
              return #bar in obj;
          }
      }
      
      1
      2
      3
      4
      5
      6
      7

      会被转译为:

      var _bar = new WeakMap();
      
      class Foo {
          constructor() {
              _bar.set(this, {
              writable: true,
              value: "bar"
              });
          }
      
          test(obj) {
              return _bar.has(obj);
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
    • 目标节点

    • 原理

  • babel-plugin-proposal-record-and-tuple

    • 作用

      转换 RecordTuple 类型。

      在介绍 babel-plugin-syntax-record-and-tuple 时,提到过,Record 类似对象,Tuple 类似数组,其成员必须是非引用类型,它们可以通过成员值是否一致判断相等性。

      #[1, 2, 3]
      #{ a: 1, b: '2' }
      
      1
      2

      被转译为:

      Tuple(1, 2, 3);
      Record({
          a: 1,
          b: '2'
      });
      
      1
      2
      3
      4
      5
    • 目标节点

      • RecordExpression: Record表达式
      • TupleExpression: Tuple表达式
    • 原理

      插件逻辑比较简单,直接用 builtIn 式的函数 Tuple()Record() 替换原来的代码。

  • babel-plugin-proposal-throw-expressions

    • 作用

      开启 throwExpressions 语法支持,且做代码转换。

      function test(param = throw new Error("required!")) {
          const test = param === true || throw new Error("Falsy!");
      }
      
      1
      2
      3

      会被转译为:

      function test( param = function (e) { throw e; }(new Error("param required!")) ) { // 默认参数值是立即执行函数
          const test = param === true || function (e) { throw e; }(new Error("Falsy!")); // 右侧是立即执行函数
      }
      
      1
      2
      3

      上述函数的作用是,如果执行 test 函数:

      • test(): 会直接抛出 "param required" 错误
      • test(false): 会直接抛出 "Falsy" 错误
      • test(true): 会无抛错运行
    • 目标节点

      UnaryExpression: 一元表达式。

    • 原理

      识别一元表达式,并过滤 path.operator 值为非 throw 的节点。

      之后创建普通函数 function(e) { throw e } 节点,最终创建一个函数执行节点 callExpression,执行的参数是 throw 后面的表达式。

  • babel-plugin-proposal-unicode-property-regex

    TODO

Last update: October 10, 2022 15:39
Contributors: hoperyy