Jarweb / thinking-in-deep

一些思考

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

webpack 模块加载分析

Jarweb opened this issue · comments

// 简单版的 webpack runtime,有很多 runtime 函数没有加入的
// modules 是所有的模块集合,通过 url 为 key,模块函数为 value

(function (modules) {
  // The module cache
  var installedModules = {};

  // 根据 moduleId 返回对应的模块对象
  function require(moduleId) {
    // 缓存中是否存在
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }

    // 构建初始对象
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false, // 是否已缓存
      exports: {}
    };

    // 在所有的模块里找到相应模块,传入一些参数执行
    modules[moduleId].call(module.exports, module, module.exports, require);
    // 执行完后,标记该模块已缓存
    module.l = true;
    // 返回该模块出去
    return module.exports;
  }

  // 将所有模块挂到 require 函数上
  require.m = modules;
  // 将缓存的模块挂到 require 函数上
  require.c = installedModules;
  // 定义一个设置 getter 的函数,并挂到 require 函数上
  require.d = function (exports, name, getter) {
    if (!require.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
  };
  // define __esModule on exports
  require.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };

  // create a fake namespace object
  // mode & 1: value is a module id, require it
  // mode & 2: merge all properties of value into the ns
  // mode & 4: return value when already ns object
  // mode & 8|1: behave like require
  require.t = function (value, mode) {
    if (mode & 1) value = require(value);
    if (mode & 8) return value;
    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    var ns = Object.create(null);
    require.r(ns);

    Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    if (mode & 2 && typeof value != 'string') for (var key in value) require.d(ns, key, function (key) { return value[key]; }.bind(null, key));
    return ns;
  };

  // getDefaultExport function for compatibility with non-harmony modules
  require.n = function (module) {
    var getter = module && module.__esModule ?
      function getDefault() { return module['default']; } :
      function getModuleExports() { return module; };
    require.d(getter, 'a', getter);
    return getter;
  };

  // Object.prototype.hasOwnProperty.call
  require.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

  // __webpack_public_path__
  require.p = "";

  // Load entry module and return exports
  return require(require.s = "./index.js"); // 执行入口文件
})({
  "./a.js": (function (module, export, require) {
    eval("
      require.r(export);
      require.d(export, \"add\", function() { return add; });
      var bjs = require(\"./b.js\");
      function add(a, b) {return Object(bjs[\"sum\"])() + a + b};
    ");
  }),
  "./b.js": (function (module, export, require) {
    eval("
      require.r(export);
      require.d(export, \"sum\", function() { return sum; });
      function sum() {return 100}
    ");
  }),
  "./index.js": (function (module, export, require) {
    eval("
      require.r(export);
      var ajs = require(\"./a.js\");
      Object(ajs[\"add\"])(1, 2)
    ");
  })
});
          
                          
// 构建初始对象
// {
//   i: "./index.js", // url 就是 moduleId
//   l: false,
//   exports: { }
// }
// 
// modules[moduleId].call(module.exports, module, module.exports, require);
// modules[moduleId] 是一个包装过的函数
// 
// function (module, export , require) {
//   eval("
//     require.r(export);
//     var ajs = require(\"./a.js\");
//     Object(ajs[\"add\"])(1, 2)
//  ");
// }
// 该函数绑定 this 为初始的模块对象,这里是空对象 {}
// 传入参数为:module, module.exports, require
//   eval("
//     require.r(export); // 为模块对象定义一个 esModule 的 key
//     var ajs = require(\"./a.js\"); // 加载当前模块的依赖 ajs,会一直加载依赖的依赖
//     Object(ajs[\"add\"])(1, 2) // 执行模块
//  ");
// 
// var ajs = require(\"./a.js\");
// eval("
//     require.r(export ); // 为模块对象定义一个 esModule 的 key
//     require.d(export, \"add\", function() { return add; }); // 为模块的 add 属性定义一个 getter 属性,当模块被调用时,读取模块上的 add
//     var bjs = require(\"./b.js\"); // 加载当前的模块依赖 bjs
//     function add(a, b) {return Object(bjs[\"sum\"])() + a + b};
// ");
// 
// var bjs = require(\"./b.js\");
// eval("
//     require.r(export ); // // 为模块对象定义一个 esModule 的 key
//     require.d(export, \"sum\", function() { return sum; }); // 为模块的 add 属性定义一个 getter 属性
//     这是最后的依赖模块,没有依赖了,然后回去执行入口模块的逻辑
//     function sum() { return 100 } 
// ");
// 复杂版的 runtime
(function (modules) {
	// 这个函数很重要,用来加载初始化模块的
	// 所有后续加载的模块都是通过 window["webpackJsonp"].push(...) 来实现的
	// 这个 push 函数就是 webpackJsonpCallback
  // 当 push 一个 data 时,其实就是执行,维护一个当前 chunk 的数组,数组的每个元素都是模块函数,通过索引来查找,索引是模块的 id,所以该数组不是连续的
	// data:是什么?push([['chunkid'],{该 chunk 包含的所有模块}, ['入口模块id',‘依赖’]])
	function webpackJsonpCallback(data) {
		var chunkIds = data[0];
		var moreModules = data[1];
		var executeModules = data[2];

		// add "moreModules" to the modules object,
		// then flag all "chunkIds" as loaded and fire callback
		var moduleId, chunkId, i = 0, resolves = [];
		for (; i < chunkIds.length; i++) {
			chunkId = chunkIds[i];
			if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
				resolves.push(installedChunks[chunkId][0]);
			}
			installedChunks[chunkId] = 0;
		}
		for (moduleId in moreModules) {
			if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
				modules[moduleId] = moreModules[moduleId];
			}
		}
		if (parentJsonpFunction) parentJsonpFunction(data);

		while (resolves.length) {
			resolves.shift()();
		}

		// add entry modules from loaded chunk to deferred list
		deferredModules.push.apply(deferredModules, executeModules || []);
		// run deferred modules when all chunks ready
		return checkDeferredModules();
	};

	function checkDeferredModules() {
		var result;
		for (var i = 0; i < deferredModules.length; i++) {
			var deferredModule = deferredModules[i];
			var fulfilled = true;
			for (var j = 1; j < deferredModule.length; j++) {
				var depId = deferredModule[j];
				if (installedChunks[depId] !== 0) fulfilled = false;
			}
			if (fulfilled) {
				deferredModules.splice(i--, 1);
				result = webpackRequire(webpackRequire.s = deferredModule[0]);
			}
		}
		return result;
	}

	// The module cache
	var installedModules = {};
	// object to store loaded and loading chunks
	// undefined = chunk not loaded, null = chunk preloaded/prefetched
	// Promise = chunk loading, 0 = chunk loaded
	var installedChunks = {
		"runtime": 0
	};
	var deferredModules = [];

	// script path function
	function jsonpScriptSrc(chunkId) {
		return webpackRequire.p + "static/js/" + ({}[chunkId] || chunkId) + "." + { "1": "bbc47b9f", "2": "9b6415cc", "3": "a9a988f4" }[chunkId] + ".chunk.js"
	}

	// The require function
	function webpackRequire(moduleId) {
		// Check if module is in cache
		if (installedModules[moduleId]) {
			return installedModules[moduleId].exports;

		}
		// Create a new module (and put it into the cache)
		var module = installedModules[moduleId] = {
			i: moduleId,
			l: false,
			exports: {}

		};

		// Execute the module function
		modules[moduleId].call(module.exports, module, module.exports, webpackRequire);

		// Flag the module as loaded
		module.l = true;

		// Return the exports of the module
		return module.exports;
	}

	// This file contains only the entry chunk.
	// The chunk loading function for additional chunks
	webpackRequire.e = function requireEnsure(chunkId) {
		var promises = [];
		// JSONP chunk loading for javascript
		var installedChunkData = installedChunks[chunkId];
		if (installedChunkData !== 0) { // 0 means "already installed".

			// a Promise means "currently loading".
			if (installedChunkData) {
				promises.push(installedChunkData[2]);

			} else {
				// setup Promise in chunk cache
				var promise = new Promise(function (resolve, reject) {
					installedChunkData = installedChunks[chunkId] = [resolve, reject];

				});
				promises.push(installedChunkData[2] = promise);

				// start chunk loading
				var script = document.createElement('script');
				var onScriptComplete;

				script.charset = 'utf-8';
				script.timeout = 120;
				if (webpackRequire.nc) {
					script.setAttribute("nonce", webpackRequire.nc);

				}
				script.src = jsonpScriptSrc(chunkId);

				// create error before stack unwound to get useful stacktrace later
				var error = new Error();
				onScriptComplete = function (event) {
					// avoid mem leaks in IE.
					script.onerror = script.onload = null;
					clearTimeout(timeout);
					var chunk = installedChunks[chunkId];
					if (chunk !== 0) {
						if (chunk) {
							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;
							chunk[1](error);
						}
						installedChunks[chunkId] = undefined;
					}
				};
				var timeout = setTimeout(function () {
					onScriptComplete({ type: 'timeout', target: script });
				}, 120000);
				script.onerror = script.onload = onScriptComplete;
				document.head.appendChild(script);
			}
		}
		return Promise.all(promises);
	};

	// expose the modules object (__webpack_modules__)
	webpackRequire.m = modules;

	// expose the module cache
	webpackRequire.c = installedModules;

	// define getter function for harmony exports
	webpackRequire.d = function (exports, name, getter) {
		if (!webpackRequire.o(exports, name)) {
			Object.defineProperty(exports, name, { enumerable: true, get: getter });
		}
	};

	// define __esModule on exports
	webpackRequire.r = function (exports) {
		if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
		}
		Object.defineProperty(exports, '__esModule', { value: true });
	};

	// create a fake namespace object
	// mode & 1: value is a module id, require it
	// mode & 2: merge all properties of value into the ns
	// mode & 4: return value when already ns object
	// mode & 8|1: behave like require
	webpackRequire.t = function (value, mode) {
		if (mode & 1) value = webpackRequire(value);
		if (mode & 8) return value;
		if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
		var ns = Object.create(null);
		webpackRequire.r(ns);
		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
		if (mode & 2 && typeof value != 'string') for (var key in value) webpackRequire.d(ns, key, function (key) { return value[key]; }.bind(null, key));
		return ns;
	};

	// getDefaultExport function for compatibility with non-harmony modules
	webpackRequire.n = function (module) {
		var getter = module && module.__esModule ?
			function getDefault() { return module['default']; } :
			function getModuleExports() { return module; };
		webpackRequire.d(getter, 'a', getter);
		return getter;
	};

	// Object.prototype.hasOwnProperty.call
	webpackRequire.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

	// __webpack_public_path__
	webpackRequire.p = "/static/app/";

	// on error function for async loading
	webpackRequire.oe = function (err) { console.error(err); throw err; };

        // push 其实就是 runtime 里的 webpackJsonpCallback
	var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
	jsonpArray.push = webpackJsonpCallback;
	jsonpArray = jsonpArray.slice();
	for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
	var parentJsonpFunction = oldJsonpFunction;

	// run deferred modules from other chunks
	checkDeferredModules();
})([]);
// 一般打包出来的模块形式:
// polyfill.js // dll
// vender.js // dll
// inline runtime 
// jsonp chunk // 通过 jsonp 形式加载的
// jsonp chunk
// ...

模块数组

  • 索引是模块 id,值的模块函数,该数组是不连续的
  • 应用的所有模块都在这个数组上
  • 当入口执行时,需要用到的依赖会在这个数组里,通过 require 传给入口函数
  • 每个文件就是一个模块
  • 开发环境中热更新是通过替换这个数组上相应索引的值来实现的。即 webpack 的热更新是模块级别的。没有 diff-patch

image-20200527165646696

一个异步加载示例

// jsonp 加载的模块
window["webpackJsonp"].push([
	["offLine"], // 当前的模块名称
	{
                // 依赖
		"./src/assets/onLineTest.txt": (function (module, exports, __webpack_require__) {
			module.exports = __webpack_require__.p + "static/media/onLineTest.d41d8cd9.txt";
		}),
                // 模块源码
		"./src/utils/offLine.ts": (function (module, exports, __webpack_require__) {
			function testOnLine() {}

			if (window.navigator) {
				setTimeout(function () {
					testOnLine()
				}, 2000);
			}
		})
	},
	[["./src/utils/offLine.ts", "runtime"]]
]);

复杂的异步加载示例

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
	["guard_pay"], // 当前模块名称
	{
    // react-dom 依赖
		"./node_modules/react-dom/index.js": (function (module, exports, __webpack_require__) {
			module.exports = (__webpack_require__("dll-reference vendor_6f16b710b6ec15e318df"))(495);
		}),
		// react 依赖
		"./node_modules/react/index.js": (function (module, exports, __webpack_require__) {
			module.exports = (__webpack_require__("dll-reference vendor_6f16b710b6ec15e318df"))(163);
		}),

		// .... 忽略很多依赖

		"./src/utils/throttle.ts": (function (module, __webpack_exports__, __webpack_require__) {
			__webpack_require__.r(__webpack_exports__);
			__webpack_require__.d(__webpack_exports__, "default", function () { return throttle; });

			function throttle(fn, wait) { var timer; return function () { if (timer) return; fn.apply(void 0, arguments); timer = setTimeout(function () { clearTimeout(timer); timer = null; }, wait || 500); }; }
		}),

		// .... 忽略很多依赖

		"./src/apps/GuardPay.tsx": function (module, __webpack_exports__, __webpack_require__) {
			__webpack_require__.r(__webpack_exports__);
			// 入口文件的依赖
			var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./node_modules/react/index.js");
			var react__WEBPACK_IMPORTED_MODULE_1___default = __webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
			var react_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./node_modules/react-dom/index.js");
			var react_dom__WEBPACK_IMPORTED_MODULE_2___default = __webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_2__);
			var _ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__("./src/ErrorBoundary.tsx");
			var _pages_VipCenter_GuardPay__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__("./src/pages/VipCenter/GuardPay/index.tsx");
			var _hooks_useDebug__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__("./src/hooks/useDebug/index.ts");
			var _hooks_useDocumentTitle__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__("./src/hooks/useDocumentTitle/index.ts");
			// app 组件
			var App = Object(_utils_checkNetWork__WEBPACK_IMPORTED_MODULE_3__["default"])(
				function () {
					Object(_hooks_useDocumentTitle__WEBPACK_IMPORTED_MODULE_9__["default"])('title');
					Object(_hooks_useDebug__WEBPACK_IMPORTED_MODULE_8__["default"])();

					return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div",
						{ className: _pages_VipCenter_GuardPay_style_module_scss__WEBPACK_IMPORTED_MODULE_5___default.a.page },
						react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(_pages_VipCenter_GuardPay__WEBPACK_IMPORTED_MODULE_7__["default"], null));
				}
			);
      
      // render dom	
react_dom__WEBPACK_IMPORTED_MODULE_2___default.a.render(react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(_ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__["default"], null, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(App, null)), document.getElementById('root'));
		}),

    // 入口文件,一般标记为 0 
		0: (function (module, exports, __webpack_require__) {
			module.exports = __webpack_require__("./src/apps/GuardPay.tsx");
		}),

		"dll-reference lib_e4f1cebcccf7c26ba350": (function (module, exports) {
			module.exports = lib_e4f1cebcccf7c26ba350;
		}),

		"dll-reference polyfill_73fb46439efc9a918d08": (function (module, exports) {
			module.exports = polyfill_73fb46439efc9a918d08;
		}),

		"dll-reference vendor_6f16b710b6ec15e318df": (function (module, exports) {
			module.exports = vendor_6f16b710b6ec15e318df;
		})
	}, 
	[[0, "runtime", "babelHelper", 0]]
]);