承接上文,先看下上次没搞明白的两个点,然后根据读到的源码写个简易版的异步加载;
上次代码中webpackJsonpCallck中有两个地方如下
webpackJsonCallback = (parentChunkLoadingFunction, data) {
var [modules, moreModules, runtime] = data;
// 。。。 此处省略模块安装
if (runtime) runtime(__webpack_require__);
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data)
// 。。。 此处省略script loading resolve
}
这个函数的调用用到了柯里化,填充参数分为了两步;
- 情况一: runtime先加载 step1:
chunkLoadingGloabl.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
step2(async module invoke):
// 作为入口的模块打包的时候会传入runtime,开始执行
(chunkLoadingGlobal.push([[chunkId], [modules], ?runtime]))();
- 情况二: 模块先加载 从webpack4开始运行时模块可以在其他模块后加载的,详情可以点击链接;
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
来看下chunkLoadingGloabl的结构:[chunkIds, moreModules, ?runtime];
- chunkIds(加载的chunkId)用于resolve对应chunk load 的 Promise;
- moreModules 存储实际模块内容,用于安装;
- runtime用于执行入口模块; webpack4可以在控制台打印webpackJsonp查看,webpack5 打印 webpackChunkwebpack_build 查看;
然后我们来实现一个低配版模块加载,webpack 这段代码应该是解析完后生成的,解析过程还未深入了解,大家可以参考这个老哥写的babel import语法 js_深入原理:Babel原理与Babel插件实践; 所以先实现主要的模块加载,类似amd加载方式:
下面是一个简陋版本的实现:
(function (global) {
let modules = {};
let pathMap = {};
function define (deps, fn) {
if (typeof deps === 'function') {
modules = {...modules, ...deps()};
return
}
for (let [index, item] of deps.entries()) {
if (modules[item]) {
deps[index] = Promise.resolve(modules[item])
} else {
let structrue = [];
deps[index] = new Promise((resolve, reject) => {
structrue = [() =>resolve(modules[item]), reject]
});
loadScript(item, structrue);
}
}
Promise.all(deps).then(res => {
let result = fn.apply(null, res);
if (result) {
modules = {...modules, ...result}
}
})
}
// 用于配置模块加载路径
define.config = function (config) {
pathMap = config.paths
}
let inProgress = {};
function loadScript (id, structure) {
const url = pathMap[id];
if (inProgress[url]) {
// 该模块已经请求中;
inProgress[url].push(structure);
return;
}
const script = document.createElement('script');
document.body.append(script);
script.src = url;
inProgress[url] = [structure];
script.onload = function () {
let callbacks = inProgress[url];
delete inProgress[url];
callbacks.forEach(([resolve, reject]) => {
resolve();
});
document.body.removeChild(script);
}
script.onerror = function (err) {
let callbacks = inProgress[url];
delete inProgress[url];
callbacks.forEach(([resolve, reject]) => {
reject(err);
});
document.body.removeChild(script);
}
}
global.define = define;
})(window);
使用方式如下:
<script src="./module.js"></script>
<script>
define.config({
paths: {mutilple: './multiple.js'}
});
define(['mutilple'], function (mutilple) {
console.log(mutilple);
console.log(mutilple(2, 3));
})
</script>
感觉看了编译完的代码主要只是感觉这个promise 的 resolve 和 reject 还可以传到别的地方去执行,这个挺有趣;
好了,整体分析到此结束,其实看编译完的代码和源码,揣测作者的真实意图还是不太容易;有问题或者错误的地方大家可以留言讨论;