webpack提供了動態加載模塊的接口import(),require.ensure()。
照例準備2個JS,a.js,b.js
a.js
function c(){
import('./b').then(func=>{ //動態加載模塊b
func()
})
}
export default c
b.js
export default function(){
console.log('hello world')
}
webpack編譯,生成了2份文件,main.js和1.js。
先看下main.js,依舊是那個自執行函數
(function(modules){
})([
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
function c(){
__webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(null, 1)).then(func=>{
func()
})
}
__webpack_exports__["default"] = (c);
})
])
數組里只有a.js的內容,import函數被轉化成了_webpack_require_.e
// 存儲已加載或正在加載中的chunk
// undefined 表示chunk還未加載, null = chunk preloaded/prefetched
// Promise表示加載中,0表示已經加載
var installedChunks = {
0: 0
};
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 表示已經被加載
// Promise表示加載中
if(installedChunkData) {
promises.push(installedChunkData[2]); //installedChunkData[2]是Promise
} else {
// 在緩存中設置Promise的resolve和reject
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise); //promise賦值到installedChunkData[2]
// 開始加載JS
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout); //清除超時定時器
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error); //chunk[1]是Promise的reject
}
installedChunks[chunkId] = undefined; //設置狀態為未加載
}
};
var timeout = setTimeout(function(){ //超時定時器
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
這里肯定有疑惑,onScriptComplete怎么都是失敗的處理,那成功的處理在哪,什么時候會把installedChunks 里的狀態變為0——已加載。
回過頭看下b.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (function(){
console.log('hello world')
});
})
]]);
執行了window["webpackJsonp"]的push方法。
在main.js中
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
其實就是加載JS成功時執行了webpackJsonpCallback。
function webpackJsonpCallback(data) {
var chunkIds = data[0]; //獲取chunkID,b.js是1
var moreModules = data[1];
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]); //講promise的resolve存到數組中
}
installedChunks[chunkId] = 0; //將狀態變為已加載
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId]; //加載后的模塊存到modules中
}
}
if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()(); //Promise resolve。即import('./b')回調觸發
}
};
回顧一下整體流程
1、_webpack_require(0) 加載入口
2、_webpack_require.e('b.js')。動態加載b.js(1.js)。
3、從installedChunks中查找是否已經有該模塊,如果已經加載過,直接返回Promise。如果沒有,installedChunks[id]設置為一個數組[resolve,reject,Promise],添加script標簽來加載JS。
4、超時或者加載失敗則會將installedChunks[id]置回undefined。
5、加載JS成功會執行webpackJsonpCallback。installedChunks[id]設為0表示加載成功,同時保存在modules中,觸發之前保存在installedChunks[id]的resolve,至此完成流程。