Open creeperyang opened 1 year ago
项目基本配置可参考基于 https://github.com/taniarascia/webpack-boilerplate.git。
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> <title>webpack-demo</title> <link rel="icon" href="/favicon.ico"> <script defer="defer" src="/js/runtime.dacd45666f2c5eb35b8d.bundle.js"></script> <script defer="defer" src="/js/main.7086fc1df0c0b6a0d989.bundle.js"></script> <link href="/styles/main.64e10d654e2697258d08.css" rel="stylesheet"> </head> <body> <div id="root"></div> </body> </html>
从浏览器角度,html 是应用入口和运行起点。可以看到,编译出的JS通过<script>标签插入了 html。
<script>
/js/runtime.dacd45666f2c5eb35b8d.bundle.js
/js/main.7086fc1df0c0b6a0d989.bundle.js
CONCATENATE MODULE
这里需额外注意到:
defer 异步加载,不阻塞 html 下载和解析,且保证这些 script 按序执行(保证了runtime先执行);按序执行非常重要,runtime 脚本提供了webpack的全局变量/方法,提供了模块加载执行的能力等等。
defer
简单来说,我们写的js文件(模块/module)被webpack处理后变成了什么?
有最简单的 src/js/info.js:
src/js/info.js
export const text = '2021/02/01'
编译后就是dist/js/info.be139a6f62da8ba74d4c.chunk.js:
dist/js/info.be139a6f62da8ba74d4c.chunk.js
"use strict"; (self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[996],{ /***/ 67: /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "text": () => (/* binding */ text) /* harmony export */ }); var text = '2021/02/01'; /***/ }) }]);
为了让编译后的代码保持可读性,webpack的配置去除了压缩。我们可以看到:
代码被包装在 (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 我们的真正代码 };
(__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 我们的真正代码 }
(module) => { 我们的真正代码 }
module|exports|require
__webpack_require__
webpack的编译产出是 chunk,是一个或多个module产出一个chunk。所以我们看到 {moduleId: wrappedModuleCode} 的形式:
{moduleId: wrappedModuleCode}
{67: (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 代码 })}
最终chunk的形式是 (self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push(参数)。
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push(参数)
self["webpackChunkwebpack_demo"].push
[[chunkId], moreModules, runtime]
runtime
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[179],{ /***/ 573: /***/ ((__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) => { "use strict"; ;// CONCATENATED MODULE: ./src/js/title.js var getTitle = function getTitle() { return '柴门闻犬吠,风雪夜归人。'; }; // EXTERNAL MODULE: ./src/js/log.js var log = __webpack_require__(967); var log_default = /*#__PURE__*/__webpack_require__.n(log); ;// CONCATENATED MODULE: ./src/images/logo.svg const logo_namespaceObject = "data:image/svg+xml;base64,..........."; ;// CONCATENATED MODULE: ./src/index.js // Test import of a JavaScript function // Test import of an asset // Test import of styles var logo = document.createElement('img'); logo.src = logo_namespaceObject; var heading = document.createElement('h1'); heading.textContent = getTitle(); var app = document.querySelector('#root'); app.append(logo, heading); setTimeout(function () { __webpack_require__.e(/* import() | info */ 996).then(__webpack_require__.bind(__webpack_require__, 67)).then(function (v) { var footer = document.createElement('footer'); footer.textContent = v.text; app.append(footer); log_default()(); }); }, 100); __webpack_require__.e(/* import() */ 232).then(__webpack_require__.bind(__webpack_require__, 232)).then(function (v) { var div = document.createElement('div'); div.textContent = v.author; app.append(div); }); /***/ }), /***/ 967: /***/ ((module) => { module.exports = function () { console.log('xxxxxxxxx'); }; /***/ }) }, /******/ __webpack_require__ => { // webpackRuntimeModules /******/ /* webpack/runtime/startup prefetch */ /******/ (() => { /******/ __webpack_require__.O(0, [179], () => { /******/ __webpack_require__.E(996); /******/ }, 5); /******/ })(); /******/ /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) /******/ var __webpack_exports__ = (__webpack_exec__(573)); /******/ } ]);
一切魔法都在 webpack runtime 脚本和模块代码的包装中,从 webpack runtime 开始分析编译出的代码,看看一个简单的项目是怎么跑起来的。而我们写的 import/export 这些模块化的代码,是怎么成功跑在浏览器里的。
import/export
这里首先明确一个概念:
import()
SplitChunksPlugin
import/require/<img src=...>
一定程度上,中文“模块”一词可能存在混用以上概念,注意分辨。
然后我们正式来分析 webpack runtime。webpack runtime 代码根据配置/是否有异步模块等不同,稍有不同,但总共不超过几百行代码。
整个webpack runtime是个IIFE(Immediately Invoked Function Expression),整体结构如下:
(() => { // webpackBootstrap "use strict"; var __webpack_modules__ = ({}); /************************************************************************/ // 缓存,缓存了所有模块的 exports var __webpack_module_cache__ = {}; // The require function function __webpack_require__(moduleId) { // 检查是不是在缓存里?在直接返回即可。 var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule !== undefined) { return cachedModule.exports; } // 否则创建 module,并放到缓存。 var module = __webpack_module_cache__[moduleId] = { // no module.id needed // no module.loaded needed exports: {} }; // 执行对应的模块,执行完成后 epxorts 就有值了 __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__; // 下面是一堆自执行函数(IIFE): // 1. 为__webpack_require__添加各种方法; // 2. 最终往window上添加 webpackChunkwebpack_demo 数组(最终是否叫webpackChunkwebpack_demo取决于config)。 })()
定义了 __webpack_modules__ 来存储所有模块,定义了 __webpack_require__ 来作为模块中用到的 require 方法。
__webpack_modules__
require
通过一堆 IIFE 在 __webpack_require__ 上定义了一堆工具函数,这些函数可以被编译的模块去使用。
最后一个IIFE中定义了一个唯一全局变量 'webpackChunkwebpack_demo',该变量为数组,提供 push 方法来加载 chunk。
'webpackChunkwebpack_demo'
output.chunkLoadingGlobal
'webpackChunkwebpack'
webpackChunkwebpack_demo.push
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[id],{id:code},runtime])
最终我们的入口 chunk 执行时,通过webpackChunkwebpack_demo.push来最终执行业务代码。
webpackJsonpCallback
webpackChunkwebpack_demo.push 是什么?webpackChunkwebpack_demo.push 最终调用 webpackJsonpCallback。
// self["webpackChunkwebpack_demo"]一般为undefined,初始化为空数组;如果不为空则保留。 var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []; // 假如 self["webpackChunkwebpack_demo"] 之前非空,那么对其每项调用 webpackJsonpCallback, // 把相关 chunks & modules 存入缓存(module未执行,后面的 startup 真正执行)。 // 这里 0 作为参数传入,即 parentChunkLoadingFunction 是0,防止了 webpackJsonpCallback 执行时元素重复压入chunkLoadingGlobal。 chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); // 设置 chunkLoadingGlobal数组的push方法(这是其它模块的wrapper function); // webpackJsonpCallback 绑定 parentChunkLoadingFunction 为 chunkLoadingGlobal.push, 元素被压入chunkLoadingGlobal chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
来具体看看 webpackJsonpCallback 作用是什么。请结合上面的 initial chunk 代码来看(看调用chunkLoadingGlobal.push的传参)。
chunkLoadingGlobal.push
var __webpack_modules__ = ({}); // expose the modules object (__webpack_modules__) __webpack_require__.m = __webpack_modules__; // 缓存所有加载完成/加载中的模块,0代表加载完成 // object to store loaded and loading chunks // undefined = chunk not loaded, null = chunk preloaded/prefetched // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded var installedChunks = { 666: 0 }; /** * install a JSONP callback for chunk loading * @params {function|0} parentChunkLoadingFunction,可能是数组的push方法,也可能是0 * @params {Array} data 格式是 [[moduleId], {moduleId: moduleCode}, runtime] * * moduleId: 数字;moduleCode:wrapper function包裹的模块代码;runtime: (__webpack_require__)=>any **/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { // "moreModules" 就是当前加载的chunk自带的模块,包括自身代码的模块、CONCATENATED过来的模块、其它依赖模块。 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; // 当前加载的chunk一般不会是0,所以执行这个if内逻辑,把moreModules添加到__webpack_modules__ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { for(moduleId in moreModules) { // moreModules 形式为 {id: wrappedCode},我们直接把它们挂载到 __webpack_modules__ // 方便之后__webpack_require__(id) 可以正确执行对应模块。 if(__webpack_require__.o(moreModules, moduleId)) { __webpack_require__.m[moduleId] = moreModules[moduleId]; } } // 然后看有需要执行的代码就直接执行 if(runtime) var result = runtime(__webpack_require__); } // push 到 chunkLoadingGlobal 数组 if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; // 如果这个chunk是loading状态,则resolve掉它,方便通知之前等待该chunk的代码继续执行。 if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { installedChunks[chunkId][0](); } // 标记这个chunk加载完成 installedChunks[chunkId] = 0; } // 那么__webpack_require__.O在干嘛?下一小节看 return __webpack_require__.O(result); } var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []; chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); // chunkLoadingGlobal.push.bind(chunkLoadingGlobal) 作为 parentChunkLoadingFunction,此时是 chunkLoadingGlobal 的数组push方法 chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
webpackJsonpCallback 作为真正的业务模块调用入口,主要就是把 push 进来的 chunk 指定的模块加载到 __webpack_modules__,并执行 chunk 指定的业务代码(模块)。
__webpack_require__.O
webpackJsonpCallback 中只剩__webpack_require__.O没搞明白作用,这一小节来解释下。
/* webpack/runtime/chunk loaded */ (() => { var deferred = []; __webpack_require__.O = (result, chunkIds, fn, priority) => { if(chunkIds) { priority = priority || 0; for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; deferred[i] = [chunkIds, fn, priority]; return; } // 以我们下面的传参为例,这里deferred是 [[179], () => { // __webpack_require__.E(996); // }, 5] var notFulfilled = Infinity; for (var i = 0; i < deferred.length; i++) { var [chunkIds, fn, priority] = deferred[i]; var fulfilled = true; for (var j = 0; j < chunkIds.length; j++) { // 5 & 1 是1,但是 Infinity >= 5 成立, // __webpack_require__.O 上只有 j,是检查chunkId对应的chunk是否已加载,这里第一次不成立而第二次成立 if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { chunkIds.splice(j--, 1); } else { fulfilled = false; if(priority < notFulfilled) notFulfilled = priority; } } // 第一次不成立,而第二次可以执行 if(fulfilled) { deferred.splice(i--, 1) var r = fn(); // 第二次其实是执行 __webpack_require__.E(996),即prefetch 996 if (r !== undefined) result = r; } } return result; }; __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); })();
多数时候 __webpack_require__.O 接收的参数全部是 undefined,即什么都不干。所以略过它多数时候也不影响理解webpack流程。但我们的入口 chunk 其实会传入不一样的参数,我们来看下:
undefined
__webpack_require__ => { // webpackRuntimeModules /* webpack/runtime/startup prefetch */ (() => { __webpack_require__.O(0, [179], () => { __webpack_require__.E(996); }, 5); })(); var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) var __webpack_exports__ = (__webpack_exec__(573)); }
这是initial chunk的runtime函数执行时,会调用__webpack_require__.O,并且是在业务代码执行前。然后等webpackJsonpCallback执行时,__webpack_require__.O会执行第二次,此时会开始 prefetch id 为 996 的 chunk。
996
这符合预期:prefetch 在父 chunk 加载完成后开始。
__webpack_require__.F
__webpack_require__.E
/* webpack/runtime/chunk prefetch function */ (() => { __webpack_require__.F = {}; __webpack_require__.E = (chunkId) => { Object.keys(__webpack_require__.F).map((key) => { __webpack_require__.F[key](chunkId); }); } })(); __webpack_require__.F.j = (chunkId) => { // 如果 chunkId 不是 666(runtime chunk),也没有加载过 if((!__webpack_require__.o(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && 666 != chunkId) { // null 代表 chunk preloaded/prefetched installedChunks[chunkId] = null; var link = document.createElement('link'); if (__webpack_require__.nc) { link.setAttribute("nonce", __webpack_require__.nc); } link.rel = "prefetch"; link.as = "script"; link.href = __webpack_require__.p + __webpack_require__.u(chunkId); document.head.appendChild(link); } };
代码一目了然。
__webpack_require__.l
__webpack_require__.f.j
__webpack_require__.e
这里讲讲 webpack 最基本的怎么加载 chunk 的(jsonp)。
/* webpack/runtime/load script */ (() => { var inProgress = {}; var dataWebpackPrefix = "webpack-demo:"; // 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 || 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) => { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var doneFns = inProgress[url]; // 加载完成就删除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/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; }, [])); }; })(); // import()|require.ensure 的核心实现代码 __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) { // 正在加载,那么取出promise放到 promises 即可 promises.push(installedChunkData[2]); } else { if(666 != chunkId) { // setup Promise in chunk cache var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); promises.push(installedChunkData[2] = promise); // start chunk loading 构造异步模块的正确加载路径(publicPath + filename) var url = __webpack_require__.p + __webpack_require__.u(chunkId); // create error before stack unwound to get useful stacktrace later var error = new Error(); // 只需要处理加载失败的问题(因为chunk代码执行会更新installedChunks[chunkId]为0) var loadingEnded = (event) => { if(__webpack_require__.o(installedChunks, chunkId)) { installedChunkData = installedChunks[chunkId]; // 加入没有加载成功(chunk代码执行会更新installedChunks[chunkId]为0), // 则置为 undefined(chunk not loaded) 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; } } };
__webpack_require__.o
__webpack_require__.r
__webpack_require__.d
1. __webpack_require__.o 即 hasOwnProperty
hasOwnProperty
/* webpack/runtime/hasOwnProperty shorthand */ (() => { __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) })();
2. __webpack_require__.r
给 exports 对象加上 __esModule=true 等标记。
exports
__esModule=true
/* 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 }); }; })();
3. __webpack_require__.d
给 exports 对象加上我们导出的那些属性/方法(其它模块通过 import 来使用)。注意,这里是通过 getter 来导出。
import
getter
/* 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] }); } } }; })();
(一)从打包产物——html 开始
从浏览器角度,html 是应用入口和运行起点。可以看到,编译出的JS通过
<script>
标签插入了 html。/js/runtime.dacd45666f2c5eb35b8d.bundle.js
是 webpack 提供的运行时,是编译出代码能运行的基础;/js/main.7086fc1df0c0b6a0d989.bundle.js
是我们的入口模块(可能因为CONCATENATE MODULE
的优化,除了入口module外有其它module的代码)。这里需额外注意到:
defer
异步加载,不阻塞 html 下载和解析,且保证这些 script 按序执行(保证了runtime先执行);按序执行非常重要,runtime 脚本提供了webpack的全局变量/方法,提供了模块加载执行的能力等等。(二)打包产物——模块/chunk 是怎么包装的?
简单来说,我们写的js文件(模块/module)被webpack处理后变成了什么?
有最简单的
src/js/info.js
:编译后就是
dist/js/info.be139a6f62da8ba74d4c.chunk.js
:为了让编译后的代码保持可读性,webpack的配置去除了压缩。我们可以看到:
代码被包装在
(__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 我们的真正代码 }
;(module) => { 我们的真正代码 }
,这取决于代码是否 ES Module 形式的等等。module|exports|require
的环境,保证了我们代码能正常运行。当然这三个变量webpack会编译并匹配包装函数。__webpack_require__
上提供了一些工具函数,具体是什么下个章节讨论。webpack的编译产出是 chunk,是一个或多个module产出一个chunk。所以我们看到
{moduleId: wrappedModuleCode}
的形式:{67: (__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 代码 })}
。最终chunk的形式是
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push(参数)
。self["webpackChunkwebpack_demo"].push
保证了数组里面如果有代码需要运行是可以运行的,而不是静态的数组。[[chunkId], moreModules, runtime]
,runtime
就是在 push 时可以运行的代码。看看entry point编译出的 initial chunk 会是什么样?
(三)打包产物——webpack runtime 有哪些功能
一切魔法都在 webpack runtime 脚本和模块代码的包装中,从 webpack runtime 开始分析编译出的代码,看看一个简单的项目是怎么跑起来的。而我们写的
import/export
这些模块化的代码,是怎么成功跑在浏览器里的。这里首先明确一个概念:
import()
或者SplitChunksPlugin
产生。import/require/<img src=...>
等等。一定程度上,中文“模块”一词可能存在混用以上概念,注意分辨。
然后我们正式来分析 webpack runtime。webpack runtime 代码根据配置/是否有异步模块等不同,稍有不同,但总共不超过几百行代码。
webpack runtime 结构
整个webpack runtime是个IIFE(Immediately Invoked Function Expression),整体结构如下:
定义了
__webpack_modules__
来存储所有模块,定义了__webpack_require__
来作为模块中用到的require
方法。通过一堆 IIFE 在
__webpack_require__
上定义了一堆工具函数,这些函数可以被编译的模块去使用。最后一个IIFE中定义了一个唯一全局变量
'webpackChunkwebpack_demo'
,该变量为数组,提供 push 方法来加载 chunk。output.chunkLoadingGlobal
等配置确定(默认值是'webpackChunkwebpack'
);webpackChunkwebpack_demo.push
来执行,或者说所有的模块被它包装:(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[id],{id:code},runtime])
最终我们的入口 chunk 执行时,通过
webpackChunkwebpack_demo.push
来最终执行业务代码。webpack runtime 中的工具函数解析
核心的
'webpackChunkwebpack_demo'
(即webpackJsonpCallback
)webpackChunkwebpack_demo.push
是什么?webpackChunkwebpack_demo.push
最终调用webpackJsonpCallback
。来具体看看
webpackJsonpCallback
作用是什么。请结合上面的 initial chunk 代码来看(看调用chunkLoadingGlobal.push
的传参)。webpackJsonpCallback
作为真正的业务模块调用入口,主要就是把 push 进来的 chunk 指定的模块加载到__webpack_modules__
,并执行 chunk 指定的业务代码(模块)。__webpack_require__.O
在干什么?webpackJsonpCallback
中只剩__webpack_require__.O
没搞明白作用,这一小节来解释下。多数时候
__webpack_require__.O
接收的参数全部是undefined
,即什么都不干。所以略过它多数时候也不影响理解webpack流程。但我们的入口 chunk 其实会传入不一样的参数,我们来看下:这是initial chunk的runtime函数执行时,会调用
__webpack_require__.O
,并且是在业务代码执行前。然后等webpackJsonpCallback
执行时,__webpack_require__.O
会执行第二次,此时会开始 prefetch id 为996
的 chunk。这符合预期:prefetch 在父 chunk 加载完成后开始。
__webpack_require__.F
和__webpack_require__.E
怎么配合完成 prefetch/preload代码一目了然。
重要的jsonp加载chunk相关
__webpack_require__.l
&__webpack_require__.f.j
&__webpack_require__.e
这里讲讲 webpack 最基本的怎么加载 chunk 的(jsonp)。
显而易见的
__webpack_require__.o
&__webpack_require__.r
&__webpack_require__.d
1.
__webpack_require__.o
即hasOwnProperty
2.
__webpack_require__.r
给
exports
对象加上__esModule=true
等标记。3.
__webpack_require__.d
给
exports
对象加上我们导出的那些属性/方法(其它模块通过import
来使用)。注意,这里是通过getter
来导出。