webpack code split原理
阿新 • • 發佈:2021-11-06
動態匯入(dynamic import)
當涉及到動態程式碼拆分時,webpack 提供了兩個類似的技術。
- 第一種,也是推薦選擇的方式是,使用符合 ECMAScript 提案 的 import() 語法 來實現動態匯入。
- 第二種,則是 webpack 的遺留功能,使用 webpack 特定的 require.ensure。
當執行程式碼時,遇到import()或require.ensure方法呼叫,會執行webpack的轉換函式__webpack_require__.e,通過script標籤載入相應的js檔案。
1. 載入html頁面,頁面內容包含
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Webpack Code Split</title> <meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="runtime.bundle.js"></script><script defer src="index.bundle.js"></script></head> <body> </body> </html>
2. 載入runtime.bundle.js並執行程式碼
// 檔案底部程式碼 // - chunkLoadingGlobal為常規陣列 var chunkLoadingGlobal = self["webpackChunkgetting_started_using_a_configuration"] = self["webpackChunkgetting_started_using_a_configuration"] || []; chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); // 1. 為chunkLoadingGlobal陣列的push方法繫結執行上下文為chunkLoadingGlobal // 2. 為webpackJsonpCallback函式繫結第一個引數為上一步的push方法 // 3. 將上一步的webpackJsonpCallback函式賦值給chunkLoadingGlobal.push // 這樣,webpackJsonpCallback的第一個引數已經繫結為常規陣列的push方法,而chunkLoadingGlobal.push函式就變成了webpackJsonpCallback方法 chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
3. 載入index.bundle.js
/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */ // 這裡實際上是webpackJsonpCallback的函式呼叫,即push === webpackJsonpCallback // 在webpackJsonpCallback函式內部,引數為該push的3個引數。 // runtime為第三個引數,在函式的底部呼叫這個這個函式,引數為moduleId="./src/index.js"即第二個引數的鍵, // 然後通過__webpack_require__的內部程式碼__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__)去呼叫 // 第二個引數的moduleId所對應值的函式。 // 遇到__webpack_require__.e這個函式,它會去動態載入通過import匯入的檔案。即所謂的code split。 (self["webpackChunkgetting_started_using_a_configuration"] = self["webpackChunkgetting_started_using_a_configuration"] || []).push([["index"],{ /***/ "./src/index.js": /*!**********************!*\ !*** ./src/index.js ***! \**********************/ /***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { eval("async function getComponent() {\n const element = document.createElement('div');\n const { default: _ } = await __webpack_require__.e(/*! import() */ \"vendors-node_modules_lodash_lodash_js\").then(__webpack_require__.t.bind(__webpack_require__, /*! lodash */ \"./node_modules/lodash/lodash.js\", 23));\n\n element.innerHTML = _.join(['Hello', 'webpack'], ' ');\n return element;\n}\n\ngetComponent().then((component) => {\n document.body.appendChild(component);\n});\n\n//# sourceURL=webpack://getting-started-using-a-configuration/./src/index.js?"); /***/ }) }, /******/ __webpack_require__ => { // webpackRuntimeModules /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) /******/ var __webpack_exports__ = (__webpack_exec__("./src/index.js")); /******/ } ]);
- 載入js檔案
webpack_require.e => webpack_require.f.j => webpack_require.l通過這個呼叫鏈,然後script標籤去載入而外的js檔案。
通過結合Promise實現非同步回撥。
/* webpack/runtime/load script */
/******/ (() => {
/******/ var inProgress = {};
/******/ var dataWebpackPrefix = "getting-started-using-a-configuration:";
/******/ // 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];
/******/ 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);
/******/ };
/******/ })();
參考
[-] https://webpack.docschina.org/guides/code-splitting/
[-] https://github.com/YYvanYang/webpack-code-split