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,至此完成流程。