[{"data":1,"prerenderedAt":678},["ShallowReactive",2],{"content:\u002F2022\u002Fast-and-babel-and-dependencies":3,"surround:\u002F2022\u002Fast-and-babel-and-dependencies":667},{"id":4,"title":5,"body":6,"categories":635,"date":637,"description":69,"draft":638,"extension":639,"image":640,"meta":641,"navigation":643,"path":644,"permalink":640,"published":640,"readingTime":645,"recommend":640,"references":640,"seo":650,"sitemap":651,"stem":662,"tags":663,"type":665,"updated":640,"__hash__":666},"content\u002Fposts\u002F2022\u002Fast-and-babel-and-dependencies.md","AST、Babel、依赖",{"type":7,"value":8,"toc":602},"minimark",[9,14,19,23,46,52,56,62,72,79,86,97,102,106,112,131,135,146,157,163,166,171,178,182,185,188,194,197,203,214,220,225,229,232,235,241,249,251,256,262,301,311,315,318,322,333,336,359,363,368,374,377,382,386,392,395,403,406,414,417,428,432,435,438,443,446,474,478,483,491,494,497,505,508,514,517,521,527,533,536,539,543,554,557,577,580,588,591],[10,11,13],"h2",{"id":12},"babel","Babel",[15,16,18],"h3",{"id":17},"babel-的原理","Babel 的原理",[20,21,22],"p",{},"Babel 转换 JS 代码可以分成以下三个大步骤：",[24,25,26,34,40],"ol",{},[27,28,29,33],"li",{},[30,31,32],"code",{"code":32},"parse",": 把代码 code 变成 AST",[27,35,36,39],{},[30,37,38],{"code":38},"traverse",": 遍历 AST 进行修改",[27,41,42,45],{},[30,43,44],{"code":44},"generate",": 把 AST 变成代码 code2",[20,47,48,49],{},"即，",[30,50,51],{"code":51},"code --(1)-> ast --(2)-> ast2 --(3)-> code2",[15,53,55],{"id":54},"一个简单的示例手动把-let-变成-var","一个简单的示例：手动把 let 变成 var",[20,57,58,61],{},[30,59,60],{"code":60},"let_to_var.ts"," 将 code 中的 let 全部变成 var",[63,64,70],"pre",{"className":65,"code":67,"language":68,"meta":69},[66],"language-js","import { parse } from \"@babel\u002Fparser\"\nimport traverse from \"@babel\u002Ftraverse\"\nimport generate from \"@babel\u002Fgenerator\"\n\nconst code = `let a = 'let'; let b = 2`\nconst ast = parse(code, { sourceType: 'module' })\ntraverse(ast, {\n  enter: item => {\n    if(item.node.type === 'VariableDeclaration'){\n      if(item.node.kind === 'let'){\n        item.node.kind = 'var'\n      }\n    }\n  }\n})\nconst result = generate(ast, {}, code)\nconsole.log(result.code)\n","js","",[30,71,67],{"__ignoreMap":69},[20,73,74,75,78],{},"执行命令 ",[30,76,77],{"code":77},"node -r ts-node\u002Fregister let_to_var.ts","，结果如下：",[20,80,81],{},[82,83],"img",{"alt":84,"src":85},"运行结果","\u002Fimages\u002Fast-and-babel-and-dependencies\u002F01.png",[20,87,88,89,92,93,96],{},"如果你想用 Chrome 查看 AST，可以添加 ",[30,90,91],{"code":91},"--inspect-brk"," 选项，",[30,94,95],{"code":95},"node -r ts-node\u002Fregister --inspect-brk let_to_var.ts","。",[20,98,99],{},[82,100],{"alt":69,"src":101},"\u002Fimages\u002Fast-and-babel-and-dependencies\u002F02.png",[15,103,105],{"id":104},"为什么必须要用-ast","为什么必须要用 AST",[107,108,109],"blockquote",{},[20,110,111],{},"为什么必须要用 AST 来做一层转换，直接将 code 字符串中的 let 替换为 var 不就行了吗？",[113,114,115,125,128],"ul",{},[27,116,117,118,121,122],{},"你很难用正则表达式来替换，正则很容易把",[30,119,120],{"code":120},"let a = 'let'","变成",[30,123,124],{"code":124},"var a = 'var'",[27,126,127],{},"你需要识别每个单词的意思，才能做到只修改用于声明变量的 let",[27,129,130],{},"而 AST 能明确地告诉你每个 let 的意思",[15,132,134],{"id":133},"自动把代码转为-es5","自动把代码转为 ES5",[20,136,137,138,141,142,145],{},"上面的例子中，我们是手动的把 code 中的 ",[30,139,140],{"code":140},"let"," 转换为了 ",[30,143,144],{"code":144},"var","，如何将这一操作自动化呢？",[20,147,148,149,152,153,156],{},"使用 ",[30,150,151],{"code":151},"@babel\u002Fcore"," 和 ",[30,154,155],{"code":155},"@babel\u002Fpreset-env"," 即可",[63,158,161],{"className":159,"code":160,"language":68,"meta":69},[66],"\u002F\u002F to_es5.ts\nimport { parse } from \"@babel\u002Fparser\"\nimport * as babel from \"@babel\u002Fcore\"\n\nconst code = `let a = 'let';let b = 2; const c = 3`\nconst ast = parse(code, { sourceType: 'module' })\n\u002F\u002F `babel.transformFromAstSync` 可以把 AST 变成 code2\nconst result1 = babel.transformFromAstSync(ast, code, {\n  presets: ['@babel\u002Fpreset-env']\n})\nconsole.log('\\nresult1.code\\n', result1.code)\n\n\u002F\u002F 如果图方便，可以用 babel.transformSync 直接把 code 变成 code2\nconst result2 = babel.transformSync(code, {\n  presets: ['@babel\u002Fpreset-env']\n})\nconsole.log('\\nresult2.code\\n', result2.code)\n",[30,162,160],{"__ignoreMap":69},[20,164,165],{},"运行结果如下：",[20,167,168],{},[82,169],{"alt":84,"src":170},"\u002Fimages\u002Fast-and-babel-and-dependencies\u002F03.png",[107,172,173],{},[20,174,175,177],{},[30,176,155],{"code":155}," 内置了很多转换规则，比如把所有 ES6+ 代码转换为 ES5 代码的规则",[15,179,181],{"id":180},"代码不应该是字符串而是应该放到文件中","代码不应该是字符串，而是应该放到文件中",[20,183,184],{},"上面的例子都是以字符串的形式，而在我们平时的开发中，代码都是分散在一个个的文件中的，此时就需要我们引入 Node 的文件模块。",[20,186,187],{},"在根目录下创建 test.js 文件",[63,189,192],{"className":190,"code":191,"language":68,"meta":69},[66],"\u002F\u002F test.js\nlet a = 'let'\nlet b = 2\nconst c = 3\n",[30,193,191],{"__ignoreMap":69},[20,195,196],{},"创建 file_to_es5.ts 文件",[63,198,201],{"className":199,"code":200,"language":68,"meta":69},[66],"\u002F\u002F file_to_es5.ts\nimport { parse } from \"@babel\u002Fparser\"\nimport * as babel from \"@babel\u002Fcore\"\nimport * as fs from 'fs'\n\nconst code = fs.readFileSync('.\u002Ftest.js').toString()\nconst ast = parse(code, { sourceType: 'module' })\nconst result = babel.transformFromAstSync(ast, code, {\n  presets: ['@babel\u002Fpreset-env']\n})\nfs.writeFileSync('.\u002Ftest.es5.js', result.code)\n",[30,202,200],{"__ignoreMap":69},[20,204,205,206,209,210,213],{},"执行 ",[30,207,208],{"code":208},"node -r ts-node\u002Fregister file_to_es5.ts","，就会得到 ",[30,211,212],{"code":212},"test.es5.js"," 文件",[63,215,218],{"className":216,"code":217,"language":68,"meta":69},[66],"\u002F\u002F test.es5.js\n\"use strict\";\n\nvar a = 'let';\nvar b = 2;\nvar c = 3;\n",[30,219,217],{"__ignoreMap":69},[107,221,222],{},[20,223,224],{},"如何把 .\u002Ftest.js 改为任意文件？",[15,226,228],{"id":227},"babel-除了转换-js-语法还能做啥","Babel 除了转换 JS 语法，还能做啥？",[20,230,231],{},"还可以用来分析 JS 文件的依赖关系（即答）",[20,233,234],{},"创建一个 project_1 目录，project_1 目录中有三个 JS 文件",[63,236,239],{"className":237,"code":238,"language":68,"meta":69},[66],"\u002F\u002F project_1\u002Fa.js\nconst a = {\n  value: 1,\n}\nexport default a\n\n\u002F\u002F project_1\u002Fb.js\nconst b = {\n  value: 2,\n}\nexport default b\n\n\u002F\u002F project_1\u002Findex.js\nimport a from '.\u002Fa.js'\nimport b from '.\u002Fb.js'\nconsole.log(a.value + b.value)\n",[30,240,238],{"__ignoreMap":69},[63,242,247],{"className":243,"code":245,"language":246,"meta":69},[244],"language-ts","\u002F\u002F deps_1.ts\nimport { parse } from \"@babel\u002Fparser\"\nimport traverse from \"@babel\u002Ftraverse\"\nimport { readFileSync } from 'fs'\nimport { resolve, relative, dirname } from 'path';\n\n\u002F\u002F 设置根目录\nconst projectRoot = resolve(__dirname, 'project_1')\n\u002F\u002F 类型声明\ntype DepRelation = { [key: string]: { deps: string[], code: string } }\n\u002F\u002F 初始化一个空的 depRelation，用于收集依赖\nconst depRelation: DepRelation = {}\n\n\u002F\u002F 将入口文件的绝对路径传入函数，如 D:\\demo\\fixture_1\\index.js\ncollectCodeAndDeps(resolve(projectRoot, 'index.js'))\n\nconsole.log(depRelation)\nconsole.log('done')\n\nfunction collectCodeAndDeps(filepath: string) {\n  const key = getProjectPath(filepath) \u002F\u002F 文件的项目路径，如 index.js\n  \u002F\u002F 获取文件内容，将内容放至 depRelation\n  const code = readFileSync(filepath).toString()\n  \u002F\u002F 初始化 depRelation[key]\n  depRelation[key] = { deps: [], code: code }\n  \u002F\u002F 将代码转为 AST\n  const ast = parse(code, { sourceType: 'module' }) \n  \u002F\u002F 分析文件依赖，将内容放至 depRelation\n  traverse(ast, {\n    enter: path => {\n      if (path.node.type === 'ImportDeclaration') {\n        \u002F\u002F path.node.source.value 往往是一个相对路径，如 .\u002Fa.js，需要先把它转为一个绝对路径\n        const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)\n        \u002F\u002F 然后转为项目路径\n        const depProjectPath = getProjectPath(depAbsolutePath)\n        \u002F\u002F 把依赖写进 depRelation\n        depRelation[key].deps.push(depProjectPath)\n      }\n    }\n  })\n}\n\u002F\u002F 获取文件相对于根目录的相对路径\nfunction getProjectPath(path: string) {\n  return relative(projectRoot, path).replace(\u002F\\\\\u002Fg, '\u002F')\n}\n","ts",[30,248,245],{"__ignoreMap":69},[20,250,165],{},[20,252,253],{},[82,254],{"alt":84,"src":255},"\u002Fimages\u002Fast-and-babel-and-dependencies\u002F04.png",[20,257,258,261],{},[30,259,260],{"code":260},"deps_1.ts"," 的思路",[24,263,264,271,281,288,291,298],{},[27,265,266,267,270],{},"调用 ",[30,268,269],{"code":269},"collectCodeAndDeps('index.js')","，代码中会有更多细节",[27,272,273,274,277,278],{},"先把 ",[30,275,276],{"code":276},"depRelation['index.js']"," 初始化为 ",[30,279,280],{"code":280},"{ deps: [], code: 'index.js的源码' }",[27,282,283,284,287],{},"然后把 ",[30,285,286],{"code":286},"index.js"," 源码 code 变成 AST",[27,289,290],{},"遍历 AST，看看 import 了哪些依赖，假设依赖了 a.js 和 b.js",[27,292,293,294,297],{},"把 a.js 和 b.js 写到 ",[30,295,296],{"code":296},"depRelation['index.js'].deps"," 里",[27,299,300],{},"最终得到的 depRelation 就收集了 index.js 的依赖",[107,302,303],{},[20,304,305,306,310],{},"启发：用",[307,308,309],"strong",{},"哈希表","来存储文件依赖；哈希表是数据结构中的术语，在 JS 中一个对象就可以看作一个哈希表；这是计数排序的基本操作",[15,312,314],{"id":313},"升级依赖的依赖","升级：依赖的依赖",[20,316,317],{},"如何处理更深层的嵌套关系？",[319,320,321],"h4",{"id":321},"三层依赖关系",[113,323,324,327,330],{},[27,325,326],{},"index -> a -> dir\u002Fa2 -> dir\u002Fdir_in_dir\u002Fa3",[27,328,329],{},"index -> b -> dir\u002Fb2 -> dir\u002Fdir_in_dir\u002Fb3",[27,331,332],{},"文件我已经创建好了，放在 project_2 目录里了",[319,334,335],{"id":335},"思路",[113,337,338,341,344,347,350,353,356],{},[27,339,340],{},"collectCodeAndDeps 太长了，缩写为 collect",[27,342,343],{},"调用 collect('index.js')",[27,345,346],{},"发现依赖 '.\u002Fa.js' 于是调用 collect('a.js')",[27,348,349],{},"发现依赖 '.\u002Fdir\u002Fa2.js' 于是调用 collect('dir\u002Fa2.js')",[27,351,352],{},"发现依赖 '.\u002Fdir_in_dir\u002Fa3.js' 于是调用 collect('dir\u002Fdir_in_dir\u002Fa3.js')",[27,354,355],{},"没有更多依赖了，a.js 这条线结束，发现下一个依赖 '.\u002Fb.js'",[27,357,358],{},"以此类推，其实就是递归",[319,360,362],{"id":361},"给-collect-加递归","给 collect 加递归",[20,364,365],{},[82,366],{"alt":69,"src":367},"\u002Fimages\u002Fast-and-babel-and-dependencies\u002F05.png",[20,369,370],{},[82,371],{"alt":372,"src":373},"执行结果","\u002Fimages\u002Fast-and-babel-and-dependencies\u002F06.png",[15,375,376],{"id":376},"用递归来获取嵌套依赖",[107,378,379],{},[20,380,381],{},"递归存在 call stack 溢出的风险，比如嵌套层数超过 20000 时，程序直接报错",[319,383,385],{"id":384},"再复杂一点循环依赖","再复杂一点：循环依赖",[63,387,390],{"className":388,"code":389,"language":68,"meta":69},[66],"\u002F\u002F project_3\u002Fa.js\nimport b from '.\u002Fb.js'\nconst a = {\n  value: b.value + 1,\n}\nexport default a\n\n\u002F\u002F project_3\u002Fb.js\nimport a from '.\u002Fa.js'\nconst b = {\n  value: a.value + 1,\n}\nexport default b\n\n\u002F\u002F project_3\u002Findex.js\nimport a from '.\u002Fa.js'\nimport b from '.\u002Fb.js'\nconsole.log(a.value + b.value)\n",[30,391,389],{"__ignoreMap":69},[20,393,394],{},"依赖关系（project_3）",[113,396,397,400],{},[27,398,399],{},"index -> a -> b",[27,401,402],{},"index -> b -> a",[20,404,405],{},"求值",[113,407,408,411],{},[27,409,410],{},"a.value = b.value + 1",[27,412,413],{},"b.value = a.value + 1",[20,415,416],{},"运行代码",[113,418,419,422,425],{},[27,420,421],{},"node -r ts-node\u002Fregister deps_3.ts",[27,423,424],{},"报错：调用栈 溢出了",[27,426,427],{},"为什么：分析过程 a -> b -> a -> b -> a -> b -> ... 把调用栈撑满了",[15,429,431],{"id":430},"不能循环依赖吗","不能循环依赖吗？",[20,433,434],{},"并不，我们需要一些小技巧",[319,436,437],{"id":437},"避免重复进入同一个文件",[20,439,440],{},[82,441],{"alt":69,"src":442},"\u002Fimages\u002Fast-and-babel-and-dependencies\u002F07.png",[20,444,445],{},"思路：",[113,447,448,451,454,461,467],{},[27,449,450],{},"一旦发现这个 key 已经在 keys 里了，就 return",[27,452,453],{},"这样分析过程就不是 a -> b -> a -> b -> ... 而是 a -> b -> return",[27,455,456,457,460],{},"注意我们",[307,458,459],{},"只需要分析依赖，不需要执行代码","，所以这样是可行的",[27,462,463,464],{},"由于我们的分析不需要执行代码，所以叫做",[307,465,466],{},"静态分析",[27,468,469,470,473],{},"但如果我们",[307,471,472],{},"执行代码","，就会发现还是出现了循环",[319,475,477],{"id":476},"执行-indexjs","执行 index.js",[20,479,480],{},[82,481],{"alt":69,"src":482},"\u002Fimages\u002Fast-and-babel-and-dependencies\u002F08.png",[113,484,485,488],{},[27,486,487],{},"发现报错：不能在 'a' 初始化之前访问 a",[27,489,490],{},"原因：执行过程 a -> b -> a 此处报错，因为 node 发现计算 a 的时候又要计算 a",[319,492,493],{"id":493},"结论",[20,495,496],{},"模块间可以循环依赖",[113,498,499,502],{},[27,500,501],{},"a 依赖 b，b 依赖 a",[27,503,504],{},"a 依赖 b，b 依赖 c，c 依赖 a",[20,506,507],{},"但不能有逻辑漏洞",[113,509,510,512],{},[27,511,410],{},[27,513,413],{},[20,515,516],{},"那能不能写出一个没有逻辑漏洞的循环依赖？当然可以。",[319,518,520],{"id":519},"合法的循环依赖没有逻辑漏洞","合法的循环依赖（没有逻辑漏洞）",[63,522,525],{"className":523,"code":524,"language":68,"meta":69},[66],"\u002F\u002F project_5\u002Fa.js\nimport b from '.\u002Fb.js'\nconst a = {\n  value: 'a',\n  getB: () => b.value + ' from a.js'\n}\nexport default a\n\n\u002F\u002F project_5\u002Fb.js\nimport a from '.\u002Fa.js'\nconst b = {\n  value: 'b',\n  getA: () => a.value + ' from b.js'\n}\nexport default b\n\n\u002F\u002F project_5\u002Findex.js\nimport a from '.\u002Fa.js'\nimport b from '.\u002Fb.js'\nconsole.log(a.getB())\nconsole.log(b.getA())\n",[30,526,524],{"__ignoreMap":69},[20,528,529],{},[82,530],{"alt":531,"src":532},"运行 index.js","\u002Fimages\u002Fast-and-babel-and-dependencies\u002F09.png",[20,534,535],{},"有的循环依赖有问题，有的循环依赖没有问题；所以最好别用循环依赖，以防万一",[10,537,538],{"id":538},"总结",[15,540,542],{"id":541},"ast-相关","AST 相关",[113,544,545,548,551],{},[27,546,547],{},"parse: 把代码 code 变成 AST",[27,549,550],{},"traverse: 遍历 AST 进行修改",[27,552,553],{},"generate: 把 AST 变成代码 code2",[15,555,556],{"id":556},"工具",[113,558,559,562,565,568,571,574],{},[27,560,561],{},"babel 可以把高级代码翻译为 ES5",[27,563,564],{},"@babel\u002Fparser",[27,566,567],{},"@babel\u002Ftraverse",[27,569,570],{},"@babel\u002Fgenerator",[27,572,573],{},"@babel\u002Fcore 包含前三者",[27,575,576],{},"@babel\u002Fpreset-env 内置很多规则",[15,578,579],{"id":579},"代码技巧",[113,581,582,585],{},[27,583,584],{},"使用哈希表来存储数据",[27,586,587],{},"通过检测 key 来避免重复",[15,589,590],{"id":590},"循环依赖",[113,592,593,596,599],{},[27,594,595],{},"有的循环依赖可以正常执行",[27,597,598],{},"有的循环依赖不可以",[27,600,601],{},"但都可以做静态分析",{"title":69,"searchDepth":603,"depth":603,"links":604},4,[605,629],{"id":12,"depth":606,"text":13,"children":607},2,[608,610,611,612,613,614,615,620,623],{"id":17,"depth":609,"text":18},3,{"id":54,"depth":609,"text":55},{"id":104,"depth":609,"text":105},{"id":133,"depth":609,"text":134},{"id":180,"depth":609,"text":181},{"id":227,"depth":609,"text":228},{"id":313,"depth":609,"text":314,"children":616},[617,618,619],{"id":321,"depth":603,"text":321},{"id":335,"depth":603,"text":335},{"id":361,"depth":603,"text":362},{"id":376,"depth":609,"text":376,"children":621},[622],{"id":384,"depth":603,"text":385},{"id":430,"depth":609,"text":431,"children":624},[625,626,627,628],{"id":437,"depth":603,"text":437},{"id":476,"depth":603,"text":477},{"id":493,"depth":603,"text":493},{"id":519,"depth":603,"text":520},{"id":538,"depth":606,"text":538,"children":630},[631,632,633,634],{"id":541,"depth":609,"text":542},{"id":556,"depth":609,"text":556},{"id":579,"depth":609,"text":579},{"id":590,"depth":609,"text":590},[636],"学习笔记","2022-03-15",false,"md",null,{"comments":638,"slots":642},{},true,"\u002F2022\u002Fast-and-babel-and-dependencies",{"text":646,"minutes":647,"time":648,"words":649},"10 min read",9.12,547200,1824,{"title":5,"description":69},{"loc":644,"images":652},[653,654,655,656,657,658,659,660,661],{"loc":85},{"loc":101},{"loc":170},{"loc":255},{"loc":367},{"loc":373},{"loc":442},{"loc":482},{"loc":532},"posts\u002F2022\u002Fast-and-babel-and-dependencies",[664,13],"AST","tech","n2iZU11T9s9Uj4saofUl-xAs-1TQoKv29g12UT25Tek",[668,673],{"title":669,"path":670,"stem":671,"date":672,"type":665,"children":-1},"DNS 与 TCP","\u002F2021\u002Fdns-and-tcp","posts\u002F2021\u002Fdns-and-tcp","2021-08-20",{"title":674,"path":675,"stem":676,"date":677,"type":665,"children":-1},"TypeScript 的运行与调试","\u002F2022\u002Ftypescript\u002Fts-1","posts\u002F2022\u002Ftypescript\u002Fts-1","2022-09-07",1793699839716]