1. 程式人生 > 其它 >webpack code split原理

webpack code split原理

動態匯入(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"));
/******/ }
]);
  1. 載入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