解析 webpack 核心——Babel 原理
Babel 做了什么,怎么做的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | 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);
 
  | 
 
运行node -r ts-node/register --inspect-brk let_to_var.ts


最终结果:
1 2 3
   |  var a = "let"; var b = 2;
 
  | 
 
为什么非得使用 AST
能不能自动把代码转为 ES5Code 并单独文件输出?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   |  let a = "let"; let b = 2; const c = 3; var set = new Set([1, 2, 3]); console.log(set);
 
  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" });
  babel   .transformFromAstAsync(ast, code, {     presets: [                            ["@babel/preset-env", { useBuiltIns: "usage", corejs: 2 }],     ],   })   .then(({ code }) => {     fs.writeFileSync("./test.es5.js", code);   });
 
  | 
 
执行node -r ts-node/register file_to_es5.ts生成文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   |  "use strict";
  require("core-js/modules/web.dom.iterable");
  require("core-js/modules/es6.array.iterator");
  require("core-js/modules/es6.object.to-string");
  require("core-js/modules/es6.string.iterator");
  require("core-js/modules/es6.set");
  var a = "let"; var b = 2; var c = 3; var set = new Set([1, 2, 3]); console.log(set);
 
  | 
 
注意:Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。 例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。 如果想使用这些新的对象和方法,则需要为当前环境提供一个 polyfill
具体:yarn add babel-polyfill core-js@2
Babel 还做了什么?
分析 JS 文件的依赖关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
   | 
 
 
 
  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 } }
  const depRelation: DepRelation = {}
 
  collectCodeAndDeps(resolve(projectRoot, 'index.js'))
  console.log(depRelation) console.log('done')
  function collectCodeAndDeps(filepath: string) {   const key = getProjectPath(filepath)       const code = readFileSync(filepath).toString()      depRelation[key] = { deps: [], code: code }      const ast = parse(code, { sourceType: 'module' })      traverse(ast, {     enter: path => {       if (path.node.type === 'ImportDeclaration') {                  const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)                  const depProjectPath = getProjectPath(depAbsolutePath)                  depRelation[key].deps.push(depProjectPath)       }     }   }) }
 
  function getProjectPath(path: string) {   return relative(projectRoot, path).replace(/\\/g, '/') }
 
  const a = {   value: 1 } export default a
 
  const b = {   value: 2 } export default b
 
  import a from './a.js' import b from './b.js'
  console.log(a.value + b.value)
 
  | 
 
执行node -r ts-node/register deps_1.ts结果输出如下:
1 2 3 4 5 6 7 8 9
   | {   'index.js': {     deps: [ 'a.js', 'b.js' ],     code: "import a from './a.js'\r\n" +       "import b from './b.js'\r\n" +       '\r\n' +       'console.log(a.value + b.value)'   } }
  | 
 
- 嵌套依赖
project_2
目录结构如图:

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
   | 
 
 
 
  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 } }
  const depRelation: DepRelation = {}
 
  collectCodeAndDeps(resolve(projectRoot, 'index.js'))
  console.log(depRelation) console.log('done')
  function collectCodeAndDeps(filepath: string) {   const key = getProjectPath(filepath)       const code = readFileSync(filepath).toString()      depRelation[key] = { deps: [], code: code }      const ast = parse(code, { sourceType: 'module' })      traverse(ast, {     enter: path => {       if (path.node.type === 'ImportDeclaration') {                  const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)                  const depProjectPath = getProjectPath(depAbsolutePath)                  depRelation[key].deps.push(depProjectPath)
                   collectCodeAndDeps(depAbsolutePath)       }     }   }) }
 
  function getProjectPath(path: string) {   return relative(projectRoot, path).replace(/\\/g, '/') }
 
  import a from './a.js' import b from './b.js'
  console.log(a.value + b.value)
 
  import a2 from './dir/a2.js' const a = {   value: 1,   value2: a2 } export default a
 
  import b2 from './dir/b2.js'
  const b = {   value: 2,   value2: b2 } export default b
 
  import a3 from './dir_in_dir/a3.js' const a2 = {   value: 12,   value3: a3 } export default a2
 
  import b3 from './dir_in_dir/b3.js'
  const b2 = {   value: 22,   value3: b3 } export default b2
 
  const a3 = {   value: 123 } export default a3
 
  const b3 = {   value: 123 } export default b3
 
 
  | 
 
