Babel
原理
- parse:把代码code变成AST
- traverse:遍历AST进行修改
- generate:把AST变成代码code2
- 即 code (1) > AST (2) > AST2 (3) > code2
示例 let to var
- 手动把let变成var ```typescript 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’ })
console.log(ast)
traverse(ast, {
enter: item => {
if (item.node.type === ‘VariableDeclaration’) {
if (item.node.kind === “let”) {
item.node.kind = ‘var’
}
}
}
})
`$ node -r ts-node/register let_to_var.ts `<br /><br />或者在chrome中调试<br />`$ node -r ts-node/register --inspect-brk let_to_var.ts `
打开Chrome,F12 <br /><br /><br />
<a name="PhvvZ"></a>
## AST
- 为什么用AST
- 很难用正则表达式来替换,正则很容易把let a = 'let' 变成 var a = 'var'
- 需要识别每个单词的意思,才能做到只修改声明变量的let
- 而AST能明确告诉每个let的意思
<a name="q3bDz"></a>
## 转ES5
使用@babel/core 和 @babel/preset-env
```typescript
import { parse } from "@babel/parser"
import * as babel from "@babel/core"
const code = `let a = 'let';let b = 2;const c = 3`
const ast = parse(code, { sourceType: "module" })
const result = babel.transformFromAstSync(ast, code, {
presets: ['@babel/preset-env']
})
console.log(result.code)
文件转es5
/test.js
let a = '123'
let b = 456
const c = 789
/file_to_es5.ts
import { parse } from "@babel/parser"
import * as babel from "@babel/core"
import * as fs from 'fs'
const code = fs.readFileSync('./test.js').toString()
const ast = parse(code, { sourceType: "module" })
const result = babel.transformFromAstSync(ast, code, {
presets: ['@babel/preset-env']
})
fs.writeFileSync('./test.es5.js', result.code)
$ node -r ts-node/register file_to_es5.ts
/test.es5.js
依赖分析
- /deps_1.ts ```typescript // 请确保你的 Node 版本大于等于 14 // 请先运行 yarn 或 npm i 来安装依赖 // 然后使用 node -r ts-node/register 文件路径 来运行, // 如果需要调试,可以加一个选项 —inspect-brk,再打开 Chrome 开发者工具,点击 Node 图标即可调试 import { parse } from “@babel/parser” import traverse from “@babel/traverse” import { readFileSync } from ‘fs’ import { resolve, relative, dirname } from ‘path’;
// 设置根目录 const projectRoot = resolve(__dirname, ‘project_1’) // 类型声明 type DepRelation = { [key: string]: { deps: string[], code: string } } // 初始化一个空的 depRelation,用于收集依赖 const depRelation: DepRelation = {}
// 将入口文件的绝对路径传入函数,如 D:\demo\fixture_1\index.js collectCodeAndDeps(resolve(projectRoot, ‘index.js’))
console.log(depRelation) console.log(‘done’)
function collectCodeAndDeps(filepath: string) { const key = getProjectPath(filepath) // 文件的项目路径,如 index.js // 获取文件内容,将内容放至 depRelation const code = readFileSync(filepath).toString() // 初始化 depRelation[key] depRelation[key] = { deps: [], code: code } // 将代码转为 AST const ast = parse(code, { sourceType: ‘module’ }) // 分析文件依赖,将内容放至 depRelation 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) } } }) } // 获取文件相对于根目录的相对路径 function getProjectPath(path: string) { return relative(projectRoot, path).replace(/\/g, ‘/‘) }
- /project_1/
<br /><br />
<a name="zeQBb"></a>
## 思路
1. 调用`collectCodeAndDeps('index.js')`
1. 先把`depRelation['index.js']`初始化为`{deps:[],code:'index.js的源码'}`
1. 然后把index.js源码code变成ast
1. 遍历ast,看看import了哪些依赖,假如依赖了a.js和b.js
1. 把a.js和b.js写到`depRelation['index.js'].deps`中
1. 最终得到depRelation就收集了index.js的依赖

<a name="oEEII"></a>
## 递归分析嵌套依赖
- 三层依赖
- index→a→dir/a2→dir/dir_in_dir/a3
- index→b→dir/b2→dir/dir_in_dir/b3
- 关键代码
- `collectCodeAndDeps(depAbsolutePath)`
- /deps_2.ts
```typescript
// 请确保你的 Node 版本大于等于 14
// 请先运行 yarn 或 npm i 来安装依赖
// 然后使用 node -r ts-node/register 文件路径 来运行,
// 如果需要调试,可以加一个选项 --inspect-brk,再打开 Chrome 开发者工具,点击 Node 图标即可调试
import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import { readFileSync } from 'fs'
import { resolve, relative, dirname } from 'path';
// 设置根目录
const projectRoot = resolve(__dirname, 'project_2')
// 类型声明
type DepRelation = { [key: string]: { deps: string[], code: string } }
// 初始化一个空的 depRelation,用于收集依赖
const depRelation: DepRelation = {}
// 将入口文件的绝对路径传入函数,如 D:\demo\fixture_1\index.js
collectCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log(depRelation)
console.log('done')
function collectCodeAndDeps(filepath: string) {
const key = getProjectPath(filepath) // 文件的项目路径,如 index.js
// 获取文件内容,将内容放至 depRelation
const code = readFileSync(filepath).toString()
// 初始化 depRelation[key]
depRelation[key] = { deps: [], code: code }
// 将代码转为 AST
const ast = parse(code, { sourceType: 'module' })
// 分析文件依赖,将内容放至 depRelation
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)
}
}
})
}
// 获取文件相对于根目录的相对路径
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, '/')
}
思路
循环依赖
- index→a→b
- index→b→a
- 调用栈溢出
- index→a→b→a→b→a→b→a→b→a→b→a……
关键代码
if(Object.keys(depRelation).includes(key)){
console.warn(`duplicated dependency: ${key}`) // 注意,重复依赖不一定是循环依赖
return
}
/deps_4.ts ```typescript // 请确保你的 Node 版本大于等于 14 // 请先运行 yarn 或 npm i 来安装依赖 // 然后使用 node -r ts-node/register 文件路径 来运行, // 如果需要调试,可以加一个选项 —inspect-brk,再打开 Chrome 开发者工具,点击 Node 图标即可调试 import { parse } from “@babel/parser” import traverse from “@babel/traverse” import { readFileSync } from ‘fs’ import { resolve, relative, dirname } from ‘path’;
// 设置根目录 const projectRoot = resolve(__dirname, ‘project_4’) // 类型声明 type DepRelation = { [key: string]: { deps: string[], code: string } } // 初始化一个空的 depRelation,用于收集依赖 const depRelation: DepRelation = {}
// 将入口文件的绝对路径传入函数,如 D:\demo\fixture_1\index.js collectCodeAndDeps(resolve(projectRoot, ‘index.js’))
console.log(depRelation) console.log(‘done’)
function collectCodeAndDeps(filepath: string) {
const key = getProjectPath(filepath) // 文件的项目路径,如 index.js
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’ })
// 分析文件依赖,将内容放至 depRelation
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)
}
}
})
}
// 获取文件相对于根目录的相对路径
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\/g, ‘/‘)
}

- 思路
- 一旦发现key已经在keys中,就return
- 这样分析过程就不会a→b→a→b→a……,而是a→b→return
- 只需要分析依赖,不需要执行代码
- 由于分析不需要执行代码,这就叫做静态分析
- 如果执行代码就会发现依然出现循环
- 结论
- 模块间可以循环依赖
- 但是不能有逻辑漏洞
- 合理的循环引用
```typescript
import a from './a.js'
import b from './b.js'
console.log(a.getB())
console.log(b.getA())
import b from './b.js'
const a = {
value: 'a',
getB: () => b.value + ' from a.js'
}
export default a
import a from './a.js'
const b = {
value: 'b',
getA: () => a.value + ' from b.js'
}
export default b
最好还是不要用循环依赖