Webpack 运行时代码分析 – 动态加载

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 6 天,点击查看活动详情

文章内容:用 webpack 打包含有动态加载的模块,分析打包后的代码,也就是 webpack 运行时代码。

先思考如下一些问题:

  1. 什么叫做动态加载
  2. 如果不使用 webpack 打包,能做到动态加载吗
  3. webpack 是如何实现动态加载的
  4. webpack 处理动态加载与静态 import 的区别是什么

代码准备

index.js

import test from './test';

console.log("test", test);

const loadSum = () => {
  import('./sum').then(module => {
    console.log(module.default(6, 9))
  })
}

const btn = document.getElementById("btn");
btn.addEventListener("click", loadSum, false);

test.js

export default 'test';

sum.js

const sum = (a, b) => {
  return a + b;
}

export default sum;

webpack.config.js

const path = require('path')
module.exports = {
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'build')
  },
  mode: 'none',
}

在控制台执行

npx webpack

输出 build/main.js 和 build/1.js

build/main.js

/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ('test');

/***/ })
/******/ ]);
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/ 
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/ 
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ 
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = __webpack_modules__;
/******/ 
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __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/runtime/ensure chunk */
/******/ (() => {
/******/ __webpack_require__.f = {};
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = (chunkId) => {
/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
/******/ __webpack_require__.f[key](chunkId, promises);
/******/ return promises;
/******/ }, []));
/******/ };
/******/ })();
/******/ 
/******/ /* webpack/runtime/get javascript chunk filename */
/******/ (() => {
/******/ // This function allow to reference async chunks
/******/ __webpack_require__.u = (chunkId) => {
/******/ // return url for filenames based on template
/******/ return "" + chunkId + ".js";
/******/ };
/******/ })();
/******/ 
/******/ /* webpack/runtime/global */
/******/ (() => {
/******/ __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/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/ 
/******/ /* webpack/runtime/load script */
/******/ (() => {
/******/ var inProgress = {};
/******/ // data-webpack is not used as build has no uniqueName
/******/ // loadScript function to load a script via script tag
/******/ __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) { 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.src = url;
/******/ }
/******/ inProgress[url] = [done];
/******/ var onScriptComplete = (prev, event) => {
/******/ // avoid mem leaks in IE.
/******/ 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);
/******/ };
/******/ })();
/******/ 
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/ 
/******/ /* webpack/runtime/publicPath */
/******/ (() => {
/******/ 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
/******/ }
/******/ }
/******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
/******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
/******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
/******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/?.*$/, "").replace(//[^/]+$/, "/");
/******/ __webpack_require__.p = scriptUrl;
/******/ })();
/******/ 
/******/ /* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ // no baseURI
/******/ 
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
            // installedChunks 存放 已经加载了 和 正在加载 的 chunks
            // 有几种取值
            // 0                          -> 已经加载了的 chunk
            // [resolve, reject, Promise] -> 正在加载的 chunk
            // undefined                  -> chunk 没有加载
            // null                       -> chunk preloaded/prefetched
/******/ var installedChunks = {
/******/ 0: 0
/******/ };
/******/ 
/******/ __webpack_require__.f.j = (chunkId, promises) => {
/******/ // JSONP chunk loading for javascript
/******/ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
/******/ if(installedChunkData !== 0) { // 0 means "already installed".
/******/ 
/******/ // a Promise means "currently loading".
/******/ if(installedChunkData) {
/******/ promises.push(installedChunkData[2]);
/******/ } else {
/******/ if(true) { // all chunks have JS
/******/ // setup Promise in chunk cache
                      // installedChunkData 先赋值为 [resolve, reject]
                      // 注意这里的 resolve,有用,后面在成功加载到 1.js 后就是执行这个 resolve(),改变了 promise 的状态
/******/ var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
                      // installedChunkData -> [resolve, reject, Promise]
                      // 这个时候 promise 实例的状态为 pending
/******/ promises.push(installedChunkData[2] = promise);
/******/ 
/******/ // start chunk loading
                      // 得到需要加载  chunk 的 url
/******/ var url = __webpack_require__.p + __webpack_require__.u(chunkId);
/******/ // create error before stack unwound to get useful stacktrace later
                      // 进行错误处理 -> 未成功加载到某个 chunk
/******/ 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
/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
/******/ } else installedChunks[chunkId] = 0;
/******/ }
/******/ }
/******/ };
/******/ 
/******/ // no prefetching
/******/ 
/******/ // no preloaded
/******/ 
/******/ // no HMR
/******/ 
/******/ // no HMR manifest
/******/ 
/******/ // no on chunks loaded
/******/ 
/******/ // install a JSONP callback for chunk loading
/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
/******/ var [chunkIds, moreModules, runtime] = data;
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ 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["webpackChunk"] = self["webpackChunk"] || [];
/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
/******/ })();
/******/ 
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);


