babel AST
babel插件手册
ast 检查工具: astexplorer.net
完整AST定义
Literal
- NumbericLiteral
- StringLiteral
- BooleanLiteral
- RegExpLiteral
- TemplateLiteral eg: `ll`
- BiginLiteral eg: 1.2324n
- NullLiteral
Identifier
变量名、属性名、参数名等各种声明和引用的名字
Statement
可以独立执行的单位,每一条独立执行的代码都是语句 特点: 语句末尾一般会加;或者换行
// BreakStatement
break;
// ContunueStatement
continue;
// ReturnStatement
return;
// DebuggerStatement
debugger;
// ThrowStatement
throw Error();
// BlockStatement
{}
// TryStatement
try {} catch(e) {} finally{}
// ForinStatement
for (let key in obj) {}
// ForStatement
for (let i = 0;i < 10;i ++) {}
// WhileStatement
while (true) {}
// DoWhileStatement
do {} while (true)
// SwitchStatement
switch (v){case 1: break;default:;}
// LabeledStatement
label: console.log();
// WithStatement
with (a){}
Declaration
声明语句是一种特殊的语句,它执行的逻辑是在作用域内声明一个变量、函数、class、import、export 等
// VariableDeclaration
const a = 1;
// FunctionDeclaration
function b(){}
// ClassDeclaration
class C {}
// ImportDeclaration
import d from 'e';
// ExportDefaultDeclaration
export default e = 1;
// ExportNameDeclaration
export {e};
// ExportAllDeclaration
export * from 'e';
Expression
表达式,执行完有返回值,有的表达式可以独立作为语句执行,会包裹一层 ExpressionStatement
// ArrayExpression
[1,2,3]
// AssignmentExpression
a = 1
// BinaryExpression 二元表达式
1 + 2;
// UnaryExpression 一元表达式
-1;
// FunctionExpression
function(){};
// ArrowFunctionExpression
() => {};
// ClassExpression
class{};
// Identifier
a;
// ThisExpression
this;
// Super
super;
// BindExpression
a::b;
Class
class整个内容是ClassBody
属性: ClassProperty
方法: ClassMethod(通过kind区分是constructor还是method)
// ClassDectaration
class Guang extends Person{
// ClassProperty
name = 'guang';
// ClassMethod (kind='constructor')
constructor() {}
// // ClassMethod(kind='method')
eat() {}
}
import
ImportDeclaration 节点中的specifiers属性不同
// ImportDeclaration
// d--> ImportDefaultSpecifier
import d from 'e';
// ImportDeclaration
// c,d --> ImportSpicifier
import {c, d} from 'c';
// ImportDeclaration
// * as b --> ImportNamespaceSpcifier
import * as b from 'b';
export
// ExportNamedDeclaration
// b, d --> ExportSpecifier
export { b, d};
// ExportDefaultDeclaration
export default a;
//ExportAllDeclaration
export * from 'c';
Program & Directive
program 是代表整个程序的节点,它有 body 属性代表程序体,存放 statement 数组,就是具体执行的语句的集合。还有 directives 属性,存放Directive 节点,比如"use strict" 这种指令会使用 Directive 节点表示。
/*
* Program
* body / \ directives
* / \
* callExpression Directive
* callExpression
*/
"use strict"
console.log(1);
console.log(2);
File & Comment
babel的AST最外层是File,它有Program,comments, tokens等属性,分别存放 Program 程序体、注释、token 等
- commentBlock
/*
* ll
*/
- CommentLine
// ll
Babel api
@babel/parse
- parse AST根节点是File
- parseExpression 返回的根节点是Expression
function parse(input: string, options?: ParserOptions): File
function parseExpression(input: string, options?: ParserOptions): Expression
@bable/traverse
// parent 指定要遍历的 AST 节点
// opts 指定 visitor 函数。babel 会在遍历 parent 对应的 AST 时调用相应的 visitor 函数。
function traverse(parent, opts)
visitor 对象的 value 是对象或者函数:
- 如果 value 为函数,那么就相当于是 enter 时调用的函数。
- 如果 value 为对象,则可以明确指定 enter 或者 exit 时的处理函数。
visitor: {
Identifier (path, state) {},
StringLiteral: {
enter (path, state) {},
exit (path, state) {}
}
}
path
path {
// 属性:
// 指向当前AST节点
node
// 父 AST 节点
parent
// 父 AST 节点的 path
parentPath
// 获取当前节点的作用域信息
scope
// 可以通过 path.hub.file 拿到最外层 File 对象, path.hub.getScope 拿到最外层作用域
hub
// 当前 AST 节点所在的父节点属性的属性值
container
// 当前 AST 节点所在父节点属性的属性名或所在数组的下标
key
// 当前 AST 节点所在父节点属性的属性值为数组时 listkey 为该属性名,否则为 undefined
listKey
// 方法
// 获取某个属性的 path
get(key)
// 设置某个属性的值
set(key, node)
// 判断节点是否在数组中,如果 container 为数组,也就是有 listkey 的时候,返回 true
inList()
// 获取某个下标的兄弟节点
getSibling(key)
// 获取下一个兄弟节点
getNextSibling()
// 获取上一个兄弟节点
getPrevSibling()
// 获取之前的所有兄弟节点
getAllPrevSiblings()
// 获取之后的所有兄弟节点
getAllNextSiblings()
//判断当前节点是否是某个类型,可以传入属性和属性值进一步判断,比如path.isIdentifier({name: 'a'})
isXxx(opts)
// 同 isXxx,但是不返回布尔值,而是抛出异常
assertXxx(opts)
// 在之前插入节点,可以是单个节点或者节点数组
insertBefore
// 在之后插入节点,可以是单个节点或者节点数组
insertAfter(nodes)
// 用某个节点替换当前节点
replaceWith(replacement)
// 用多个节点替换当前节点
replaceWithMultiple(nodes)
// 解析源码成 AST,然后替换当前节点
replaceWithSourceString(replacement)
// 删除当前节点
remove()
// 遍历当前节点的子节点,传入 visitor 和 state(state 是不同节点间传递数据的方式)
traverse(visitor, state)
// 跳过当前节点的子节点的遍历
skip()
// 结束所有遍历
stop()
}
path.scope 作用域信息
path.scope {
// 当前作用域内声明的所有变量
bindings
// 生成作用域的 block
block
// 父作用域的信息
parent
// 父作用域对应的块节点
parentBlock
// 生成作用域的节点对应的 path
path
// 所有 binding 的引用对应的 path
references
// 打印作用域链的所有 binding 到控制台
dump()
// 父级作用域的 block
parentBlock()
//从当前作用域到根作用域的所有 binding 的合并
getAllBindings()
// 查找某个 binding,从当前作用域一直查找到根作用域
getBinding(name)
从当前作用域查找 binding,可以指定是否算上全局变量,默认是 false
hasBinding(name, noGlobals)
// 从当前作用域查找 binding
getOwnBinding(name)
// 查找某个 binding,从父作用域查到根作用域,不包括当前作用域。可以通过 noGlobals 参数指定是否算上全局变量(比如console,不需要声明就可用),默认是 false
parentHasBinding(name, noGlobals)
// 删除某个 binding
removeBinding(name)
// 把当前作用域中的某个 binding 移动到其他作用域
moveBindingTo(name, scope)
// 生成作用域内唯一的名字,根据 name 添加下划线,比如 name 为 a,会尝试生成 _a,如果被占用就会生成 __a,直到生成没有被使用的名字
generateUid(name)
}
scope.block
通过 path.scope.block 来拿到所在的块对应的节点,通过 path.scope.parentBlock 拿到父作用域对应的块节点。
通过 path.scope 拿到作用域的信息,通过 patsh.scope.parent 拿到父作用域的信息
// scope block节点,
export type Scopable =
| BlockStatement
| CatchClause
| DoWhileStatement
| ForInStatement
| ForStatement
| FunctionDeclaration
| FunctionExpression
| Program
| ObjectMethod
| SwitchStatement
| WhileStatement
| ArrowFunctionExpression
| ClassExpression
| ClassDeclaration
| ForOfStatement
| ClassMethod
| ClassPrivateMethod
| StaticBlock
| TSModuleBlock;
scope.bindings、scope.references
每一个 binding 都有 kind,这代表绑定的类型:
- var、let、const 分别代表 var、let、const 形式声明的变量
- param 代表参数的声明
- module 代表 import 的变量的声明
const a = 1;
// 对应的scope.bindings
bindings: {
a: {
// 代表变量是否被修改过
constant: true,
// 如果被修改了,可以通过 binding.constViolations 拿到所有修改的语句的 path
constantViolations: [],
// 标识符的 AST、
identifier: {type: 'Identifier', ...}
kind:'const',
// 整个声明语句的 AST
path: {node,...}
// 声明的变量是否被引用
referenced: false
// 如果被引用了,通过 binding.referencePaths 拿到所有引用的语句的 path
referencePaths: [],
references: 0,
scope: ...
}
}
node 链、block 链、scope 链
从某一个节点出发有 3 条链
- path 和 path.parent 串联起来的 AST node 链,
- path.scope 和 path.scope.parent 串联起来的作用域 scope 链,
- path.scope.block 和 path.scope.parentBlock 串联起来的块 AST block 链。
state
遍历过程中在不同节点之间传递数据的机制
state {
// 文件级别的信息,也可以从 path.hub.file 中拿
file
// 插件的配置项
opts
}
AST 的别名
// 单个 AST 类型
FunctionDeclaration(path, state) {},
// 多个 AST 类型
'FunctionDeclaration|VariableDeclaration'(path, state) {}
// AST 类型别名
Declaration(){}
SourceMap
格式
{
// source map 版本号
version: 3,
// 转换后的文件名
file: "outName.js",
// 转换前文件所在目录。如果输入和输出文件在同一个目录下
sourceRoot: "",
// 转换前的文件。该项是一个数组,可能存在多个文件合并成一个文件
sources: ["inputName1.js", "inputName2.js"],
// 转换前的所有变量名和属性名
names: ["src", "maps", "are", "fun"],
//记录位置信息的字符串
mappings: "XXXXX, XXXX, XXXXXX"
}
mappings
- 第一位,表示这个位置在转换后的代码的第几列。
- 第二位,表示这个位置属于 sources 属性中的哪一个文件。
- 第三位,表示这个位置属于转换前代码的第几行。
- 第四位,表示这个位置属于转换前代码的第几列。
- 第五位,表示这个位置属于 names 属性中的哪一个变量。不一定有
source-map
用于生成和解析 sourcemap
source-map 暴露了 SourceMapConsumer、SourceMapGenerator、SourceNode 3个类,分别用于消费 sourcemap、生成 sourcemap、创建源码节点。
==生成流程==:
- 创建一个 SourceMapGenerator 对象
- 通过 addMapping 方法添加一个映射
- 通过 toString 转为 sourcemap 字符串
@babel/types
创建 AST 和判断 AST 的类型
@babel/template
批量创建AST节点
@babel/generator
AST 转换完之后就要打印成目标代码字符串
@babel/code-frame
打印错误信息
@babel/core
基于@babel/core完成整个编译流程,从源码到目标代码,生成 sourcemap。
generate
babel 内置功能
- compat-table 提供每个特性在不同环境中的版本支持
- electron-to-chromium
- @babel/compat-data
browerslist
- browerslist query 提供了一个从 query (查询表达式) 到对应环境版本的转换。
@babel/plugin-transform-runtime
把直接注入全局的方式改成模块化引入。
实践
babel的基本实践,git地址github.com/liling0726/…
总结
这边文章基本是根据babel通关秘籍小册整理,以及很多掘金大佬的文章作为参考,对于babel的理解,目前还在理论阶段,后续会完善下实践部分