在本章,我们将全面了解 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 名称, 插件配置参数 ]
]
}
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
12babel-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 中内置了新的高精度数字计算类型,开发者也就不再需要引入第三方工具或其他手段解决精度问题,代码量更少。
用哪种新的数据类型?
提案中提到了两种新的数据类型:
BigDecimal
和Decimal128
。二者主要是对数字精度方面的区别,各有利弊,用哪一种需要做权衡。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
18babel-plugin-syntax-export-default-from
开启对
export-default-from
表达式语法的支持,不提供转译支持。可以使用babel-plugin-proposal-export-default-from
同时启动语法和转译。export default { ... }
1babel-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
15babel-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
9babel-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' } })
1export
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';
1babel-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
11babel-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
)。该特性使用
#
标记,且支持Record
和Tuple
的互相嵌套式写法。例如:
// 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
9babel-plugin-syntax-throw-expressions
开启对
throwExpressions
的语法支持。举例:
function save(filename = throw new TypeError("Argument required")) {}
1babel-plugin-syntax-top-level-await
开启在一级作用域直接使用
await
语法的支持。const val = await promise; export { val };
1
2
3babel-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
3spec分别为
true
、false
的转译结果: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
标记的表达式- 转换后的普通函数利用:
Promise
、yield
表达式的done/value
、递归调用等实现了一个自动执行方法
其实,就是常说的:
async/await
是promise/generaotr
的语法糖。用第三方工具实现
async/await
添加配置项
module
和method
,使用第三方实现协程:{ 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/await
。state
对象定义在 babel-traverse 中,但并没有定义state.methodWrapper
方法,笔者判断应该是一个临时的自定义方法,一旦确定,后续执行的插件逻辑会统一使用该方式定义的"协程"。自定义方法实现"协程":
state.addHelper("asyncToGenerator")
state.addHelper
是state.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
节点有node
、parent
属性,二者均是 AST 节点对象,具体定义,在"Babel节点集"有详细介绍。t.isFunction
和t.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
3throwIfClosureRequired: 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
4tdz: 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...of
、for...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
中含有两个绑定信息:i
、key
。这两个绑定信息将被挪动到当前节点上层最近的一个函数作用域或顶层作用域。转译结果是:
{ (function () { // 函数作用域 for (var i of [1, 2, 3]) { var key = void 0; } })(); }
1
2
3
4
5
6
7可以看到,
i
、key
两个变量影响的作用域在最近的一层函数作用域。
插件源码细节
VariableDeclaration
处理逻辑path.getBindingIdentifiers()
获取和
path.node
节点绑定的标识符列表。"Babel插件编写"章节中t.getBindingIdentifiers
有对该方法的详细介绍。scope.getOwnBinding(name)
获取当前节点中,
uid
为name
的scope.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
12ClassExpression
类表达式处理逻辑,如
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
是一个别名,包括ForInStatement
、ForOfStatement
。如:
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
4TODO
ForXStatement
如:
for ({ length: k } in { abc: 3 }) {}
会被转译为:
for (var _ref in { abc: 3 }) { k = _ref.length; void 0; }
1
2
3
4CatchClause
如:
try { } catch({ message }) {}
会被转译为:
try {} catch (_ref) { var message = _ref.message; }
1
2
3AssignmentExpression
如:
[ 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
4VariableDeclaration
如:
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)
为true
的NumericLiteral
节点/\\[u]/gi.test(node.extra.raw)
为true
的StringLiteral
节点
然后设置该节点
node.extra = undefined
。在重新生成代码的 babel-generator中,
babel/packages/babel-generator/src/generators/types.ts
文件针对不同类型的节点有不同的处理,其中NumericLiteral
、StringLiteral
节点在重新生成代码字符串时,会判断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
运行时的this
是Foo
的实例的话,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
15Object.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
节点,且有rest
和assignmentPattern
类型的参数时,做转换操作。在满足上述条件时,针对箭头函数做特殊处理,箭头函数内不能使用
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
: TODOReferencedIdentifier
: TODO
原理
对于
BindingIdentifier
和ReferencedIdentifier
节点,如果是 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
19TODO,详解
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
9TemplateLiteral
模板字面量,如
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
4UnaryExpression
针对
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