Webpack如何保证构建产物的加载顺序?

假如通过splitChunks分出了多个initialChunk,Webpack如何保证这些产物的加载顺序?下文参考这个仓库,构建产物包含app.js和vendors.js。webpack如何保证在下载了vendors.js之后才执行app.js相关的模块代码?

app.js中有如下代码

1
2
3
4
// 表示在下载完vendors之后再require入口文件
var __webpack_exports__ = __webpack_require__.O(undefined, ["vendors"], function() {
return __webpack_require__("./client/src/entry.tsx");
})

webpack通过JSONP的方式加载chunk,__webpack_require__.O 是webpack管理runtime chunk加载的函数,会将entry所依赖的chunk以及dependency chunk loaded callback记录到deferred数组中,dependency chunk加载完成后便执行callback。

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
!function() {
var deferred = [];
__webpack_require__.O = function(result, chunkIds, fn, priority) {
// 添加webpackPrefetch: true注释会添加priority
if (chunkIds) {
priority = priority || 0;
// priority越大越靠前
for (var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--)
deferred[i] = deferred[i - 1];
// 将依赖的chunkid和加载完成之后的callback加入到deferred中。
deferred[i] = [chunkIds, fn, priority];
return;
}
var notFulfilled = Infinity;
for (var i = 0; i < deferred.length; i++) {
var chunkIds = deferred[i][0];
var fn = deferred[i][1];
var priority = deferred[i][2];
var fulfilled = true;
for (var j = 0; j < chunkIds.length; j++) {
if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) {
// 判断依赖的chunk是否加载到内存中
return __webpack_require__.O[key](chunkIds[j]);
})) {
chunkIds.splice(j--, 1);
} else {
fulfilled = false;
if (priority < notFulfilled)
notFulfilled = priority;
}
}
// 关联的chunks都下载完成
if (fulfilled) {
deferred.splice(i--, 1)
// 执行回调
var r = fn();
if (r !== undefined)
result = r;
}
}
return result;
};
}();

chunk加载

vendors.js下载完执行时会调用window[chunkLoadingGlobal].push相关模块。

vendors.js内容

chunkLoadingGlobal这个键名可以通过output.chunkLoadingGlobal更改。window[chunkLoadingGlobal]是在app.js中实现的(假如webpack配置开启了optimization.runtimeChunk,则在单独的runtimeChunk.js文件中),将chunk相关的modules添加到全局模块缓存后,会check一遍deferred数组中是否有需要执行的callback,我们这个例子中就是执行entry.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
/**
* @param parentChunkLoadingFunction 旧的push方法,一般是Array.prototype.push
* @param data [[vendors.js], {'/node_modules/axios/index.js': module}]
*/
var webpackJsonpCallback = function(parentChunkLoadingFunction, data) {
var chunkIds = data[0];
var moreModules = data[1];
var runtime = data[2];
var moduleId, chunkId, i = 0;
if (chunkIds.some(function(id) {
return installedChunks[id] !== 0;
})) {
for (moduleId in moreModules) {
// hasOwnProperty
if (__webpack_require__.o(moreModules, moduleId)) {
// 添加modules到webpack模块缓存中
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if (runtime) {
// 暂时不知道用来处理什么场景
var result = runtime(__webpack_require__);
}
}
if (parentChunkLoadingFunction) {
// push到window[chunkLoadingGlobal]中
parentChunkLoadingFunction(data);
}
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
// resolve 异步加载的chunk
installedChunks[chunkId][0]();
}
// 标记为chunk依赖的module都已添加到缓存
installedChunks[chunkId] = 0;
}
// 将所有module添加到内存后,check一下deferred是否有需要执行的callback
return __webpack_require__.O(result);
}
var chunkLoadingGlobal = self["webpackChunkmobx"] = self["webpackChunkmobx"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));

Optimize.RuntimeChunk配置的作用

将运行时模块管理相关的代码从app.js中抽离出来,有利于缓存。因为线上环境vendors文件名都会带有hash,假如vendors内容改了导致hash改了,app.js也得做变更,影响网页性能。

1
2
3
4
// app.js
var __webpack_exports__ = __webpack_require__.O(undefined, ["vendors"], function() {
return __webpack_require__("./client/src/entry-client.tsx");
})