console.log("test", _test__WEBPACK_IMPORTED_MODULE_0__["default"]);

const loadSum = () => {
  __webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(__webpack_require__, 2)).then(module => {
    console.log(module.default(6, 9))
  })
}

const btn = document.getElementById("btn");
btn.addEventListener("click", loadSum, false);

})();

/******/ })()
;

build/1.js

"use strict";
(self["webpackChunk"] = self["webpackChunk"] || []).push([[1],{

/***/ 2:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
const sum = (a, b) => {
  return a + b;
}

/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (sum);


/***/ })

}]);

这里的示例,test.js 是通过静态 import 的方式引入的,sum.js 是通过动态 import 的方式引入的,可以看到一个很明显的区别:

  • test 模块一开始就放到了 __webpack_modules__中,而 sum 模块被单独打包成了一个独立的文件 1.js 中

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="btn">load sum</button>
  <script src="./build/main.js"></script>
</body>
</html>

由于这里涉及到 DOM 的一些操作,所以需要到浏览器中调试,这里给出两种方式:

  1. npm i serve,然后在控制台执行 npx serve
  2. 安装 VS Code 插件 Live Server,点击右下角 go live,打开对应的 html

加载 sum 模块流程分析

sum.js 源码

import('./sum').then(module => {
  console.log(module.default(6, 9))
})

webpack 运行时代码,加载 sum 模块

__webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(__webpack_require__, 2)).then(module => {
  console.log(module.default(6, 9))
})

可以将上部分代码进行拆分一下,更有利于我们分析

const p1 = __webpack_require__.e(/* import() */ 1);
console.log("p1", p1);
const fn = __webpack_require__.bind(__webpack_require__, 2)
console.log("fn", fn);
// p1.then(fn) 这个时候才去执行 fn,拿到返回的 module.export,并且把这个结果再封装成一个 Promise 实例
const p2 = p1.then(fn);
console.log("p2", p2);
p2.then(module => {
  console.log(module.default(6, 9))
})

这是运行后的结果

这样拆解之后,可以看到 import('./sum') 转化为了 __webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(__webpack_require__, 2)) 做了如下一些事情:

  1. __webpack_require__.e(/* import() */ 1)

    • 通过 Promise.all 返回一个 Promise 实例 p1
  2. p1.then(__webpack_require__.bind(__webpack_require__, 2))

    • 当 p1 的状态变为 fulfilled ,执行 .then 中的函数 __webpack_require__(2),这个函数返回对应的 module.exports,被 then 处理后为一个 状态为 fulfilled的 Promise 实例,实例结果值就为 sum 模块导出值

然后再一步一步看看每个环节具体做了哪些事情

准备加载 1.js

__webpack_require__.e

输入:chunkId

输出:通过 Promise.all 返回一个 Promise 实例

作用:通过这个函数调用 __webpack_require__.f.j__webpack_require__.f.j 调用 __webpack_require__.l ,所以后面明确 __webpack_require__.f.j__webpack_require__.l 作用之后,就知道 __webpack_require__.e 作用是啥啦~

/******/ /* webpack/runtime/ensure chunk */
/******/ (() => {
/******/ __webpack_require__.f = {};
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = (chunkId) => {
            // __webpack_require__.f -> { j: function } 
/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
              // 执行 __webpack_require__.f.j
/******/ __webpack_require__.f[key](chunkId, promises);
/******/ return promises;
/******/ }, []));
/******/ };
/******/ })();

这个时候必须要往下执行

__webpack_require__.f.j

输入:chunkId, promises

输出:undefined

作用:为加载 chunk 做一些准备

  1. installedChunks 存放 已经加载了 和 正在加载 的 chunks,标记它们的加载状态
  2. 通过 __webpack_require__.p + __webpack_require__.u(chunkId) 得到需要加载 chunk 的 url
  3. 进行错误处理 -> 未成功加载到某个 chunk
  4. 调用 __webpack_require__.l

注:这里的分析省略了一些逻辑判断

/******/ /* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ // no baseURI
/******/ 
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
            // installedChunks 存放 已经加载了 和 正在加载 的 chunks
            // 有几种取值
            // 0                          -> 已经加载了的 chunk
            // [resolve, reject, Promise] -> 正在加载的 chunk
            // undefined                  -> chunk 没有加载
            // null                       -> chunk preloaded/prefetched
