标签(空格分隔): 前端
[TOC]
参考文章 juejin.cn/post/684490… github.com/hoperyy/wat…
简述
babel是什么
babel是一个接受输入,进行处理,进行输出的工具。 用途是将输入的高版本的js代码转为兼容旧版本的代码。 使用babel本质上是对字符串进行处理,插件越多,进行的处理越多,也就会越耗时。所以从优化上来说的话,应当懂得自己的目标需求,将使用的插件减少到必要范围内。
使用方式
最常使用的是结合 webpack
一起使用的 babel-loader
,另外 babel-cli
提供了命令行执行 npx babel xxx --xxxoptions
的方式。
关于插件
plugin 配置的是具体使用的插件。
preset 因为有些插件经常结合使用,所以就把一组插件的集合设置一个 preset。最终 preset 在被使用时还是会被变成一个个 plugin 放到 visitor
对象中。
插件分为语法插件和转译插件,前者只是负责开启 babel-parser 本身具备的能力,后者开发时按照约定的姿势写 visitor。
如果不开启任何插件,bebel 执行后将原样出处,但是中间依然经理 ast
处理过程,会耗费性能。
关于 @babel/runtime
www.babeljs.cn/docs/babel-… 只是把一些反复使用的函数抽取出来,放进模块里,这样在生成的代码中不用反复写同样的代码。
简要流程
- 从 babel-cli 或 babel-loader 得到原始的文件内容或原始字符串
_parser.default()
中处理参数中的 preset plugin,得到将要使用的 plugin 列表。- 在
@babel/parser
中运行_parser.parse(input)
,这里会进行new Node()
递归进行词法解析和语法解析生成ast
节点。【对原始字符串用str.codePointAt()
得到原始字符的 unicode,然后使用一定规则判断该字符的意义,得到原始ast
】 traverse()
会递归调用traverse.node()
遍历原始ast
,配合 plugin 得到目标ast
,babel本身不做什么。@babel/generator
结合 printer 递归处理目标ast
,并且在输出上利用了buffer
, 用_flush
递归目标ast
,去进行this._append
, 利用this._buf.join("").trimRight()
得到目标字符串。【关于buffer.concat】- 输出目标字符串。
源码小trick
void 0
表示 undefined。void 运算符 对给定的表达式进行求值,然后返回 undefined。- (0, function)(param),本质是逗号操作符,这样的话就解除了function的隐式this绑定,把this默认指向了window(浏览器)或global(node)。如
@babel/cli/lib/babel/index.js:12
的const opts = (0, _options.default)(process.argv);
详细流程
关于 _interopRequireDefault
是为了兼容 commonjs和es6的模块。
stackoverflow.com/questions/5…
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
关于 commonjs中的 module.exports 和 exports
两者最初都是指向同一个对象,exports.a = 1的话,module.exports上面也会有1,但是exports本身就是一个变量而已,如果 module.exports = {},那么输出的就会是新的对象,而 exports指向的还是老的对象。那整个模块输出的就会是新的对象,而不是exports指向的对象了。但是设置 exports = xxx 就不行了,只是把 exports 指向了别处,而输出的是 module.exports。
关于 babel-cli 入口
- @babel/cli
index.js
中直接 throw new Error("Use the@babel/core
package instead of@babel/cli
."); - 在
package.json
中bin
定义的babel
命令,指向bin/babel.js
。 - 如果没有传入其他参数决定使用什么 plugin 或 preset 的话,就原样输出读入的文件内容。但是
npx babel 18-jianzhi.js --presets=@babel/preset-env
就能输出转换后的代码。
以 babel-cli 为入口开始执行
- 从命令行或
npx babel 1.js
会在babel/cli中先读取options,经过options中的函数的处理,得到最终的参数,然后决定使用dir
函数还是file
函数。这里测试是使用的是 file。 - 在
/Users/liujunyang/work/xuetang/gets3/node_modules/@babel/core/lib/transform-file.js
中,设置的 @babel/preset-env 在 babel-core 中经过它的 config.default 函数处理后,都变成了 plugin 数组中的元素(目前 40 个),而 preset数组成了空数组。 - 然后通过
fs.readFile
读取目标文件,然后运行_transformation.run
进行处理, - 在
_transformation
模块中,先执行_normalizeFile
,在_normalizeFile
中,判断如果没有ast
的话,就先调用_parser.default(pluginPasses【plugin列表】, options【文件名、是不是babelrc方式,cwd等】, code【输入的解析前的代码】)
去生成ast
,【ast 根部的 type 必须是 Program 或 File】, - 上面在
_parser.default
中会逐个判断plugin
有没有设置parserOverride
, 然后运行_parser().parse
, 也就是进入了@babel/parser【是用TS 2.9写的】
, - 在
@babel/parser
中代表输入的代码的变量名是叫input
, 这里会进行new Node()
去生成节点【一个Node
实例有type start end loc【SourceLocation】 range
这些属性】, - 接下来会
getTokenFromCode
直到finishToken
。 - 而
getTokenFromCode
用的就是str.codePointAt()
得到每一个字符的unicode值【如let 的 l 就是 108】,然后逐步去readWord()
, 就是读取一个个的字符或者词语,比如把let
arr
=
[
11
,
等字符给识别出来, - 最终
parse()
方法返回出来的变量名叫file
,也就是传说中的 ast, - 得到的结果类似下面的结构,比如
type: VariableDeclaration
用了关键词kind: let
来表示是怎么进行变量声明的,type: VariableDeclarator
用的name: arr
来表示变量名,type: NumericLiteral
用的value: 12
来表示的元素的值。总之不同的type
用了不同的字段去表述。共同的结构比如都有start end loc
等等【具体结构见文末】。 - 接下来回到
@babel/core
中 开始transformFile
进行转换。这个函数中直接执行了 for 循环【数组只有一个值,内容是一个数组】 - 然后执行了
_traverse().default.visitors.merge
得到了visitor
对象,每个键名就是一个插件的名字,值就是插件的enter
方法等组成的对象。 - 然后执行
(0, _traverse().default)(file.ast, visitor, file.scope);
对ast
进行遍历,然后这些visitor【其实就是一个个的babel插件】
去操作每一步遍历过程的ast
节点。 traverse()
会递归调用traverse.node()
, 其中关键的就是_context.default()
方法生成了一个context
对象,- 然后调用了
context.visit()
方法,然后其中根据是不是数组分别调用visitMultiple
visitSingle
方法。 - 在这个
traverse.node()
的过程中,把ast
转换成了目标ast
【比如kind: let
变成了kind: var
】TODO。 - 然后继续在
@babel/core
中的transformFile
调用_generate.default
方法去生成目标代码。就进入了@babel/generator
。 @babel/generator
利用了buffer
, 用_flush
递归目标ast
,去进行this._append
, 利用this._buf.join("").trimRight()
得到目标字符串然后返回出去。
原始 ast 简要结构
{
"type": "File",
"start": 0,
"end": 32,
"loc": {},
"errors": [],
"comments": [],
"program": {
"type": "Program",
"start": 0,
"end": 32,
"loc": {},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"kind": "let",
"start": 0,
"end": 31,
"loc": {},
"declarations": [
{
"type": "VariableDeclarator",
"name": "arr",
"start": 4,
"end": 31,
"loc": {},
"id": {
"type": "Identifier",
"start": 4,
"end": 7,
"loc": {},
},
"init": {
"type": "ArrayExpression",
"start": 10,
"end": 31,
"loc": {},
"elements": [
{
"type": "NumericLiteral",
"start": 11,
"end": 12,
"loc": {},
"value": 2
},
]
}
}
],
}
],
},
}