源码

文件列表

1
2
3
4
5
6
├── example
│ ├── entry.js
│ ├── message.js
│ └── name.js
├── bundle.js
└── package.json

webpack打包原理

1.createAsset函数创建AST,主要使用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
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
107
108
109
110
111
112
```dependencies```传入文件的内部依赖
```code```转译成一般js的当前文件代码

通过```fs.readFileSync```读取文件,```babylon.parse()```转换为```AST```语法树

```json
// example/entry.js转译为Node
Node {
type: 'File',
start: 0,
end: 57,
loc: SourceLocation {
start: Position { line: 1, column: 0 },
end: Position { line: 3, column: 21 }
},
program: Node {
type: 'Program',
start: 0,
end: 57,
loc: SourceLocation { start: [Position], end: [Position] },
sourceType: 'module',
body: [ [Node], [Node] ],
directives: []
},
comments: [],
tokens: [
Token {
type: [KeywordTokenType],
value: 'import',
start: 0,
end: 6,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: 'message',
start: 7,
end: 14,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: 'from',
start: 15,
end: 19,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: './message.js',
start: 20,
end: 34,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: 'console',
start: 36,
end: 43,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: undefined,
start: 43,
end: 44,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: 'log',
start: 44,
end: 47,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: undefined,
start: 47,
end: 48,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: 'message',
start: 48,
end: 55,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: undefined,
start: 55,
end: 56,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: undefined,
start: 56,
end: 57,
loc: [SourceLocation]
},
Token {
type: [TokenType],
value: undefined,
start: 57,
end: 57,
loc: [SourceLocation]
}
]
}

经过traverse检索出导入的模块路径

1
2
// example/entry.js
import message from './message.js'

example/entry.js中引用了同目录下的message.js
因此

1
2
3
4
5
traverse(ast, {
ImportDeclaration: ({ node }) => {
// node即为entrt.js中导入的message.js
},
});

将获取到的依赖路径放入依赖树中

1
dependencies.push(node.source.value);

通过babel-coretransformFromAst转译成一般浏览器能识别的js代码

1
2
3
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});
1
2
3
4
5
6
7
8
9
"use strict";

var _message = require("./message.js");

var _message2 = _interopRequireDefault(_message);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

console.log(_message2.default)

最后返回需要的参数

1
2
3
4
5
6
return {
id,
fileName,
dependencies,
code,
}

全部代码

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
const fs = require('fs')
const babylon = require('babylon')
const traverse = require('babel-traverse').default
const { transformFromAst } = require('babel-core')
/**
* @function 创建AST
* @param {String} fileName
* @returns {} id, fileName, dependenciescode, code
*/
let ID = 0
function createAsset(fileName) {
const content = fs.readFileSync(fileName, 'utf-8')

const ast = babylon.parse(content, {
sourceType: 'module'
})
const dependencies = []
traverse(ast,{
ImportDeclaration: ({node}) => {
dependencies.push(node.source.value)
}
})
const id = ID++
const { code } = transformFromAst(ast, null, {
presets: ['env']
})
return {
id,
fileName,
dependencies,
code
}
}

2.创建模块依赖树

根据获取到的文件AST,遍历依赖获取AST,生成模块依赖树,并将当前模块的依赖模块以{key: relationPath: value: id}的格式存入map中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const path = require('path')

function createGraph(entry) {
const mainAsset = createAsset(entry)
const queue = [mainAsset]
for(const ast of queue) {
const dirname = path.dirname(ast.fileName)
ast.mapping = {}
ast.dependencies.forEach(relationPath => {
const absolutePath = path.join(dirname, relationPath)
const child = createAsset(absolutePath)
ast.mapping[relationPath] = child.id
queue.push(child)
})
}
return queue
}

3.

源代码

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
const fs = require("fs");
const path = require("path");
const babylon = require("babylon");
const traverse = require("babel-traverse").default;
const { transformFromAst } = require("babel-core");

let ID = 0;

function createAsset(fileName) {
const content = fs.readFileSync(fileName, "utf-8");

const ast = babylon.parse(content, {
sourceType: "module",
});
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({ node }) => {
dependencies.push(node.source.value);
},
});
const id = ID++;
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});
return {
id,
fileName,
dependencies,
code,
};
}

function createGraph(entry) {
const mainAsset = createAsset(entry);
const queue = [mainAsset];
for (const ast of queue) {
const dirname = path.dirname(ast.fileName);
ast.mapping = {};
ast.dependencies.forEach((relationPath) => {
const absolutePath = path.join(dirname, relationPath);
const child = createAsset(absolutePath);
ast.mapping[relationPath] = child.id;
queue.push(child);
});
}
return queue;
}

function bundle(graph) {
let modules = "";
graph.forEach((mod) => {
modules += `${mod.id}: [
function(require, module, exports) {
${mod.code}
},
${JSON.stringify(mod.mapping)}
],`;
});
const res = `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id]

function localRequire(relativePath) {
return require(mapping[relativePath])
}

const module = { exports: {} }

fn(localRequire, module, module.exports)

return module.exports
}
require(0)
})({${modules}})
`;
return res;
}

const graph = createGraph("./example/entry.js");
const res = bundle(graph);
console.log(res);

最后更新: 2022年10月30日 05:10