懒加载流程(webpack5)
f.j (生成promise) --->
f.l (script 动态加载文件) -->
f.j 的promise调用 --->
加载的文件执行 ( self["webpackChunk_02_webpack_config_start"].push ) , push 被改造 --->
webpackJsonpCallback (内容被放入内存) --->
f.t (返回ns, 包含文件内容)
-
用import动态导入的文件,webpack会单独打出chunk文件
-
webpack会用__webpack_require__.e方法引入文件
__webpack_require__.e(/* import() */ 1).then(__webpack_require__.t.bind(__webpack_require__, 1, 23)).then(function (login) {
console.log(login);
});
复制代码
3. e方法内部会掉哟哦那个 f.j 方法,最后调转到 f.l方法中使用jsonp
__webpack_require__.e = function(chunkId) {
debugger
return Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {
debugger
__webpack_require__.f[key](chunkId, promises); // key === j,这里实际调用了 j方法
return promises;
}, []));
};
复制代码
__webpack_require__.f.j = function(chunkId, promises) {
var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
if(installedChunkData !== 0) { // 0 means "already installed".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
if(true) {
var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });
promises.push(installedChunkData[2] = promise);
var url = __webpack_require__.p + __webpack_require__.u(chunkId);
var error = new Error();
var loadingEnded = function(event) {
};
__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
} else installedChunks[chunkId] = 0;
}
}
};
复制代码
这里j 控制了模块是否安装了,安装过了直接用installedChunkData数据(所以后面加载的数据肯定存在了installedChunkData中)。
如果没有安装过,则拼接url,用script动态去加载数据
__webpack_require__.l = function(url, done, key, chunkId) {
if(inProgress[url]) { inProgress[url].push(done); return; }
var script, needAttach;
if(!script) {
needAttach = true;
script = document.createElement('script');
script.charset = 'utf-8';
script.timeout = 120;
script.setAttribute("data-webpack", dataWebpackPrefix + key);
script.src = url;
}
inProgress[url] = [done];
var onScriptComplete = function(prev, event) {
}
// 超时处理
var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
script.onerror = onScriptComplete.bind(null, script.onerror);
script.onload = onScriptComplete.bind(null, script.onload);
// 这一步会去加载文件,就会执行文件内容
needAttach && document.head.appendChild(script);
};
复制代码
文件加载来了以后,会去执行:
(self["webpackChunk_02_webpack_config_start"] = self["webpackChunk_02_webpack_config_start"] || []).push()
复制代码
然后 push方法被全局覆盖成了:
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
复制代码
所以 webpackJsonpCallback 函数会被执行:
var webpackJsonpCallback = function(parentChunkLoadingFunction, data) {
var chunkIds = data[0];
var moreModules = data[1];
var runtime = data[2];
var moduleId, chunkId, i = 0;
for(moduleId in moreModules) {
if(__webpack_require__.o(moreModules, moduleId)) {
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if(runtime) var result = runtime(__webpack_require__);
if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
installedChunks[chunkId][0]();
}
installedChunks[chunkIds[i]] = 0;
}
}
复制代码
其中 installedChunks[chunkId][0] 就是 chunkId 的promise 的resovle方法被执行。
所以到这里问题都解决了:
-
为什么要使用promise介入?
因为我们不知道什么时候文件被加载完成。只有当文件加载完成以后执行了里面的内容我们再调用resolve方法。
promise来精确控制文件加载完成和加载失败的。
2. 文件是怎么加载的?
按需加载的文件是动态创建script标签加载的。
总结流程:当我们点击按钮去加载新的文件的时候,webpack 会根据chunkId调用e方法,e方法最后去调用f.j方法,为每一个chunkId 生成一个promise,并把状态改变的resovle,reject方法唯一存起来:installedChunks[chunkId] = [resolve, reject]等待触发。然后拼接绝地地址,调用l方法,l具体就是动态创建script标签并且放入头部,这样就会去加载文件。加载完的文件就会执行,执行的时候会调用被串改的push的方法,就是webpackJsonpCallback,webpackJsonpCallback把内容存起来以后就调用该chunkId的resolve方法,resolve被触发就会调用t方法,t方法就是正常的用__webpack_require__去加载文件内容,然后执行逻辑代码。
webpack流程的钩子
钩子汇总:
-
environment 读取环境
-
afterEnvironment 读取环境后触发
-
initiallize 初始化
-
beforeRun 运行前,启动文件读取功能
-
run 机器跑起来了,在编译,有缓存则启用缓存
const run = () => {
// 看这里,看这里beforeRun钩子
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
// 看这里,看这里run钩子
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
// 看这里看这里,这是又一个核心方法。
this.compile(onCompiled); // 开启编译阶段
});
});
});
};
复制代码
- beforCompile 开始编译前的准备,创建的ModuleFactory,创建Compilation,并绑定ModuleFactory到Compilation,同时处理一些不需要编译的模块。
- compile 进行编译
- make 编译的核心流程
- finishMake
- finish
- seal 对每个 chunk 进行整理、优化、封装
__webpack_require__
来模拟模块化操作. - afterCompile 编译结束
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
...
this.hooks.make.callAsync(compilation, err => {
...
this.hooks.finishMake.callAsync(compilation, err => {
...
process.nextTick(() => {
...
compilation.finish(err => {
...
compilation.seal(err => {
...
this.hooks.afterCompile.callAsync(compilation, err => {
logger.timeEnd("afterCompile hook");
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});
});
复制代码
-
shouldEmit 是否可以输出
-
emit 输出文件
-
afterEmit 输出完成
-
done 所有流程结束
const onCompiled = (err, compilation) => {
...
if (this.hooks.shouldEmit.call(compilation) === false) { // 看这里,看这里,shouldEmit是否可以输出
...
}
process.nextTick(() => {
...
this.emitAssets(compilation, err => { // emitAssets进行输出
...
});
});
};
复制代码
流程: