了解一下Babel、AST和依赖分析
Babel
Babel做了什么?
- parse: 把代码code变成AST
- traverse: 遍历AST进行修改
- generate: 根据AST重新生成代码
通过将let
改成var
来了解一下这个过程。
运行node -r ts-node/register --inspect-brk xxx.ts
命令来通过浏览器控制台调试代码,以查看生成的ast的结构。
import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import generate from "@babel/generator"
const code = `let a = 'let'; let b = 2`;
const ast = parse(code, { sourceType: 'module' });
traverse(ast, {
enter: item => {
if(item.node.type === 'VariableDeclaration'){
if(item.node.kind === 'let'){
item.node.kind = 'var'
}
}
}
})
const result = generate(ast, {}, code);
打印出如下结果,是不是有那么点味道了? 哈哈哈。。。
var a = 'let';
var b = 2;
使用Babel的transformFromAstSync自动将代码转换为ES5
通过配置presets: ['@babel/preset-env']
可以将ES2015-2020的代码转换成ES5的代码。改写一下代码
const result = babel.transformFromAstSync(ast, code, {
presets: ['@babel/preset-env']
})
运行得到结果
"use strict";
var a = 'let';
var b = 2;
依赖分析
创建三个js文件,index.js引入a和b,a和b互相引用。
/xxx/index.js
import a from './a.js'
import b from './b.js'
console.log(a.getB())
console.log(b.getA())
/xxx/a.js
import b from './b.js'
const a = {
value: 'a',
getB: () => b.value + ' from a.js'
}
export default a
xxx/b.js
import a from './a.js'
const b = {
value: 'b',
getA: () => a.value + ' from b.js'
}
export default b
编写文件分析依赖
import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import { readFileSync } from 'fs'
import { resolve, relative, dirname } from 'path';
// 得到xxx当前目录的绝对路径 如F:\xxx\yyy\xxx
const projectRoot = resolve(__dirname, 'xxx')
// 声明依赖的类型
type DepRelation = { [key: string]: { deps: string[], code: string } }
// 初始化一个空的 depRelation,用于收集依赖
const depRelation: DepRelation = {}
// 分析依赖
collectCodeAndDeps(resolve(projectRoot, 'index.js'))
function collectCodeAndDeps(filepath: string) {
const key = getProjectPath(filepath);
if (Object.keys(depRelation).includes(key)) {
console.warn(`duplicated dependency: ${key}`) // 注意,重复依赖不一定是循环依赖
return
}
// 获取文件内容,将内容放至 depRelation
const code = readFileSync(filepath).toString()
// 初始化 depRelation[key]
depRelation[key] = { deps: [], code: code }
// 将代码转为 AST
const ast = parse(code, { sourceType: 'module' })
traverse(ast, {
enter: path => {
if (path.node.type === 'ImportDeclaration') {
// path.node.source.value 往往是一个相对路径,如 ./a.js,需要先把它转为一个绝对路径
const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)
// 然后转为项目路径
const depProjectPath = getProjectPath(depAbsolutePath)
// 把依赖写进 depRelation
depRelation[key].deps.push(depProjectPath)
collectCodeAndDeps(depAbsolutePath)
}
}
})
}
// 获取文件相对于根目录projectRoot的相对路径 如index.js
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
运行以上代码我们的依赖分析就出来了
{
'index.js': {
deps: [ 'a.js', 'b.js' ],
code: "import a from './a.js'\r\n" +
"import b from './b.js'\r\n" +
'console.log(a.value + b.value)\r\n'
},
...
}