Babel的API

Babel的API有哪些?

babel的编译流程分三步:parsetransformgenerate,每一步都暴露了一些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整体的编译流程,并用pluginpreset

我们主要学习的就是@babel/parse@babel/traverse@babel/generator@babel/types@babel/template五个包的api的使用。

@babel/parser

@babel/parser是基于acorn实现的,扩展了很多语法,可以支持es nextjsxtypescriptflow等语法的解析。

@babel/parser默认只能parse js代码,jsxflowtypescript这些类型代码的解析需要用到对应的插件才可以。

@babel/parser提供了两个api,分别是:parseparseExpression。这两个都是把源码转化为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从源码的哪一行开始parse
  • errorRecovery出错时是否记录错误,并继续往下parse
  • tokens 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等

相关推荐
aoi3 个月前
Babel 示例插件:处理 AST 替换变量、箭头函数
javascript·babel
文艺理科生5 个月前
Webpack项目构建入门:babel的核心角色
前端·webpack·babel
阿镇吃橙子6 个月前
由浅入深 ——Vite工具链学习总结
vue.js·vite·babel
总之就是非常可爱6 个月前
提升前端开发效率:利用 Babel 实现 JavaScript 文件的定制化修改
前端·babel
月下点灯6 个月前
小白也能看懂的AST抽象语法树+babel插件开发教程
前端·javascript·babel
let_code6 个月前
Babel
前端·babel
ichimaru_xx6 个月前
node前端工具实战-svg引入整理工具
node.js·babel
每天写一个BUG7 个月前
简单玩一玩 Babel
前端·babel·前端工程化
yuansucai7 个月前
LocatorJS接入
babel
喜欢踢足球的老罗7 个月前
一个H5页面中直接使用React的示例与说明
前端·react.js·前端框架·babel