背景及用户角度
webpack懒加载分离代码方式有如下几种
- 入口起点:配置entry
- 同一份代码引用:splitChunks即代码分割
- 动态导入:通过内联模块分离代码
此处主要讨论第三点
main.js中写下如下代码
// 实际场景可能是点击事件之类的
setTimeout(()=>{
import('./test.js').then(res=>{
res.say()
})
})
test.js
export function say(){
console.log('说一些东西')
}
配置
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
module.exports = {
mode: "development",
devtool: false,
entry: {
main: "./src/main.js",
},
output: {
filename: "main.js", //定义打包后的文件名称
path: path.resolve(__dirname, "./dist"), //必须是绝对路径
},
};
主要代码
代码主要内容会被打包为如下(经优化)
main.js
require.e('src_test_js')
.then(require.bind(require,'./src/test.js'))
.then(res=>{
res.say()
})
src_test_js.main.js
(self["webpackChunLib"] = self["webpackChunLib"] || []).push([
["src_test_js"],
{
"./src/test.js": function (module, exports, require) {
require.setModuleTag();
require.defineProperties(exports, {
say: () => say,
});
var say = function () {
conosle.log("说明一些东西");
};
},
},
]);
原理
大致原理过程如下
1、创建script标签,通过jsonp加载文件
2、执行其中脚本,运行push将内容合并至modules
3、通过require.bind(require,'./src/test.js')
执行modules中对应的模板,拿到模块返回内容
4、执行对应的内容
上帝视角
接下来通过上帝视角看看require.e以及文件关联
各个函数
require.e
代码大致:引入promise,执行require.j 并返回promise.all执行完成
功能:返回require.j整合promise.all ,promise.all中执行完成时机即为 modules合并
require.j
代码大致:赋值promise.all,jsonp导入文件
功能: j 为promise.all添加执行项;sonp导入文件,导入时执行js文件;
src_test_js.main.js
功能:调用全局对象webPackChunk.push功能,完成modules合并
webPackChunk
功能:合并modules,合并完成后执行promise.all中每个promise.resolve完成promise.all调用
至此require.e执行完成,modules完成导入
接下来则是像上一节一样利用require完成数据输入
效果
如下:src_test_js.main.js会延迟一秒导入
总体代码如下
mian.js
const modules = {}
function require(id){
const module = {exports:{}}
modules[id](module,module.exports,require)
return module.exports
}
// 设置module标志
require.setModuleTag=function(exports){
Object.defineProperty(exports,Symbol.toStringTag,{
value:'esModule'
})
Object.defineProperty(exports,'_es_moduel',{
value:true
})
}
// 设置属性
require.defineProperties = function(exports,definition){
for(let key in definition){
Object.defineProperty(exports,key,{
enumerable:true,
get:definition[key]
})
}
}
// 异步调用
require.e = function(chunkId){
const promises = []
require.j(chunkId,promises)
return Promise.all(promises)
}
// output中的配置
require.publicPath = './'
const installChunk = {}
require.j = function(chunkId,promises){
var promise = new Promise((resolve,reject)=>{
installChunk[chunkId] = [resolve,reject]
})
promises.push(promise)
var script = document.createElement('script')
script.src = require.publicPath+chunkId+'.main.js'
document.body.appendChild(script)
}
// 外部文件jsonp导入后会执行 整合module主函数
var webPackChunk = function([chunkId,moreModule]){
let resolves = []
for(let i =0;i<chunkId.length;i++){
resolves.push(installChunk[chunkId][0])
// 标志完成了
installChunk[chunkId] = 0
}
for(let key in moreModule){
modules[key] = moreModule[key]
}
//执行完promise.all
while(resolves.length){
resolves.shift()()
}
}
var chunkLoadingGlobal = this["webpackChunklib"] = this["webpackChunklib"] || [];
chunkLoadingGlobal.push = webPackChunk
setTimeout(()=>{
require.e('src_test_js').then(require.bind(require,'./src/test.js')).then((module)=>{
const {say} = module
say()
})
},1000)
src_test_js.main.js
(this.webpackChunklib =this.webpackChunklib||[]).push([
['src_test_js'],{
'./src/test.js'(module,exports,require){
require.setModuleTag(exports)
require.defineProperties(exports,{
say:()=>say
})
function say(){
console.log('说一些东西')
}
}
}
])