最近想研究react小程序代码的,后来感觉跨度有些大,因为平时也会写一些vue的代码,而且vue小程序更接近一些,所以还是先做了一个vue小程序的PoC。可是这些都不是重点啊,重点是在这一过程中学习并使用了babylonAST。因为也是第一次接触,所以想写点笔记记录一下,也希望能给大家一点参考。

  代码是写出来的,一定一定多写多练,所以我这里还是以实例代码为主,涉及到的点也是在vue小程序中用到的,或者是转换的基础。可是也会有很多超出此范围的知识点,我们这里就先不做具体讨论啦,这里给出了一些参考资料,大家可以参考下。

涉及到的参考资料:

  1. AST explorer: https://astexplorer.net/ 神器,阅读和书写AST操作全靠它
  2. Babel Plugin Handbook https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md 从这里找到了一些API
  3. Babylon和babel-traverse详解 https://github.com/xtx1130/blog/issues/7 其实也称不上详解,不过看到的比较早,算是篇启蒙文章吧
  4. Babel types https://babeljs.io/docs/core-packages/babel-types/ 生成代码时会大量用到

AST explorer查看代码

  首先打开 https://astexplorer.net/ ,直接在代码里输入1,查看基本效果。

图1

  我们要做的只是代码的转换,这里只需关心program.body部分即可(是否可以操作tokens来更改代码,还没有研究)。

  如上图,程序代码里只有一个表达式ExpressionStatement。点击+展开,可以看到内部的细节,如下图:

图2 Literal

  内部结构只有一个Literal,非常简单。更复杂的代码,我们在后面再来解释。

AST 的CRUD(Create-Retrieve-Update-Delete)

1. Create

  实际上,Create是个相对复杂的操作,通常会结合RetrieveUpdate使用。可以结合实际需要,选择阅读顺序。

  首先,构造一个空的node工程,后续会基于该项目一步步拓展。

npm install babylon @babel/types @babel/generator @babel/traverse

测试代码

const babylon = require('babylon')
const t = require('@babel/types')
const generate = require('@babel/generator').default
const traverse = require('@babel/traverse').default

const code = ''
const ast = babylon.parse(code)
// manipulate ast
const output = generate(ast, {}, code)  
console.log('Input \n', code)
console.log('Output \n', output.code)

因为这里是空的code,所以InputOutput都没有输出,只是搭一个代码结构。

构造一个1的代码

  根据上面的两个图,只有1代码由一个ExpressionStatement内嵌一个Literal组成。直接代码如下,可以参考下注释。

const code = ''
const ast = babylon.parse(code)

// 生成literal
const literal = t.numericLiteral(1)
// 生成expressionStatement
const exp = t.expressionStatement(literal)  
// 将表达式放入body中
ast.program.body.push(exp)

const output = generate(ast, {}, code)
console.log('Input \n', code)
console.log('Output \n', output.code)

运行输出

Input

Output
 1;

  这个例子中首先用到了t(babel-types)。可以在 https://babeljs.io/docs/core-packages/babel-types/ 查看文档。t里面有类型判断和生成实例等方法。
  如t.expressionStatement(literal)就是生成一个ExpressionStatement,这个函数要用到的参数在文档中(https://babeljs.io/docs/core-packages/babel-types/#expressionstatement) 有描述,可是文档真心不好看,大家还是根据AST explorer中查找对应的方法,然后在文档看个参数就好了:

图t.expressionStatement

构造const a = 1的代码

  上述1的代码过于简单,我们来生成const a = 1的代码。将const a = 1输入到AST explorer中,查看语法树信息如下图:

图const a = 1

  在这段代码中涉及了VariableDeclaration, VariableDeclarator, Identifier, Literal几个babel-typesLiteral中使用的是数字类型NumericLiteral。这时就可以分别查看文档了,比如:VariableDeclaration

图VariableDeclaration

  这个t.variableDeclaration有两个参数kinddeclarations,第二个参数是个数组。

  根据语法树,一层层的生成代码如下:

const code = ''
const ast = babylon.parse(code)

// 生成 VariableDeclarator
const id = t.identifier('a')
const literal = t.numericLiteral(1)
const declarator = t.variableDeclarator(id, literal)

// 生成 VariableDeclaration
const declaration = t.variableDeclaration('const', [declarator])

// 将表达式放入body中
ast.program.body.push(declaration)

const output = generate(ast, {}, code)
console.log('Input \n', code)
console.log('Output \n', output.code)

执行结果如下:

Input

Output
 const a = 1;

Create总结

  根据AST explorer可以完美生成代码,常见的异常是参数没有填对,特别是数组什么的,一定要注意。多结合API文档和从小的代码片段做起能够规避这类错误。

  最后,生成一个稍复杂一点的代码。

function add(a, b) {
  return a + b
}

AST树如下

图function add

  感兴趣的同学可以先尝试根据语法树提示写一写,再看下面的对照代码,如果上面看懂了其实写这个真心不是很难了。

const code = ''
const ast = babylon.parse(code)

// BinaryExpression a + b
const binaryExp = t.binaryExpression('+', t.identifier('a'), t.identifier('b'))
const returnStatement = t.returnStatement(binaryExp)

// function body
const fnBody = t.blockStatement([returnStatement])
const params = [t.identifier('a'), t.identifier('b')]

const fnDeclaraton = t.functionDeclaration(t.identifier('add'), params, fnBody)
ast.program.body.push(fnDeclaraton)

const output = generate(ast, {}, code)
console.log('Input \n', code)
console.log('Output \n', output.code)

  以上,就是ASTCreate的介绍,想进一步学习的接着看后面几篇文章哦