前言
本文分析webpack5的异步加载原理,代码是简化后的,原代码大概200行,简化后100行左右,但是功能依旧可以正常实现。
正文
首先贴出所有的代码,然后分析。
这是未打包的代码:
index.js代码,引入了test.js,但是是通过import异步引入。
// index.js
import("./test").then(val => {
console.log(val)
})
test.js, 默认导出了一个字符串。
// test.js
export default "test代码"
这是打包后的代码。
// index.js打包代码
var webpackModules = {};
var webpackModuleCache = {};
function webpackRequire(moduleId) {
var cachedModule = webpackModuleCache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = webpackModuleCache[moduleId] = {
exports: {}
};
webpackModules[moduleId](module, module.exports, webpackRequire);
return module.exports;
}
webpackRequire.m = webpackModules;
(() => {
webpackRequire.d = (exports, definition) => {
for (var key in definition) {
if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key]
});
}
}
};
})();
(() => {
webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
webpackRequire.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
});
}
Object.defineProperty(exports, 'esmodule', {
value: true
});
};
})();
var webpackJsonpCallback = ([chunkIds, moreModules]) => {
var resolves = []
for (var i = 0; i 0) {
resolves.shift()()
}
}
webpackRequire.f = {}
var installedChunks = {
main: 0
}
webpackRequire.p = "";
webpackRequire.u = (chunkId) => chunkId + ".js";
// 3
webpackRequire.l = (url) => {
var script = document.createElement("script");
script.src = url;
document.head.appendChild(script);
}
// 2
webpackRequire.f.j = (chunkId, promises) => {
var installedChunkData = installedChunks[chunkId];
// 说明这个chunk已经加载过了
if (installedChunkData !== undefined) { return }
var promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
})
installedChunkData[2] = promise;
promises.push(promise);
var url = webpackRequire.p + webpackRequire.u(chunkId);
webpackRequire.l(url)
}
// 1
webpackRequire.e = (chunkId) => {
var promises = [];
webpackRequire.f.j(chunkId, promises);
return Promise.all(promises)
}
// 4
var wepackLoadingGlobal = window.webpackChunkwebpack2 = [];
// 重写push
wepackLoadingGlobal.push = webpackJsonpCallback
webpackRequire.e("src_test_js").then(webpackRequire.bind(webpackRequire, "./src/test.js")).then(val => {
console.log(val);
});
这个是test.js打包后的代码
(self["webpackChunkwebpack2"] = self["webpackChunkwebpack2"] || []).push([["src_test_js"], {
"./src/test.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
webpackRequire.r(webpackExports);
webpackRequire.d(webpackExports, {
"default": () => webpackDefaultExport
});
const webpackDefaultExport = "test代码";
}
}]);
我们先来看这段代码比较重要的初始化。
// 这个是存放所有的模块。
var webpackModules = {};
// 模块的缓存。
var webpackModuleCache = {};
// 模块引入
function webpackRequire(moduleId) {
var cachedModule = webpackModuleCache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = webpackModuleCache[moduleId] = {
exports: {}
};
webpackModules[moduleId](module, module.exports, webpackRequire);
return module.exports;
}
webpackRequire.m = webpackModules;
对象 webpackModules
表示的是所有的模块的集合,无论我们是通过esModule还是commonjs导出的模块都在这里存放,这里之所以是一个空的对象是因为,我虽然导出了test模块,但是这个模块是被异步加载的,webpack会在后面执行import("./test.js")
的时候把对应的模块放进 webpackModules
,这个后面会说。
然后就是webpackModuleCache
,这个对象是一个用于缓存的对象,当我们使用过某个模块后,这个模块就会被放进webpackModuleCache
。
函数webpackRequire
是一个很重要的函数,这个函数的作用是用来获取webpackModules
里面的模块的,要想知道webpackRequire
是怎么获取模块的,我们就要先看懂导出后的模块打包好的代码是什么样子的。
来看上面的test.js打包后的代码:
(self["webpackChunkwebpack2"] = self["webpackChunkwebpack2"] || []).push([["src_test_js"], {
// 暂时只看这个对象字段
"./src/test.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
webpackRequire.r(webpackExports);
webpackRequire.d(webpackExports, {
"default": () => webpackDefaultExport
});
const webpackDefaultExport = "test代码";
}
}]);
我们先不看上面的函数调用,就看 "./src/test.js"
这个字段,这个字段就是test.js打包后的代码,webapck把它封装成为了一个函数,这样做的原因就是为了让外界能拿到这个模块导出的内容,这个函数接受了三个参数,分别是:unusedWebpackModule
,webpackExports
,webpackRequire
,第一个参数就是commonjs的全局module对象,之所以翻译过来叫“没有使用的模块”,是因为在上面的webpackRequire
函数里面检查了模块是否被缓存,有缓存就从缓存里面去取,就不会走到这个函数了
// webpackRequire的过滤
if (cachedModule !== undefined) {
return cachedModule.exports;
}
第二个参数webpackExports
表示的是commonjs的module.exports,这个参数其实也是esModule导出变量的方法,但是commonjs和esModule有很大的不同,这个我下面说。
第三个参数是webpackRequire
,这个参数的作用是提供一些工具方法。
函数分析:webpackRequire.r(webpackExports);
的作用是把当前模块标记为esmodule
。
简单看看webpackRequire.r
的实现:
webpackRequire.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
});
}
Object.defineProperty(exports, 'esmodule', {
value: true
});
};
代码很简单,就是添加了两个字段,标明是esModule。这个工具函数只有在模块是esModule的时候才会被使用。
webpackRequire.d(webpackExports, {
"default": () => webpackDefaultExport
});
const webpackDefaultExport = "test代码";
这段代码是导出的核部分,这段代码通过 webpackRequire.d
,把test.js导出的代码给添加到了 webpackExports
上,函数的第二个参数包含了模块导出的代码。
来看 webpackRequire.d
的实现:
webpackRequire.d = (exports, definition) => {
for (var key in definition) {
if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key]
});
}
}
};
这段代码的实现很简单,这里的webpackRequire.o
的作用是用来判断参数1是否包含参数2。webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
做完判断之后,函数对definition
进行了遍历,并且把每个值都以 Object.defineProperty的方式定义到了exports
上,这也就解释了刚刚为什么模块导出的参数是写成了一个函数,就是为了这里方便添加到get方法上。这同样解释了为什么commonjs导入的值可以修改,但是esModule导入的值不能修改,因为esModule导出的值根本没有set方法。webpack把值放在了exports上,最后webpackRequire
返回了这个值,这样就完成了模块对值的导出。
累了累了,明天接着写
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net