Babel的API有哪些?
babel
的编译流程分三步:parse
、transform
、generate
,每一步都暴露了一些api出来。
parse
阶段有@babel/parser
,功能是把源码转成AST。transform
阶段有@babel/traverse
,可以遍历AST,并调用visitor函数修改AST,修改AST自然涉及到AST的判断、创建、修改等,这个时候就需要@babel/types
来对AST进行分类识别,当需要批量创建AST的时候可以使用@babel/template
来简化AST的创建逻辑。generate
阶段会把AST打印为目标代码字符串,同时生成sourcemap
,需要@babel/generator包。- 中途遇到错误想打印代码位置时,使用@babel/code-frame包。
babel
的整体功能通过@babel/core
提供,基于上面的包完成babel
整体的编译流程,并用plugin
和preset
。
我们主要学习的就是@babel/parse
、@babel/traverse
、@babel/generator
、@babel/types
、@babel/template
五个包的api的使用。
@babel/parser
@babel/parser
是基于acorn
实现的,扩展了很多语法,可以支持es next
、jsx
、typescript
、flow
等语法的解析。
@babel/parser
默认只能parse
js代码,jsx
、flow
、typescript
这些类型代码的解析需要用到对应的插件才可以。
@babel/parser
提供了两个api,分别是:parse
和parseExpression
。这两个都是把源码转化为AST,不过parse
返回的AST根节点是File
(整个AST),parseExpression
返回的AST根节点是Expression
(表达式的AST),粒度不同。
js
function parse(input: string, options?: ParserOptions): File
function parseExpression(input: string, options?: ParserOptions): Expression
parse主要分两类,一是parse的内容是什么,二是以什么方式去parse:
parse的内容是什么:
plugins
:指定jsx、typescript、flow等插件来解析对应的语法。allowXxx
:指定一些语法是否允许,比如函数外的await
、未声明的export
等。sourceType
:指定是否支持解析模块语法,有module、script、unambiguous 3个取值- module:解析es module语法。
- script不解析es module语法。
- unambiguous:根据内容是否有import和export来自动设置moudle还是script
一般会指定sourceType为unambiguous。
比如:
js
const parser = require('@babel/parser');
const ast = parser.parse("代码", {
sourceType: 'unambiguous',
plugins: ['jsx']
});
以什么方式parse:
strictMode
是否是严格模式。startLine
从源码的哪一行开始parseerrorRecovery
出错时是否记录错误,并继续往下parsetokens
parse的时候是否保留token信息ranges
是否在ast节点中添加ranges属性
@babel/traverse
parse出的AST由@babel/traverse
来遍历和修改,babel traverse包提供了traverse方法:
js
function traverse(parent, opts)
常用的就前面两个参数,parent是要遍历的AST节点,opts是指定visitor
函数。babel会在遍历parent对应的AST时调用相应的visitor函数。
遍历过程
visitor是指定对什么AST做什么处理的函数,babel会在遍历到对应的AST时回调它们。
而且可以指定刚开始遍历(enter
)和遍历结束后(exit
)两个阶段的回调函数,比如:
js
traverse(ast, {
FunctionDeclaration: {
enter(path, state){}, // 进入节点时调用
exit(path, state){} // 离开节点时调用
}
})
如果仅指定了一个函数,那就是enter阶段会调用:
js
traverse(ast, {
FunctionDeclaration(path, state){}
})
enter时调用是在遍历当前节点的子节点前调用,exit时调用是遍历完当前节点子节点后调用: 而且同一个visitor函数可以用于多个AST节点的处理,方式是指定一系列AST,用|
连接:
js
// 进入FunctionDeclaration和VariableDeclaration节点时调用
traverse(ast, {
'FunctionDeclaration|VariableDeclaration'(path, state){}
})
此外,AST还有别名,比如各种XxxStatement有个Statement的别名,各种XxxDeclaration有个Declaration的别名,那自然可以通过别名来指定对这些AST的处理:
js
// 通过别名指定离开各种Declaration节点时调用
traverse(ast, {
Declaration: {
exit(path, state){}
}
})
具体别名可以在babel文档中查看
path
每个visitor都有path和state的参数,这些参数都是干啥的?
AST是棵树,遍历过程中肯定是有个路径的,path就是记录这个路径的: 如图,节点1、节点2、节点3是三层AST,通过两个path关联了起来。
path1关联了节点1和节点2,记录了节点1是父节点,节点2是子节点。
path2关联了节点2和节点3,记录了节点2是父节点,节点3是子节点。
而且path1和path2还有父子关系。
通过这样的path对象就把遍历的路径串联了起来。
而且最重要的是path有很多属性和方法,比如记录父子、兄弟关系等关系:
- path.node指向当前AST节点
- path.parent指向父级AST节点
- path.getSibling、path.getNextSibling、path.getPrevSibling获取兄弟节点。
- path.find从当前节点向上查找节点
- path.get、path.set获取/设置属性的path
还有作用域相关的:
- path.scope获取当前节点的作用域信息
判断AST类型的:
- path.isXXX判断当前节点是不是xxx类型
- path.assertXxx判断当前节点是不是xx类型,不是则抛出异常
增删改AST的:
- path.replaceWith、path.replaceWithMultiple、path.replaceWithSourceString替换节点
- path.insertBefore、path.insertAfter插入节点
- path.remove移除节点
调过遍历的:
- path.skip跳过当前节点的子节点的遍历
- path.stop结束后续遍历
可以增删改AST,可以按照路径查找任意的节点,还有作用域的信息,那怎么转换和分析代码不就呼之欲出了么。
所以说,path的api是学习babel的最核心。
上面罗列了一些常用的api,可以通过这些api完成对AST的操作,当然path的api不是只有这些。
state
第二个参数state则是遍历过程中在不同节点之间传递数据的机制,插件会通过state传递options和file信息,我们也可以通过state存储一些遍历过程中的共享数据。 这个也很容易理解,节点之间是有传输数据的需求的,不同状态下可能会做不同的处理,这就是为什么这个参数叫做state。
@babel/types
遍历AST的过程中需要创建一些AST和判断AST类型,这时候就需要@babel/types包。
比如要创建IfStatement就可以调用:
js
t.ifStatement(test, consequent, alternate);
而判断节点是否是IfStatement就可以调用isIfStatement或者assertIfStatement:
js
t.isIfStatement(node, opts);
t.assertIfStatment(node, opts)
opts可以指定一些属性是什么值,增加更多限制条件,做更精确的判断。
js
t.isIdentifier(ode, {name: 'paths'})
isXxx和assertXxx看起来很像,但是功能大不一样:isXxx会返回boolean,而assertXxx则会在类型不一致时抛出异常。
@babel/template
通过@babel/types创建AST还是比较麻烦的,要一个个的创建然后组装,如果AST节点比较多的话需要写很多的代码,这个时候可以使用@babel/template包来批量创建。
这个包的api有下面这些:
js
const ast = template(code, [opts])(args);
const ast = template.ast(code, [opts]);
const ast = template.program(code, [opts]);
这些都是传入一段字符串,然后返回创建好的AST,却别是返回的AST粒度不一样:
- template.ast返回的是整个AST。
- template.program返回的是Program根节点。
- template.expression返回创建的expression的AST
- template.statements返回创建的statement数组的AST
@babel/generator
AST转换完之后就要打印成目标代码字符串,通过@babel/generator包的generator api
js
function (ast: Object, opts: Object, code: string): {code, map}
第一个参数是要打印的AST。
第二个参数是options,指定打印的一些细节,比如通过comments指定是否包含注释,通过minified指定是否包含空白字符。
第三个参数当多个文件合并打印的时候需要用到。
options中常用的是sourceMaps,开启了这个选项才会生成soucemap
js
import generate from "@babel/generator";
const {code, map} = generate(ast, {sourceMaps: true})
@babel/code-frame
babel的报错一半都会直接打印错误代码的位置,而且还能高亮,我们打印错误信息的时候也可以用,就是@babel/code-frame这个包。
js
const result = codeFrameColumns(rawLines, location, {
//...
})
options可以设置highlighted(是否高亮)、message(展示错误信息)。
比如:
js
const {codeFrameColumns} = require("@babel/colde-frame");
try{
throw new Error('xx错误')
} catch(err) {
console.error(codeFrameColumns(`const name = chuze`, {
start: {line: 1, column: 14}
}, {
highlightCode: true,
message: err.message
}))
}
打印出来的错误信息如下: 这种控制台打印代码格式的功能就叫做code frame
@babel/core
前面讲了@babel/parser、@babel/travers、@babel/generator、@babel/types、@babel/template等包,bable的功能就是通过这些包来实现的。
babel基于这些包来实现编译、插件、预设等功能的包就是@babel/core。
这个包的功能就是完成整个编译流程,从源码到目标代码,生成sourecmap。实现plugin和preset的调用。
api也有好几个:
js
transformSync(code, options); // => { code, map, ast }
transformFileSync(filename, options); // => { code, map, ast }
transformFromAstSync(
parsedAst,
sourceCode,
options
); // => { code, map, ast }
比如这三个 transformXxx 的 api 分别是从源代码、源代码文件、源代码 AST 开始处理,最终生成目标代码和 sourcemap。
options主要配置plugins和presets,指定具体要做什么转换。
这些api也同样提供了异步的版本,异步的进行编译,返回一个promise:
js
transformAsync("code();", options).then(result => {})
transformFileAsync("filename.js", options).then((result) => {})
transformAstAsync(parsedAst, sourceCode, options).then((result) => {})
注意:不带sync、async的api已经被标记过时了,也就是transfromXxx这些,后续会删掉,不建议用,直接用transformXxxSync和transformXxxAsync。也就是明确是同步还是异步。
@babel/core支持plugin和preset,一般我们配置的都是对象的格式,其实也有一个api来创建,也就是createConfigItem:
js
createConfigItem(value, options)
除了这些包之外,也可以安装@types/babel__xx的包来增加ts的提示,比如@types/babel_parser、@types/babel_travers等