提案特性转换插件
# 提案特性转换插件
截至本书编写时,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
: TODOclassPrivateProperties
: TODOclassPrivateMethods
: 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-commonjs
、babel-plugin-transform-modules-amd
、babel-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
原理
插件内部实现上,针对
BindExpression
、CallExpression
节点分别处理。从转译产物也可以看到,主要是生成新的
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
3obj.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
的值是否严格等于null
和undefined
两个值。对于
OptionalCallExpression
节点:f?.()()
转换结果是:
var _f; (_f = f) === null || _f === void 0 ? void 0 : _f()();
1
2
3判断
f
是否是null
或undefined
,若是,返回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
20o.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
作用
转换
Record
和Tuple
类型。在介绍
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