babel的原理

  • parse:把代码code变成AST
  • traverse:遍历AST进行修改
  • generate:把AST变成代码code2
    即 code -> ast -> ast2 -> code2

示例:手动把let变成var

  1. import { parse } from "@babel/parser" // 把代码`code`变成`AST`
  2. import traverse from "@babel/traverse" // 遍历AST
  3. import generate from "@babel/generator" // 把`AST`变成代码`code2`
  4. const code = `let a = 'let'; let b = 2`
  5. const ast = parse(code, { sourceType: 'module' })
  6. traverse(ast, {
  7. enter: item => {
  8. if(item.node.type === 'VariableDeclaration'){
  9. if(item.node.kind === 'let'){
  10. item.node.kind = 'var'
  11. }
  12. }
  13. }
  14. })
  15. const result = generate(ast, {}, code)
  16. console.log(result.code)

在Chrome运行TS代码:node -r ts-node/register —inspect-brk xxx.ts

先打印看看ast是什么:
「深入Webpack」babel 与 AST - 图1
从ast对象的属性中,可以看到这个program就是上面code这段代码的程序,程序的body里有两个节点:

  1. // const code = `let a = 'let'; let b = 2`
  2. let a = 'let' // 节点1
  3. let b = 2 // 节点2

再看一下这些节点都包含哪些属性,重点关注这三个属性

  1. ...
  2. // 声明的节点信息
  3. declarations: [Node]
  4. // 变量声明的关键字是let
  5. kind: "let"
  6. // 类型,说明是一个变量声明
  7. type: "VariableDeclaration"
  8. ...
  9. // declarations是一个节点数组,因为可以同时声明多个节点
  10. // let a = 'let'
  11. // name为a的标识符,对应初始值value是'let',类型是字符串
  12. declarations: [
  13. 0: Node
  14. {
  15. id: {
  16. ...,
  17. name: "a",
  18. type: "Identifier"
  19. },
  20. init: {
  21. ...,
  22. value: "let",
  23. type: "StringLiteral"
  24. }
  25. },
  26. length: 1
  27. ]

所以说AST就是用来将源代码字符串,表示成一个树形结构

  1. // traverse用来遍历AST,可以对AST进行修改
  2. traverse(ast, {
  3. // 每进入一个节点,就执行item=>{}函数
  4. enter: item => {
  5. ...
  6. }
  7. })

这样看来,将let变成var就水到渠成了

  1. traverse(ast, {
  2. enter: item => {
  3. if(item.node.type === 'VariableDeclaration'){ // 如果是变量声明
  4. if(item.node.kind === 'let'){ // 如果是let声明
  5. item.node.kind = 'var' // 将let改成var
  6. }
  7. }
  8. }
  9. })

再使用generate函数将新的AST翻译成code

  1. const result = generate(ast, {}, code) // 传入修改后的ast,还有原始code
  2. console.log(result.code) // 得到新的code

运行后就得到了

var a = 'let';
var b = 2;

为什么要用AST

  • 你很难用正则表达式去替换,正则很容易把let a = 'let'变成var a = 'var'
  • 你需要识别每个单词的意思,才能做到只修改用于变量声明的let
  • 而AST能明确告诉你每个let的意思

下篇《如何将代码转为ES5》