Babel

把es6的代码转化成es5

原理

  1. parse:把代码code变成AST
  2. traverse:遍历AST进行修改
  3. generate:把AST变成代码code2
  4. 即 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’ } } } })

  1. `$ 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 `
  2. 打开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)
  3. <a name="PhvvZ"></a>
  4. ## AST
  5. - 为什么用AST
  6. - 很难用正则表达式来替换,正则很容易把let a = 'let' 变成 var a = 'var'
  7. - 需要识别每个单词的意思,才能做到只修改声明变量的let
  8. - AST能明确告诉每个let的意思
  9. <a name="q3bDz"></a>
  10. ## 转ES5
  11. 使用@babel/core @babel/preset-env
  12. ```typescript
  13. import { parse } from "@babel/parser"
  14. import * as babel from "@babel/core"
  15. const code = `let a = 'let';let b = 2;const c = 3`
  16. const ast = parse(code, { sourceType: "module" })
  17. const result = babel.transformFromAstSync(ast, code, {
  18. presets: ['@babel/preset-env']
  19. })
  20. console.log(result.code)

image.png

文件转es5

  • /test.js

    1. let a = '123'
    2. let b = 456
    3. const c = 789
  • /file_to_es5.ts

    1. import { parse } from "@babel/parser"
    2. import * as babel from "@babel/core"
    3. import * as fs from 'fs'
    4. const code = fs.readFileSync('./test.js').toString()
    5. const ast = parse(code, { sourceType: "module" })
    6. const result = babel.transformFromAstSync(ast, code, {
    7. presets: ['@babel/preset-env']
    8. })
    9. fs.writeFileSync('./test.es5.js', result.code)

    $ node -r ts-node/register file_to_es5.ts

  • /test.es5.js

image.png

依赖分析

  • /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, ‘/‘) }

  1. - /project_1/
  2. ![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)
  3. <a name="zeQBb"></a>
  4. ## 思路
  5. 1. 调用`collectCodeAndDeps('index.js')`
  6. 1. 先把`depRelation['index.js']`初始化为`{deps:[],code:'index.js的源码'}`
  7. 1. 然后把index.js源码code变成ast
  8. 1. 遍历ast,看看import了哪些依赖,假如依赖了a.jsb.js
  9. 1. a.jsb.js写到`depRelation['index.js'].deps`
  10. 1. 最终得到depRelation就收集了index.js的依赖
  11. ![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)
  12. <a name="oEEII"></a>
  13. ## 递归分析嵌套依赖
  14. - 三层依赖
  15. - indexadir/a2dir/dir_in_dir/a3
  16. - indexbdir/b2dir/dir_in_dir/b3
  17. - 关键代码
  18. - `collectCodeAndDeps(depAbsolutePath)`
  19. - /deps_2.ts
  20. ```typescript
  21. // 请确保你的 Node 版本大于等于 14
  22. // 请先运行 yarn 或 npm i 来安装依赖
  23. // 然后使用 node -r ts-node/register 文件路径 来运行,
  24. // 如果需要调试,可以加一个选项 --inspect-brk,再打开 Chrome 开发者工具,点击 Node 图标即可调试
  25. import { parse } from "@babel/parser"
  26. import traverse from "@babel/traverse"
  27. import { readFileSync } from 'fs'
  28. import { resolve, relative, dirname } from 'path';
  29. // 设置根目录
  30. const projectRoot = resolve(__dirname, 'project_2')
  31. // 类型声明
  32. type DepRelation = { [key: string]: { deps: string[], code: string } }
  33. // 初始化一个空的 depRelation,用于收集依赖
  34. const depRelation: DepRelation = {}
  35. // 将入口文件的绝对路径传入函数,如 D:\demo\fixture_1\index.js
  36. collectCodeAndDeps(resolve(projectRoot, 'index.js'))
  37. console.log(depRelation)
  38. console.log('done')
  39. function collectCodeAndDeps(filepath: string) {
  40. const key = getProjectPath(filepath) // 文件的项目路径,如 index.js
  41. // 获取文件内容,将内容放至 depRelation
  42. const code = readFileSync(filepath).toString()
  43. // 初始化 depRelation[key]
  44. depRelation[key] = { deps: [], code: code }
  45. // 将代码转为 AST
  46. const ast = parse(code, { sourceType: 'module' })
  47. // 分析文件依赖,将内容放至 depRelation
  48. traverse(ast, {
  49. enter: path => {
  50. if (path.node.type === 'ImportDeclaration') {
  51. // path.node.source.value 往往是一个相对路径,如 ./a.js,需要先把它转为一个绝对路径
  52. const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)
  53. // 然后转为项目路径
  54. const depProjectPath = getProjectPath(depAbsolutePath)
  55. // 把依赖写进 depRelation
  56. depRelation[key].deps.push(depProjectPath)
  57. collectCodeAndDeps(depAbsolutePath)
  58. }
  59. }
  60. })
  61. }
  62. // 获取文件相对于根目录的相对路径
  63. function getProjectPath(path: string) {
  64. return relative(projectRoot, path).replace(/\\/g, '/')
  65. }
  • 思路

    • collectionCodeAndDeps缩写为collect
    • 调用collect('index.js')
    • 发现依赖 './a.js' 于是调用 collect('a.js')
    • 发现依赖'./dir/a2.js' 于是调用 collect('dir/a2.js')
    • 发现依赖'./dir_in_dir/a3.js' 于是调用 collect('dir/dir_in_dir/a3.js')
    • 没有更多依赖,a.js这条线路结束,发现下一个依赖’b.js’
    • 这就是递归

      静态分析循环依赖

  • 循环依赖

    • index→a→b
    • index→b→a
  • 调用栈溢出
    • index→a→b→a→b→a→b→a→b→a→b→a……
    • image.png
  • 关键代码

    1. if(Object.keys(depRelation).includes(key)){
    2. console.warn(`duplicated dependency: ${key}`) // 注意,重复依赖不一定是循环依赖
    3. return
    4. }
  • /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, ‘/‘) }

  1. ![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)
  2. - 思路
  3. - 一旦发现key已经在keys中,就return
  4. - 这样分析过程就不会ababa……,而是abreturn
  5. - 只需要分析依赖,不需要执行代码
  6. - 由于分析不需要执行代码,这就叫做静态分析
  7. - 如果执行代码就会发现依然出现循环
  8. - 结论
  9. - 模块间可以循环依赖
  10. - 但是不能有逻辑漏洞
  11. - 合理的循环引用
  12. ```typescript
  13. import a from './a.js'
  14. import b from './b.js'
  15. console.log(a.getB())
  16. console.log(b.getA())
  1. import b from './b.js'
  2. const a = {
  3. value: 'a',
  4. getB: () => b.value + ' from a.js'
  5. }
  6. export default a
  1. import a from './a.js'
  2. const b = {
  3. value: 'b',
  4. getA: () => a.value + ' from b.js'
  5. }
  6. export default b

最好还是不要用循环依赖