前端的终极工具-AST(webpack核心原理)

993 阅读3分钟

前言

之前看到一篇文章说过: 2020年后,真正高级的前端一定是得懂AST的,具备工具链开发能力的程序员。

为什么我称其为前端的终极工具?

AST的奇妙之处就在于,它可以让我们实现程序员的终极梦想:用代码来写代码,直接通过代码(字符串)层面的操作,理论上我们可以通过操作AST做到任何事情

AST虚拟语法树

首先我们需要知道什么是AST
AST简单的说就是: 使用对象的形式来表达一行代码

代码转AST工具网站: AST explorer

我们来看一个句简单的AST

import name from './index.js'
 {
        "type": "ImportDeclaration", // 语句的类型 
        "start": 0,
        "end": 31, 
        "loc": {}, 
        "specifiers": [...], // 保存了{name} 部分
        "importKind": "value",
        "source": {...}, // 保存了'./index.js'部分 
        "assertions": []
      }

我们可以通过这句的AST下的source找到import的文件路径 其中的specifiers保存了import的内容

image.png

AST语句的分类

AST可以分为多种类型

ImportDeclaration // import语句
VariableDeclaration // 变量声明语句 
IfStatement // if/else语句
FunctionDeclaration // 函数声明
Identifier // 变量名/函数名等
...... 还有很多

案例实现 手写一个去除console.log()的脚本

这里我们使用前端人都很熟悉的babel来做AST的解析,当然也可以用市面上的其他工具

现在我们创建如下文件 需要去除文件下的console语句

//test.js
const name = '张三'
console.log(name)
// removeConsole.js
const fs = require('fs')
const parser = require('@babel/parser')// 代码转AST工具
const traverse = require('@babel/traverse').default //AST遍历器

// 读取test中的代码  使用babel/parser转为AST
const code = fs.readFileSync('./test.js', 'utf-8')
const ast = parser.parse(code, {
               sourceType: 'module'
            })
// 使用babel/traverse遍历AST         
// 二号参数为AST的visitor 传入对应的钩子函数,每当遍历到对应的语句即执行这个钩子函数
// 我们这里的console语句是一句'ExpressionStatement' 我们使用这个钩子读取它
// 钩子函数接收path参数  path.node即为当前语句的AST  
traverse(ast,{
    ExpressionStatement:(path,state)=>{
        const node = path.node //
    }
})

我们可以通过AST Explorer看到 这句AST的 node.expression.callee.object.name 就是console。 我们可以通过这个字段来判断这句是不是console语句

image.png

判断为console语句,删除代码

// removeConsole.js

//如果name为console,则删除这行代码
traverse(ast,{
    ExpressionStatement:(path,state)=>{
        const name = path.node.expression.callee.object.name
        if(name==='console'){
               path.remove() // 通过path.remove()删除这串AST
        }
    }
})

还有很多操作AST语句的方法 详见@babel/types · Babel 中文网 (babeljs.cn)

将删除console的AST重新转为JS代码

// removeConsole.js

// 使用babel/generator 将AST重新转为JS代码
const generate = require('@babel/generator').default;

const jsCode = generate(ast)
console.log(jsCode)

此时的jsCode就只剩下const name = '张三' 了

结尾

大家可以展开想象 我们可以通过操作AST来实现几乎所有的操作

比如修改所有的var为let,

自己实现一个ESLint

通过脚本写下一串代码等等

实际上 我们熟知的很多工具 如webpack,esbuild等等都是通过操作AST来实现的许多功能。

当然要学好AST,除了熟悉AST的API调用之外,还需要大量的算法知识,这也是为什么AST作为前端的高级工具的原因。
本人感悟: 其实前端玩到最后,基本上就是操作字符串,而AST为我们提供了一种更稳定操作JS字符串的功能,当然如果你是正则大神也可以)。