执行node -r ts-node/register deps_2.ts结果输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   | {   'index.js': {     deps: [ 'a.js', 'b.js' ],     code: "import a from './a.js'\r\n" +       "import b from './b.js'\r\n" +       '\r\n' +       'console.log(a.value + b.value)'   },   'a.js': {     deps: [ 'dir/a2.js' ],     code: "import a2 from './dir/a2.js'\r\n" +       'const a = {\r\n' +       '  value: 1,\r\n' +       '  value2: a2\r\n' +       '}\r\n' +       'export default a'   },   'dir/a2.js': {     deps: [ 'dir/dir_in_dir/a3.js' ],     code: "import a3 from './dir_in_dir/a3.js'\r\n" +       'const a2 = {\r\n' +       '  value: 12,\r\n' +       '  value3: a3\r\n' +       '}\r\n' +       'export default a2'   },   'dir/dir_in_dir/a3.js': {     deps: [],     code: 'const a3 = {\r\n  value: 123\r\n}\r\nexport default a3'   },   'b.js': {     deps: [ 'dir/b2.js' ],     code: "import b2 from './dir/b2.js'\r\n" +       '\r\n' +       'const b = {\r\n' +       '  value: 2,\r\n' +       '  value2: b2\r\n' +       '}\r\n' +       'export default b'   },   'dir/b2.js': {     deps: [ 'dir/dir_in_dir/b3.js' ],     code: "import b3 from './dir_in_dir/b3.js'\r\n" +       '\r\n' +       'const b2 = {\r\n' +       '  value: 22,\r\n' +       '  value3: b3\r\n' +       '}\r\n' +       'export default b2'   },   'dir/dir_in_dir/b3.js': {     deps: [],     code: 'const b3 = {\r\n  value: 123\r\n}\r\nexport default b3'   } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  import a from './a.js' import b from './b.js'
  console.log(a.value + b.value)
 
  import b from './b.js'
  const a = {   value: b.value + 1 } export default a
 
  import a from './a.js'
  const b = {   value: a.value + 1 } export default b
 
 
 
  | 
 
执行node -r ts-node/register deps_3.ts报错RangeError: Maximum call stack size exceeded说明出现了循环嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
   | 
 
 
 
 
 
  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_3");
  type DepRelation = { [key: string]: { deps: string[], code: string } };
  const depRelation: DepRelation = {};
 
  collectCodeAndDeps(resolve(projectRoot, "index.js"));
  console.log(depRelation); console.log("done");
  function collectCodeAndDeps(filepath: string) {   const key = getProjectPath(filepath); 
       if (Object.keys(depRelation).includes(key)) {     console.warn(`duplicated dependency: ${key}`);      return;   }      const code = readFileSync(filepath).toString();      depRelation[key] = { deps: [], code: code };      const ast = parse(code, { sourceType: "module" });      traverse(ast, {     enter: (path) => {       if (path.node.type === "ImportDeclaration") {                  const depAbsolutePath = resolve(           dirname(filepath),           path.node.source.value         );                  const depProjectPath = getProjectPath(depAbsolutePath);                  depRelation[key].deps.push(depProjectPath);
                   collectCodeAndDeps(depAbsolutePath);       }     },   }); }
 
  function getProjectPath(path: string) {   return relative(projectRoot, path).replace(/\\/g, "/"); }
 
  | 
 
执行node -r ts-node/register deps_4.ts结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | duplicated dependency: a.js duplicated dependency: b.js {   'index.js': {     deps: [ 'a.js', 'b.js' ],     code: "import a from './a.js'\r\n" +       "import b from './b.js'\r\n" +       '\r\n' +       'console.log(a.value + b.value)'   },   'a.js': {     deps: [ 'b.js' ],     code: "import b from './b.js'\r\n" +       '\r\n' +       'const a = {\r\n' +       '  value: b.value + 1\r\n' +       '}\r\n' +       'export default a'   },   'b.js': {     deps: [ 'a.js' ],     code: "import a from './a.js'\r\n" +       '\r\n' +       'const b = {\r\n' +       '  value: a.value + 1\r\n' +       '}\r\n' +       'export default b'   } }
   | 
 
执行node project_4/index.js会报错
- 循环依赖 有的循环依赖可以正常运行
project_5 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   |  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
 
 
 
  | 
 
执行node -r ts-node/register deps_5.ts结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | duplicated dependency: a.js duplicated dependency: b.js {   'index.js': {     deps: [ 'a.js', 'b.js' ],     code: "import a from './a.js'\r\n" +       "import b from './b.js'\r\n" +       '\r\n' +       'console.log(a.getB())\r\n' +       'console.log(b.getA())'   },   'a.js': {     deps: [ 'b.js' ],     code: "import b from './b.js'\r\n" +       '\r\n' +       'const a = {\r\n' +       "  value: 'a',\r\n" +       "  getB: () => b.value + ' from a.js'\r\n" +       '}\r\n' +       'export default a'   },   'b.js': {     deps: [ 'a.js' ],     code: "import a from './a.js'\r\n" +       '\r\n' +       'const b = {\r\n' +       "  value: 'b',\r\n" +       "  getA: () => a.value + ' from b.js'\r\n" +       '}\r\n' +       'export default b'   } }
   | 
 
执行node project_5/index.js不会报错
总结
Babel 原理(AST)
- parse 把代码 code 变成 AST
 
- traverse 遍历 AST 进行修改
 
- generate 把 AST 变成代码 code2
 
工具
babel 可以把高级代码翻译为 ES5
 
@babel/parser
 
@babel/traverse
 
@babel/generator
 
@babel/core 包含前三者
 
@babel/preset-env 内置很多规则
 
代码技巧
循环依赖
有的循环依赖可以正常执行
 
有的循环依赖不可以
 
但都可以做静态分析
 
源码参考:https://github.com/Matthrews/webpack-core