原文件内容
import("./a").then(console.log);
export default "async a";
文件目录
入口文件生成代码
var __webpack_modules__ = {
"./src/index.js": (
__unused_webpack_module,
__unused_webpack_exports,
__webpack_require__
) => {
eval(
'__webpack_require__.e(/*! import() */ "src_a_js").then(__webpack_require__.bind(__webpack_require__, /*! ./a */ "./src/a.js")).then(console.log);\n\n\n//# sourceURL=webpack://webpack5_demo/./src/index.js?'
);
},
};
var __webpack_exports__ = __webpack_require__("./src/index.js");
- __webpack_modules__是文件id(moduleId)和引入文件内容的方法的映射对象,通过__webpack_require__根据chunkId实现内容的导入
- 可以看到异步文件是通过__webpack_require__.e引入的
- “src_a_js"是打包后的异步文件名称(chunkId),”./src/a.js"是异步文件未打包前的名称,作为异步文件的id(moduleId)
- 当异步chunk下载完成后,通过__webpack_require__引入
生成的一些辅助方法
__webpack_require__.m = __webpack_modules__;
-
webpack_require.d
- 通过Object.defineProperty定义对象属性
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
__webpack_require__.u = (chunkId) => {
return "" + chunkId + ".bundle.js";
};
})();
(() => {
__webpack_require__.g = (function () {
if (typeof globalThis === "object") return globalThis;
try {
return this || new Function("return this")();
} catch (e) {
if (typeof window === "object") return window;
}
})();
})();
(() => {
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
}
Object.defineProperty(exports, "__esModule", { value: true });
};
})();
导入文件通用方法
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
异步文件引入
-
获取下载文件的公共地址__webpack_require__.p
- 通过获取已有script标签的src属性,来获取下载地址
(() => {
var scriptUrl;
if (__webpack_require__.g.importScripts) {
scriptUrl = __webpack_require__.g.location + "";
}
var document = __webpack_require__.g.document;
if (!scriptUrl && document) {
if (document.currentScript) scriptUrl = document.currentScript.src;
if (!scriptUrl) {
var scripts = document.getElementsByTagName("script");
if (scripts.length) {
scriptUrl = scripts[scripts.length - 1].src;
}
}
}
if (!scriptUrl)
throw new Error("Automatic publicPath is not supported in this browser");
scriptUrl = scriptUrl
.replace(/#.*$/, "")
.replace(/\?.*$/, "")
.replace(/\/[^\/]+$/, "/");
__webpack_require__.p = scriptUrl;
})();
-
异步导入__webpack_require__.e
- 通过Promise.all进行阻塞,当文件下载完毕后再通过__webpack_require__导入
(() => {
__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => {
let res = Promise.all(
Object.keys(__webpack_require__.f).reduce((promises, key) => {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, [])
);
return res;
};
})();
-
包装异步文件__webpack_require__.f.j
- 将初次未下载的异步chunk包裹成promise,再通过__webpack_require__.l发出实际请求
(() => {
var installedChunks = {
main: 0,
};
__webpack_require__.f.j = (chunkId, promises) => {
var installedChunkData = __webpack_require__.o(installedChunks, chunkId)
? installedChunks[chunkId]
: undefined;
if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
if (true) {
var promise = new Promise(
(resolve, reject) =>
(installedChunkData = installedChunks[chunkId] =
[resolve, reject])
);
promises.push((installedChunkData[2] = promise));
var url = __webpack_require__.p + __webpack_require__.u(chunkId);
var error = new Error();
var loadingEnded = (event) => {
if (__webpack_require__.o(installedChunks, chunkId)) {
installedChunkData = installedChunks[chunkId];
if (installedChunkData !== 0)
installedChunks[chunkId] = undefined;
if (installedChunkData) {
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;
installedChunkData[1](error);
}
}
};
__webpack_require__.l(
url,
loadingEnded,
"chunk-" + chunkId,
chunkId
);
} else installedChunks[chunkId] = 0;
}
}
};
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
};
})();
-
下载文件请求方法__webpack_require__.l
(() => {
var inProgress = {};
var dataWebpackPrefix = "webpack5_demo:";
__webpack_require__.l = (url, done, key, chunkId) => {
if (inProgress[url]) {
inProgress[url].push(done);
return;
}
var script, needAttach;
if (key !== undefined) {
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if (
s.getAttribute("src") == url ||
s.getAttribute("data-webpack") == dataWebpackPrefix + key
) {
script = s;
break;
}
}
}
if (!script) {
needAttach = true;
script = document.createElement("script");
script.charset = "utf-8";
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.setAttribute("data-webpack", dataWebpackPrefix + key);
script.src = url;
}
inProgress[url] = [done];
var onScriptComplete = (prev, event) => {
console.log("script end");
script.onerror = script.onload = null;
clearTimeout(timeout);
var doneFns = inProgress[url];
delete inProgress[url];
script.parentNode && script.parentNode.removeChild(script);
doneFns && doneFns.forEach((fn) => fn(event));
if (prev) return prev(event);
};
var timeout = setTimeout(
onScriptComplete.bind(null, undefined, {
type: "timeout",
target: script,
}),
120000
);
script.onerror = onScriptComplete.bind(null, script.onerror);
script.onload = onScriptComplete.bind(null, script.onload);
needAttach && document.head.appendChild(script);
};
})();
-
监听文件下载完成的黑魔法webpackJsonpCallback
(() => {
var installedChunks = {
main: 0,
};
__webpack_require__.f.j = (chunkId, promises) => {
};
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
var [chunkIds, moreModules, runtime] = data;
var moduleId,
chunkId,
i = 0;
if (chunkIds.some((id) => installedChunks[id] !== 0)) {
for (moduleId in moreModules) {
if (__webpack_require__.o(moreModules, moduleId)) {
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if (runtime) var result = runtime(__webpack_require__);
}
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (
__webpack_require__.o(installedChunks, chunkId) &&
installedChunks[chunkId]
) {
installedChunks[chunkId][0]();
}
installedChunks[chunkId] = 0;
}
};
var chunkLoadingGlobal = (self["webpackChunkwebpack5_demo"] =
self["webpackChunkwebpack5_demo"] || []);
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(
null,
chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
);
console.log("www3123", chunkLoadingGlobal);
})();
- 修改chunkLoadingGlobal数组的push方法,变成webpackJsonpCallback
- 当异步文件下载完成执行时,会调用chunkLoadingGlobal.push方法,从而执行webpackJsonpCallback,改变文件状态
- 从而将__webpack_require__.e中的Promise.all返回的promise状态改变,进而能够再运行__webpack_require__方法引入下载成功后保存到__webpack_modules__上的方法
- 最终实现异步模块的导入
- 其中chunkLoadingGlobal的push改变和添加bind
- 既改变了push方法,又保留了原对象的操作方法,也十分巧妙
打包后的异步文件
"use strict";
(self["webpackChunkwebpack5_demo"] = self["webpackChunkwebpack5_demo"] || []).push([["src_a_js"],{
"./src/a.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (\"async a\");\n\n\n//# sourceURL=webpack://webpack5_demo/./src/a.js?");
})
}]);
- 可以知道当下载完成,执行脚本内容的时候就会执行self[“webpackChunkwebpack5_demo”]的push方法,进而调用webpackJsonpCallback通知下载完毕
- 再来看看index.js打包后的代码,__webpack_require__就会执行上面"./src/a.js"对应的方法
eval(
'__webpack_require__.e(/*! import() */ "src_a_js").then(__webpack_require__.bind(__webpack_require__, /*! ./a */ "./src/a.js")).then(console.log);\n\n\n//# sourceURL=webpack://webpack5_demo/./src/index.js?'
);
|