bbael基本用法

852 阅读7分钟

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、创建源码节点。
==生成流程==:

  1. 创建一个 SourceMapGenerator 对象
  2. 通过 addMapping 方法添加一个映射
  3. 通过 toString 转为 sourcemap 字符串

@babel/types

创建 AST 和判断 AST 的类型

@babel/template

批量创建AST节点

@babel/generator

AST 转换完之后就要打印成目标代码字符串

@babel/code-frame

打印错误信息

@babel/core

基于@babel/core完成整个编译流程,从源码到目标代码,生成 sourcemap。

generate

babel 内置功能

browerslist

  • browerslist query 提供了一个从 query (查询表达式) 到对应环境版本的转换。

@babel/plugin-transform-runtime

把直接注入全局的方式改成模块化引入。

实践

babel的基本实践,git地址github.com/liling0726/…

总结

这边文章基本是根据babel通关秘籍小册整理,以及很多掘金大佬的文章作为参考,对于babel的理解,目前还在理论阶段,后续会完善下实践部分

参考文献