https://astexplorer.net/ 查看AST。
1、抽象语法树 AST
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中,这个处理过程中的每一步都涉及到创建或是操作抽象语法树,亦称 AST。
function square(n) {return n * n;}// 转换成下面的样子{type: "FunctionDeclaration",id: {type: "Identifier",name: "square"},params: [{type: "Identifier",name: "n"}],body: {type: "BlockStatement",body: [{type: "ReturnStatement",argument: {type: "BinaryExpression",operator: "*",left: {type: "Identifier",name: "n"},right: {type: "Identifier",name: "n"}}}]}}
每一层都有相同的结构:
{type: "FunctionDeclaration",id: {...},params: [...],body: {...}}{type: "Identifier",name: ...}{type: "BinaryExpression",operator: ...,left: {...},right: {...}}
这样的每一层结构也被叫做 节点(Node)。 一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。
字符串形式的 type 字段表示节点的类型(如: “FunctionDeclaration”,”Identifier”,或 “BinaryExpression”)。 每一种类型的节点定义了一些附加属性用来进一步描述该节点类型。
Babel 还为每个节点额外生成了一些属性,用于描述该节点在原始代码中的位置。比如 start end
2、Babel 的处理步骤
Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。
2.1 解析
解析步骤接收代码并输出 AST。
- 词法分析:
- 词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。
- 你可以把令牌看作是一个扁平的语法片段数组:n * n
- 转换成tokens是这样的:
[
{ type: { … }, value: “n”, start: 0, end: 1, loc: { … } },
{ type: { … }, value: “*”, start: 2, end: 3, loc: { … } },
{ type: { … }, value: “n”, start: 4, end: 5, loc: { … } },
…
]
- 语法分析
- 语法分析阶段会把一个令牌流转换成 AST 的形式。 这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操作。
2.2 转换
转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程 同时也是插件将要介入工作的部分。
2.3 生成
代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。
代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。
3、简单写一个babel插件
yarn add @babel/core
yarn add babel-template
const template = require('babel-template');const temp = template("var b = 1")module.exports = function ({types: t}) {// 插件内容return {visitor: {// 接收两个参数path, stateVariableDeclaration(path, state) {// 找到AST节点const node = path.node;// 判断节点类型 是否是变量节点, 申明方式是constif (t.isVariableDeclaration(node, {kind: "const"})) {// 将const 声明编译为letnode.kind = "let";// var b = 1 的AST节点const insertNode = temp();// 插入一行代码var b = 1path.insertBefore(insertNode);}}}}}
使用插件:
const myPlugin = require('./plugin')const babel = require('@babel/core');const content = 'const name = xiaoming';// 通过你编写的插件输出的代码const {code} = babel.transform(content, {plugins: [myPlugin]});console.log(code);// var b = 1// let name = 'xiaoming'