/******/ var installedChunks = {
/******/ 0: 0
/******/ };
/******/ 
/******/ __webpack_require__.f.j = (chunkId, promises) => {
/******/ // JSONP chunk loading for javascript
/******/ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
/******/ if(installedChunkData !== 0) { // 0 means "already installed".
/******/ 
/******/ // a Promise means "currently loading".
/******/ if(installedChunkData) {
/******/ promises.push(installedChunkData[2]);
/******/ } else {
/******/ if(true) { // all chunks have JS
/******/ // setup Promise in chunk cache
                      // installedChunkData 先赋值为 [resolve, reject]
                      // 注意这里的 resolve,有用,后面在成功加载到 1.js 后就是执行这个 resolve(),改变了 promise 的状态
/******/ var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
                      // installedChunkData -> [resolve, reject, Promise]
                      // 这个时候 promise 实例的状态为 pending
/******/ promises.push(installedChunkData[2] = promise);
/******/ 
/******/ // start chunk loading
                      // 得到需要加载  chunk 的 url
/******/ var url = __webpack_require__.p + __webpack_require__.u(chunkId);
/******/ // create error before stack unwound to get useful stacktrace later
                      // 进行错误处理 -> 未成功加载到某个 chunk
/******/ 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
/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
/******/ } else installedChunks[chunkId] = 0;
/******/ }
/******/ }
/******/ };
/******/ 
/******/ })();

这个时候仍然需要继续往下分析

__webpack_require__.l

输入:url, done, key, chunkId

输出:undefined

作用:通过增加 script 标签的方式加载 chunk

/******/ /* webpack/runtime/load script */
/******/ (() => {
/******/ var inProgress = {};
/******/ // data-webpack is not used as build has no uniqueName
/******/ // loadScript function to load a script via script tag
/******/ __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) { 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.src = url;
/******/ }
/******/ inProgress[url] = [done];
/******/ var onScriptComplete = (prev, event) => {
/******/ // avoid mem leaks in IE.
/******/ script.onerror = script.onload = null;
/******/ clearTimeout(timeout);
/******/ var doneFns = inProgress[url];
/******/ delete inProgress[url];
                // 注意这里,当通过 script 加载 chunk 完成之后,会删除这个 script 标签
/******/ 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);
/******/ };
/******/ })();

needAttach && document.head.appendChild(script); 这段代码执行前:

needAttach && document.head.appendChild(script); 这段代码执行后:

如图,__webpack_require__.l 函数执行完,head 中多了 <script charset="utf-8" src="http://localhost:3003/build/1.js"></script>,这个时候就开始加载 1.js 了

加载并执行 1.js

"use strict";
(self["webpackChunk"] = self["webpackChunk"] || []).push([[1],{

/***/ 2:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
const sum = (a, b) => {
  return a + b;
}

/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (sum);


/***/ })

}]);

self["webpackChunk"] || []).push 在最开始的时候的时候就定义了

/******/ var chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || [];
/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));

于是就走进 webpackJsonpCallback 这个函数中


webpackJsonpCallback

输入:parentChunkLoadingFunction, data

输出:undefined

作用:

  1. 将加载到的 sum 模块放到 __webpack_modules__

    • __webpack_require__.m[moduleId] = moreModules[moduleId];
  2. 将 p1 Promise 实例状态变为 fulfilled

    • installedChunks[chunkId][0]();

注意:__webpack_require__.m = __webpack_modules__; 说明它们都指向同一个内存地址,所以 __webpack_require__.m 改变,__webpack_modules__也会跟着改变

/******/ // install a JSONP callback for chunk loading
/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
/******/ var [chunkIds, moreModules, runtime] = data;
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ 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;
/******/ }
/******/ 
/******/ }

加载 sum 模块

p1.then(__webpack_require__.bind(__webpack_require__, 2))

通过上述处理之后,__webpack_modules__已经有 sum 模块了

也就是

// __webpack_modules__ 
([
/* 0 */,
/* 1 */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ('test');

/***/ }),
/* 2 */ 
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
const sum = (a, b) => {
  return a + b;
}

/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (sum);


/***/ })
/******/ ]);
/************************************************************************/

这里就走加载静态 import 一样的逻辑了,可以转到 Webpack 运行时代码分析 – ESM 静态导入模块 这篇文章看一下,这里不再分析。

小结

简单总结一下:__webpack_modules__没有一开始就存放动态加载的模块,而是在满足设定的条件后(比如本示例是点击 load sum 按钮),通过 JSONP 的方式加载动态模块,并且通过 webpackJsonpCallback(JSONP callback)将加载的模块放到 __webpack_modules__中,并将 __webpack_require__.e 返回的 Promise 实例状态变为 fulfilled,然后就可以通过 __webpack_require__ 加载模块了。

补充

文章并没有完全解答开篇提出的问题,有的需要读者自己思考并且实践一下,如果还有问题,可以在评论区留言,我会尽力回答~

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容