懒加载原理
webpack懒加载其实就是一个很简单的概念:动态标签,可以理解为就是JSONP,原理也就是动态插入script标签,Just this!!!
首先让我们先看看JSONP的实现
(function (window,document) {
"use strict";
var jsonp = function (url,data,callback) {
// 将传入的data数据转化为url字符串形式
var dataString = url.indexof('?') == -1? '?': '&';
for(var key in data){
dataString += key + '=' + data[key] + '&';
};
// 处理url中的回调函数
var cbFuncName = 'cb' + Math.random().toString().replace('.','');
dataString += 'callback=' + cbFuncName;
// 创建一个script标签并插入到页面中
var script = document.createElement('script');
script.src = url + dataString;
// 挂载回调函数
window[cbFuncName] = function (data) {
callback(data);
// 处理完回调函数的数据之后,删除jsonp的script标签
document.body.removeChild(script);
}
// append到页面中
document.body.appendChild(script);
}
window.jsonp = jsonp;
})(window,document)
webpack懒加载实现
- webpack的import的加载源码
/**
* @desc webpack是动态加载script的函数
* @param chunkId 为webpack打包自动生成的序号, 可由魔法注释 webpackChunkName修改打包后的命名
* import(/* webpackChunkName: 'Lazy' */'./Lazy') => Lazy.main.js
*/
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// installedChunks是webpack内部维护的依赖安装的变量
var installedChunkData = installedChunks[chunkId];
// 0 代表已安装, 如果在缓存中不存在, 则进入以下流程
if(installedChunkData !== 0) { // 0 means "already installed".
// 如果在缓存中存在, 但是状态不等于0, 则说明还在安装过程中
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// 将依赖包装成一个promise推到缓存中, 代表已经有加载过这个依赖了
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
// ∨∨∨ 注意这一步, 这里推入的promises, 后续会在JsonCallback中执行 ∨∨∨
promises.push(installedChunkData[2] = promise);
// ∨∨∨ 从这一步开始,创建script标签了 ∨∨∨
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 ∨∨∨
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
// script加载成功或失败的回调
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);
}
installedChunks[chunkId] = undefined;
}
};
// 超时处理
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
// 在head插入script标签
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
对比了JSONP的实现, 是不是发现很类似, 也就多了步缓存处理
- webpackJsonpCallback源码
...
// webpack 在window下注册的一个webpackJsnop 事件,并重写了这个方法的push事件,
// 每次懒加载都会触发这个push事件
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
...
=======================================================================================
// 我们先看下import(/* webpackChunkName: 'Lazy' */'./Lazy')中的Lazy最后会打包成什么样子
@file dist/lazy.main.js
// 这是懒加载文件的源码, 可以看到, 文件默认执行的 window["webpackJsonp"]的push操作,
// 而push操作会触发webpackJsonpCallback函数,使依赖置为 0,并执行then方法
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
["Lazy"],
{
// 一些注释
"./src/Lazy.js": (function(module, exports) {
eval("var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal[\"default\"].signature : function (a) {\n return a;\n};\n\nconsole.log('This is a Lazy test');\n");
}),
...,
// 如果Lazy.js有import其他依赖, 则会出现在这里
}
]);
=======================================================================================
// 然后再看看 webpackJsonpCallback的方法
function webpackJsonpCallback(data) {
// 懒加载的文件: ['Lazy']
var chunkIds = data[0];
// 加载文件中依赖的文件, 结构大致如下
// {
// "./src/Lazy.js": (function() {}),
// "./src/xxx.js": (function() {}),
// ...
// }
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]) {
// 在这里, 将promises中的resolve推入resolves中
resolves.push(installedChunks[chunkId][0]);
}
// 将全局变量中的依赖置为0, 说明依赖已经安装完成
installedChunks[chunkId] = 0;
}
// 遍历循环依赖,并将依赖执行代码 (function() {}) 推到全局变量module中
// ∨∨∨ 后面__webpack_require__会用到 ∨∨∨
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonpFunction) parentJsonpFunction(data);
// 这里,开始执行promises中的resolve, 进入__webpack_require__的执行
while(resolves.length) {
resolves.shift()();
}
};
- __webpack_require__源码
// import(/* webpackChunkName: 'Lazy' */ './Lazy').then(data => console.log(data))
// 实际上, import().then()在webpack的编译下会解释成以下这个样子
// 看上文 __webpack_require__.e = function requireEnsure(chunkId) 也就是动态创建标签,加载script
__webpack_require__.e(/*! import() | Lazy */ \"Lazy\")
.then(
// webpackJsonpCallback最后的resolve.shift()()将运行到这里
__webpack_require__.bind(null,\"./src/Lazy.js\")
)
.then(
function (data) {\n console.log(data);\n }
);
=======================================================================================
// 在这里出现了一个__webpack_require__, 我们看看这是个什么东东
function __webpack_require__(moduleId) {
// 检查全局依赖中是否已经安装了,有的话则返回它的导出
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 如果没有,创建一个新的模块,并把它推到全局缓存module中
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
hot: hotCreateModule(moduleId),
parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
children: [],
hot: hotCreateModule(moduleId),
parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
children: []
};
// 在全局变量中取出执行代码
// 对应上面webpackJsonpCallback回调中,推入全局module缓存的代码块
// ∨∨∨ 相当于执行 ∨∨∨
// (function(module, __webpack_exports__, __webpack_require__) {
// ...
// console.log('xxx');
// }).call(module.exports, module, module.exports, __webpack_require__(moduleId))
modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
module.l = true;
// 返回模块的exports,给到then去执行
// import('./Lazy').then(data => console.log(data))
// ↑↑↑↑ 这个就是module.exports了
return module.exports;
}
webpack懒加载流程图
最后附上具体流程图
结语
至此, webpack的懒加载实现就讲完了。是不是感觉非常简单呢
后续结合懒加载的实现原理,可能会将出新的分享《自己手撸的热更新实现》
(如果有人看的话)