前言背景
在我们前端首页页面做优化时候, 代码分割是一个常用的手段,页面只加载需用用到的资源,其他的模块等到需要时候再去加载
import()示例
// a.js
export default function a() {
console.log("我是模块 a");
}
import("./a").then(({ default: a }) => {
console.log(a);
});
webpack 解析原理
源码
- import 字段解析 webpack_require.e 主体方法
(() => {
__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => {
return Promise.all(
Object.keys(__webpack_require__.f).reduce((promises, key) => {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, [])
);
};
})();
/*
- webpack_require.e 对应 import() 方法,即异步加载的主体方法
- 返回一个 promise
*/
- 主体方法
__webpack_require__.e = function requireEnsure(chunkId) {
var installedChunkData = installedChunks[chunkId];
// 0 表示已经加载成功,无需再做任何处理
if (installedChunkData === 0) {
return new Promise(function (resolve) {
resolve();
});
}
if (installedChunkData) {
return installedChunkData[2];
}
var promise = new Promise(function (resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
// 数据结构为
/*
{
id: [resolve, reject]
}
*/
installedChunkData[2] = promise;
// 生成一个 script 标签,用于异步加载 js 文件
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.charset = "utf-8";
script.async = true;
script.timeout = 120000;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
// __webpack_require__.p 就是 __webpack_public_path__ 对应的地址
script.src =
__webpack_require__.p +
"" +
({ 0: "a-async" }[chunkId] || chunkId) +
".async.js";
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
function onScriptComplete() {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
// 如果文件加载成功,chunk就被设置为 0;后面只处理了加载失败的情况
if (chunk !== 0) {
if (chunk) {
chunk[1](new Error("Loading chunk " + chunkId + " failed."));
}
installedChunks[chunkId] = undefined;
}
}
head.appendChild(script);
return promise;
};
- a 文件
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
[0],
{
"./src/a.js": function (module, __webpack_exports__, __webpack_require__) {
"use strict";
eval(``);
/***/
},
},
]);
- webpackJsonp 方法
window["webpackJsonp"] = function webpackJsonpCallback(data) {
// chunkid
var chunkIds = data[0];
// chunkid对应的模块
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]
) {
// 收集chunk对应的resolve方法
resolves.push(installedChunks[chunkId][0]);
}
// 标记该chunk已经加载
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
// 添加chunk模块,到全局modules对象中
modules[moduleId] = moreModules[moduleId];
}
}
if (parentJsonpFunction) parentJsonpFunction(data);
// 依次执行chunk对应promise的resolve方法
while (resolves.length) {
resolves.shift()();
}
};
图例
原理
- 主文件
- import 会被解析为 webpack_require_e
- chunk 没有被加载过会为这个 chunkId 创建一个 promsie 对象
- promise的resolve、reject方法放在installchunks[chunkId]中
-
{ id: [resolve, reject] }
-
- 创建一个script标签来加载js文件
- 挂载到window上一个webpackJsonp方法
- 异步文件
- chunk对应的resolve方法, 放在了installedChunks[chunkId]里
- 依次执行chunk对应promise的resolve方法