webpack-bundler-demo-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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const fs = require("fs");
const path = require("path");
const babylon = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");

let ID = 0;

//读取文件信息,并获得当前js文件的依赖关系
function createAsset(filename) {
//获取文件,返回值是字符串
const content = fs.readFileSync(filename, "utf-8");

//讲字符串为ast(抽象语法树, 这个是编译原理的知识,说得简单一点就是,可以把js文件里的代码抽象成一个对象,代码的信息会存在对象中)
//babylon 这个工具是是负责解析字符串并生产ast。
const ast = babylon.parse(content, {
sourceType: "module"
});

//用来存储 文件所依赖的模块,简单来说就是,当前js文件 import 了哪些文件,都会保存在这个数组里
const dependencies = [];

//遍历当前ast(抽象语法树)
traverse(ast, {
//找到有 import语法 的对应节点
ImportDeclaration: ({ node }) => {
//把当前依赖的模块加入到数组中,其实这存的是字符串,
//例如 如果当前js文件 有一句 import message from './message.js',
//'./message.js' === node.source.value
dependencies.push(node.source.value);
}
});

//模块的id 从0开始, 相当一个js文件 可以看成一个模块
const id = ID++;

//这边主要把ES6 的代码转成 ES5
const { code } = babel.transformFromAstSync(ast, null, {
presets: ["@babel/preset-env"]
});

return {
id,
filename,
dependencies,
code
};
}

//从入口开始分析所有依赖项,形成依赖图,采用广度遍历
function createGraph(entry) {
const mainAsset = createAsset(entry);

//既然要广度遍历肯定要有一个队列,第一个元素肯定是 从 "./example/entry.js" 返回的信息
const queue = [mainAsset];


for (const asset of queue) {
const dirname = path.dirname(asset.filename);

//新增一个属性来保存子依赖项的数据
//保存类似 这样的数据结构 ---> {"./message.js" : 1}
asset.mapping = {};

asset.dependencies.forEach(relativePath => {
const absolutePath = path.join(dirname, relativePath);

//获得子依赖(子模块)的依赖项、代码、模块id,文件名
const child = createAsset(absolutePath);

//给子依赖项赋值,
asset.mapping[relativePath] = child.id;

//将子依赖也加入队列中,广度遍历
queue.push(child);
});
}
return queue;
}

//根据生成的依赖关系图,生成对应环境能执行的代码,目前是生产浏览器可以执行的
function bundle(graph) {
let modules = "";

//循环依赖关系,并把每个模块中的代码存在function作用域里
graph.forEach(mod => {
modules += `${mod.id}:[
function (require, module, exports){
${mod.code}
},
${JSON.stringify(mod.mapping)},
],`;
});

//require, module, exports 是 cjs的标准不能再浏览器中直接使用,所以这里模拟cjs模块加载,执行,导出操作。
const result = `
(function(modules){
//创建require函数, 它接受一个模块ID(这个模块id是数字0,1,2) ,它会在我们上面定义 modules 中找到对应是模块.
function require(id){
const [fn, mapping] = modules[id];
function localRequire(relativePath){
//根据模块的路径在mapping中找到对应的模块id
return require(mapping[relativePath]);
}
const module = {exports:{}};
//执行每个模块的代码。
fn(localRequire,module,module.exports);
return module.exports;
}
//执行入口文件,
require(0);
})({${modules}})
`;

return result;
}

const graph = createGraph("./example/entry.js");

const ret = bundle(graph);

// 打包生成文件
fs.writeFileSync("./bundle.js", ret);
坚持原创技术分享,您的支持将鼓励我继续创作!