Skip to content

快来享受 AST 转换的乐趣

有用的链接

var 替换成 let

假设有如下源代码:

js
var foo = 234;

我们想将它转换为如下的代码:

js
let foo = 234;

对比转换前后的两张 ast 的截图:

发现只有 kind 的值从 "var" 变成了 "let"。

所以我们只需遍历 ast,当遇到 VariableDeclaration 时,替换 king 的值就可以了。

js
const parser = require("@babel/parser")
const traverse = require("@babel/traverse")
const generate = require("@babel/generator")
const code = `
var foo = 234;
`
const ast = parser.parse(code)
traverse.default(ast, {
  VariableDeclaration(path) {
    if (path.node.kind === 'var') {
      path.node.kind = 'let'
    }
  }
})
const outputCode = generate.default(ast)
console.log(outputCode.code)

根据情况,将 var 替换成 let 或 const

js
const parser = require("@babel/parser")
const traverse = require("@babel/traverse")
const generate = require("@babel/generator")
const code = `
var foo = 234;
var bar = 123;
foo = 250;
console.log(foo, bar);
`
const ast = parser.parse(code)
// 先找到所有赋值语句
// 把所有将会被重新赋值的变量存到一个数组中
// 这些变量在声明时需要使用关键字 let
const letSet = new Set()
traverse.default(ast, {
  AssignmentExpression(path) {
    letSet.add(path.node.left.name)
  }
})
traverse.default(ast, {
  VariableDeclaration(path) {
    const Identifier = path.node.declarations[0].id.name
    if (letSet.has(Identifier)) {
      path.node.kind = 'let'
    } else {
      path.node.kind = 'const'
    }
  }
})
const outputCode = generate.default(ast)
console.log(outputCode.code)

箭头函数转换

js
const parser = require("@babel/parser")
const traverse = require("@babel/traverse")
const generate = require("@babel/generator")
const t = require("@babel/types")
const code = `
const sum = (a, b) => a + b;
`
const ast = parser.parse(code)
traverse.default(ast, {
  ArrowFunctionExpression(path) {
    const funcExpress = t.functionExpression(
        null, // Identifier 函数变量名
        path.node.params,
        t.blockStatement([
          t.returnStatement(path.node.body)
        ])
    )
    path.replaceWith(funcExpress)
  }
})
const outputCode = generate.default(ast)
console.log(outputCode.code)

tree-shaking

js
const parser = require("@babel/parser")
const traverse = require("@babel/traverse")
const generate = require("@babel/generator")
const code = `
const add = (a, b) => a + b;
function subtract(a, b) { return a - b }
const num1 = 1;
const num2 = 10;
const num3 = 100;
// 表达式 1
add(num1, num2);
// 表示式 2
num2 + num3
`
const ast = parser.parse(code)
// 首先找出表示式中用到的所有变量
const usedId = new Set()
traverse.default(ast, {
  ExpressionStatement(path) {
    path.traverse({
      Identifier(path) {
        usedId.add(path.node.name)
      }
    })
  }
})
traverse.default(ast, {
  VariableDeclaration(path) {
    for(let declaration of path.node.declarations) {
      if (!usedId.has(declaration.id.name)) {
        path.remove()
      }
    }
  },
  FunctionDeclaration(path) {
    if (!usedId.has(path.node.id.name)) {
      path.remove()
    }
  },
})
const outputCode = generate.default(ast)
console.log(outputCode.code)

babel-import-plugin

js
const parser = require("@babel/parser")
const traverse = require("@babel/traverse")
const generate = require("@babel/generator")
const t = require("@babel/types")
const code = `
import { flatten } from 'lodash'
flatten([1, [2, [3, [4]], 5]])
`
const ast = parser.parse(code, {
  sourceType: "module"
})
traverse.default(ast, {
  ImportDeclaration(path){
    if (t.isImportSpecifier(path.node.specifiers[0])) {
      const importName = path.node.specifiers[0].local.name
      const sourceName = path.node.source.value
      const declation = t.importDeclaration(
        [t.importDefaultSpecifier(t.identifier(importName))],
        t.stringLiteral(`${sourceName}/${importName}`)
      )
      path.replaceWith(declation)
    }
  }
})
const outputCode = generate.default(ast)
console.log(outputCode.code)

Released under the MIT License.