解析 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