Babel-handbook插件

/post/babel-plugin article cover image

Babel作为source to source的转换编辑器(transpiler)转换过程主要分为三步:

  • parse: 通过parser把源码转换成抽象语法树(AST),词法分析根据规则拆分成各个token再通过语法分析将token递归组装成可用的AST
  • transform: 通过相应visitor函数遍历AST调用各种transform插件对AST进行操作修改
  • generate: 把转换后的AST生成对应结构的字符串并拼接还会并生成sourcemap

抽象语法树ast

ast节点组成

处理过程中每一步都涉及到创建或是操作抽象语法树,babel使用基于ESTree标准的ast

js
function square(n) {
	return n * n;
}

解析后的ast如下:

json
{
  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"
        }
      }
    }]
  }
}

每个层级都有相同的结构:

json
{
  type: "FunctionDeclaration",
  id: {...},
  params: [...],
  body: {...}
}
json
{
  type: "Identifier",
  name: ...
}
json
{
  type: "BinaryExpression",
  operator: ...,
  left: {...},
  right: {...}
}

ast节点类型

节点类型包括:

  • Literals
  • Programs
  • Functions
  • Statements
  • Declarations
  • Misc
  • Expressions
  • Template Literals
  • Patterns
  • Patterns
  • Classes
  • Modules

具体构成及说明文档位于 @babel/parser ast specification

ast节点属性

json
{
  type: ...,
  start: 0,
  end: 38,
  loc: {
    start: {
      line: 1,
      column: 0
    },
    end: {
      line: 3,
      column: 1
    }
  },
  ...
}

<Callout type="info"> <p><strong>js parser历史:</strong></p> <p>SpiderMonkey的AST标准 esprima ,后来形成了estree标准</p> <p>accorn也是estree标准但是比esprima更快而且支持插件对语法进行扩展</p> <p>eslint的 espree 源自esprima但是espree2.0基于accron重新实现也适用accorn的插件机制扩展语法</p> <p>babel parser(babylon)在accorn的基础上对AST节点和属性做了 扩展 </p> </Callout>

转换过程

语法解析

通过@babel/parser将源码解析(词法分析、语法分析)为ast,具体 options 配置项决定产出形式

js
require('@babel/parser').parse("soucecode", {
  sourceType: 'module',
  plugins: [
    'jsx'
  ]
})

ast遍历

通过@babel/traverse进行遍历ast节点进行增删改,在遍历过程中通过访问者模式(visitor)对指定的 节点类型 例如:FunctionDeclaration进行访问,同时可使用@babel/types提供的工具函数进行节点判断及生成

ts
interface traverse {
  ast: AST
  visitor: (path, state) => {}
}
js
traverse(ast, {
  FunctionDeclaration: {
    enter(path, state) {}, // 进入节点时调用
    exit(path, state) {} // 离开节点时调用
  }
})

// 只指定一个函数那就是enter阶段调用
traverse(ast, {
  FunctionDeclaration(path, state) {} // 进入节点时调用
})

// 多个ast类型节点处理,通过"|"
// 通过别名指定离开各种 Declaration 节点时调用
traverse(ast, {
  'FunctionDeclaration|VariableDeclaration'(path, state) {}
})

<Callout type="info"> <p>path记录了节点间相互关系的路径,同时提供了很多 属性和方法 方便遍历操作</p> <p>path.scope包含了js中能生成作用域(模块、函数、块)等所形成的作用域链</p> <p>state保存了在传递到对应节点是的 options和file以及共享数据 </p> </Callout>

转换后生成

使用@babel/generatorast生成代码字符串

ts
function (ast: Object, opts: Object, code: string): {code, map}
js
import generate from "@babel/generator";

const { code, map } = generate(ast, { sourceMaps: true })

同时可使用@babel/code-frame在生成过程中打印报错代码位置

js
import { codeFrameColumns } from "@babel/code-frame";

const rawLines = `class Foo {
  constructor()
}`;
const location = { start: { line: 2, column: 16 } };

const result = codeFrameColumns(rawLines, location, {
  /* options */
});

console.log(result);

<Callout type="info"> <p>@babel/generator通过对应AST 生成方法 进行生成</p> <p>sourcemap通常用于从目标代码映射到源码位置,具体创建、消费、及源码节点格式可以查看 文档 </p> </Callout>

to-be-continued