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 />![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629084279756-c68edb7c-ddda-4061-9ee9-86bccbbbb1d6.png#clientId=u08d58213-f6a4-4&from=paste&height=417&id=ucdea327c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=436&originWidth=373&originalType=binary&ratio=1&size=30211&status=done&style=none&taskId=u8f3b3d9d-da31-4173-aab8-dbd57cdb612&width=356.5)<br />或者在chrome中调试<br />`$ node -r ts-node/register --inspect-brk let_to_var.ts `
打开Chrome,F12 <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629083340594-cd99ad81-5139-4d2d-98e9-d422d77f2d06.png#clientId=u08d58213-f6a4-4&from=paste&height=84&id=u9a6a289f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=95&originWidth=295&originalType=binary&ratio=1&size=4794&status=done&style=none&taskId=ub798300f-b4af-42d3-8ac4-288b3ed7f15&width=261.5)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629083604556-c7367350-52d1-4271-b2df-f2e0083fc323.png#clientId=u08d58213-f6a4-4&from=paste&height=521&id=u198522c3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1042&originWidth=1920&originalType=binary&ratio=1&size=142855&status=done&style=none&taskId=u663fc469-7f0c-48fb-a3eb-25c08396792&width=960)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629083796953-8908150f-580b-4a76-a306-1337ac6ebd81.png#clientId=u08d58213-f6a4-4&from=paste&height=420&id=ue71a0bca&margin=%5Bobject%20Object%5D&name=image.png&originHeight=839&originWidth=899&originalType=binary&ratio=1&size=64473&status=done&style=none&taskId=u1e91a412-1fd9-439b-adc6-a7f7732749d&width=449.5)
<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/
![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629098376494-d0fec428-b33e-4b17-8304-a94ba6f310cc.png#clientId=u08d58213-f6a4-4&from=paste&height=97&id=u381beba2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=144&originWidth=542&originalType=binary&ratio=1&size=14375&status=done&style=none&taskId=ue01aacc5-aaf4-4f07-98a1-269b239f714&width=365)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629098434121-346ad6fe-b277-4c23-9998-d7f5a2b5aea2.png#clientId=u08d58213-f6a4-4&from=paste&height=140&id=ud675c69b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=154&originWidth=398&originalType=binary&ratio=1&size=9989&status=done&style=none&taskId=u59b2f2d4-7194-4711-a50a-f5228100686&width=363)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629098411371-eb45fd66-74ca-42aa-b4ce-11884abf433a.png#clientId=u08d58213-f6a4-4&from=paste&height=136&id=u2d8edb70&margin=%5Bobject%20Object%5D&name=image.png&originHeight=165&originWidth=440&originalType=binary&ratio=1&size=10266&status=done&style=none&taskId=u41e4e829-edee-430c-84da-b07146d60ee&width=363)
<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的依赖
![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629098856791-49e4f36f-7b4b-4ca0-8adc-b9b9e4aa2400.png#clientId=u08d58213-f6a4-4&from=paste&height=229&id=uba2d8b22&margin=%5Bobject%20Object%5D&name=image.png&originHeight=280&originWidth=600&originalType=binary&ratio=1&size=40964&status=done&style=none&taskId=ua58e86cb-c951-4a5d-9101-5da8cfca1c9&width=490)
<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, ‘/‘)
}
![image.png](https://cdn.nlark.com/yuque/0/2021/png/734550/1629100511080-c6c0dcbe-7d12-4a53-b7ba-be016044a61f.png#clientId=u08d58213-f6a4-4&from=paste&height=604&id=ucec230fe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=656&originWidth=589&originalType=binary&ratio=1&size=74229&status=done&style=none&taskId=uaabdd4f0-a1c4-4b72-af83-1b93f39f0d2&width=542.5)
- 思路
- 一旦发现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
最好还是不要用循环依赖