AST
AST(Abstract Syntax Tree)是根据一套规范对源码进行结构描述,如操作Js源码的ESTree标准。
例如 const name = "lyla";的const描述为
interface VariableDeclaration {
kind: "const";
}
Acorn
Acorn 一个JS parser,将Js源码解析为Ast。
-
基本使用
const ast = acorn.parse(code, {ecmaVersion: 2020}); -
通过plugin的方式解析jsx语法acorn-jsx、以及其他插件如acorn-stage3或acorn-bigint等。
const extendAcorn = acorn.Parser.extend( require('acorn-stage3'), require('acorn-jsx')(), ) const ast = extendAcorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });问题:经测试未支持装饰器Decorators
Unexpected character '@' -
遍历 AST nodes
- acorn-walk
import * as acornWalk from 'acorn-walk'; acornWalk.full(ast, visit) function visit(node) { switch(node.type) { case 'JSXText': case 'Literal': { const { start, end, value } = node; // ... break; } case 'TemplateLiteral': { const { start, end } = node; const templateContent = code.slice(start, end); // ... break; } } }
问题:当使用acorn-jsx插件时,这里会遇到 baseVisitor[type] is not a function,不支持JSXElement等type,可以自定义扩展类型做第三个参数解决:
acornWalk.full(ast, visit, { ...acornWalk.base, JSXElement: () => {} })
-
提供了针对AST类型系统的各类方法,其中包含AST节点遍历机制
import * as astTypes from 'ast-types'; astTypes.visit(ast, { visitJSXText(path) { const { node: { start, end, value } } = path; // ... this.traverse(path); // 继续向下遍历子节点 }, visitTemplateLiteral(path) { const { node: { start, end } } = path; const templateContent = code.slice(start, end); // ... this.traverse(path); // 继续向下遍历子节点 } })
总结:Acorn对于解析js有完整的方案,只是对一些ES语法还不支持(仅支持TC39 stage4提案,其他可通过plugin的方式支持)。
Babel
Babel是一个JS Compiler,将ES6+的语法转换为向后兼容的版本,支持JSX语法的转换。
例如:input ?? "Hello world" 会转换为 input != null ? input : "Hello world"
如何做到的?其实是通过操作AST实现: input string -> @babel/parser parser -> AST -> transformer[s] -> AST -> @babel/generator -> output string
- 解析为AST
可以使用@babel/core的transformSync方法,也可以直接使用@babel/parser
- @babel/core
import * as babel from '@babel/core'; const { ast } = babel.transformSync(code, { ast: true, presets: ['@babel/preset-react'], plugins: [['@babel/plugin-proposal-decorators', { version: '2021-12' }]], }) - @babel/parse
import * as babelParser from '@babel/parser'; const ast = babelParser.parse(code, { sourceType: "module", plugins: ['jsx'] })
-
遍历 AST nodes
使用@babel/traverse遍历AST nodes,结合@babel/types检查AST nodes类型。
import * as babelTraverse from '@babel/traverse'; import * as babelTypes from '@babel/types'; babelTraverse.default(ast, { StringLiteral({ node }) { const { start, end, value } = node as babelTypes.StringLiteral; // ... }, TemplateLiteral({ node }) { const { start, end } = node as babelTypes.TemplateLiteral; const templateContent = code.slice(start, end); // ... }, JSXElement({ node }) { const { children } = node as babelTypes.JSXElement; children.forEach(child => { if (babelTypes.isJSXText(child)) { const { value, start, end } = child; // ... } }) } })