# Minipack (简易打包器实现)
参考 minipack (opens new window)
# 目录结构
├─example
│ entry.js
│ message.js
│ name.js
│
└─src
minipack.js
1
2
3
4
5
6
7
2
3
4
5
6
7
# example 代码
// entry.js
import message from "./message.js";
console.log(message);
// message.js
import { name } from "./name.js";
export default `hello ${name}!`;
// name.js
export const name = "world";
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# minipack 代码
const fs = require("fs"); // 文件读取
const path = require("path"); // 文件路径
const { parse } = require("@babel/parser"); // 解析代码生成 AST
const transformFromAst = require("@babel/core").transformFromAst; // 将 AST 解析生成代码
const traverse = require("@babel/traverse").default; // 对 AST 进行遍历
let ID = 0;
/**
* @description: 生成文件依赖信息
* @param {*} filename 文件的绝对路径
* @return {*}
*/
function createAsset(filename) {
/* 读取文件内容 */
// 字符串形式读取文件内容.
const content = fs.readFileSync(filename, "utf-8");
// 现在我们需要搞清楚文件的依赖关系,可以通过寻找文件内容中的 import 字符串,为此我们就需要一个 JavaScript 解析器
/* 生成 AST */
// JavaScript 解析器可以帮助我们来理解代码,生成一个抽象的数据结构称为 AST(抽象语法树)
// 可以在这里看看抽象语法树是什么样子
// AST Explorer (https://astexplorer.net)
//
// 一个 AST 包含了代码中的大量信息,我们可以通过它来了解代码想要做什么
const ast = parse(content, {
sourceType: "module"
});
// 这个数组用来保存当前模块依赖的其他模块的路径
const dependencies = [];
/* 遍历 AST */
// 遍历 AST 来查询当前模块依赖哪些模块,也就是检查 AST 上每个 import 语句
traverse(ast, {
// ES 模块都很简单,因为它们都是静态的,所以看到 import 语句可以直接把值作为依赖
ImportDeclaration: ({ node }) => {
dependencies.push(node.source.value);
}
});
// 用 ID 来标识当前模块
const id = ID++;
// ES 模块和其他 js 特性可能不是所有浏览器都支持,为了能够在所有浏览器上运行,我们使用 Babel 来转译
const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
});
// 返回该模块的所有信息.
return {
id,
filename,
dependencies,
code
};
}
/**
* @description: 创建依赖结构图
* @param {*} entry 入口文件的相对路径
* @return {*}
*/
function createGraph(entry) {
// 先解析入口文件的依赖信息.
const mainAsset = createAsset(entry);
// 用一个队列来保存所有的依赖信息
const queue = [mainAsset];
// 遍历队列
for (const asset of queue) {
// 每个 asset 都有一组它依赖模块的相对路径
// 我们用 createAsset 来迭代解析
asset.mapping = {};
// 当前模块的目录.
const dirname = path.dirname(asset.filename);
// 迭代访问依赖的相对路径.
asset.dependencies.forEach(relativePath => {
// 生成绝对路径
const absolutePath = path.join(dirname, relativePath);
// 提取依赖信息.
const child = createAsset(absolutePath);
// asset 保存 id 和 相对路径 的映射关系
asset.mapping[relativePath] = child.id;
// 加入队列
queue.push(child);
});
}
// queue 保存的就是当前应用的所有模块的依赖结构图
return queue;
}
/**
* @description: 文件打包
* @param {*} graph 依赖结构图
* @return {*}
*/
function bundle(graph) {
let modules = "";
graph.forEach(mod => {
// 每个模块用 id 来标识都是数组类型
// 每个模块有两个值
// 第一个是用函数包装每个模块的代码,是为了限定每个模块的作用域
// 第二个是模块的 相对路径和id 的依赖关系
modules += `${mod.id}: [
function (require, module, exports) {
${mod.code}
},
${JSON.stringify(mod.mapping)},
],`;
});
// 最后实现自动执行函数的主题
// 1. 创建 require 函数接收模块 id
// 2. 通过 id 来获取模块代码和映射关系
// 3. 根据不同的模块导入方式生成代码
const result = `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports : {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;
// 返回的结果就是打包完成的内容了
return result;
}
const graph = createGraph("./example/entry.js");
const result = bundle(graph);
console.log(result);
fs.writeFile("dist.js", result, e => {
if (e) throw e;
console.log("打包完成");
});
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# package.json
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.12.3",
"@babel/generator": "^7.12.1",
"@babel/parser": "^7.12.3",
"@babel/preset-env": "^7.12.1",
"@babel/traverse": "^7.12.1"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用说明
# 安装依赖
yarn install
# 执行
node src/minipack.js
# 会在最外层目录下生成 dist.js,执行该文件
node dist.js
# 输出 hello world!
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10