webpack如何实现模块化加载

webpack如何管理所有所需模块之间的交互?

  1. 首先webpack添加了一些用于管理模块的代码(称为runtime)
  • __webpack_require__ 是一个模块加载函数,模拟 require 和 import, 该函数接收一个moduleId参数,用于执行js模块。
  • __webpack_require__.m__webpack_modules__对象,该对象存放一个chunk下的module集合
  • 除了上面列举的变量和函数外,还有其他更多.
  1. 当浏览器加载一个业务代码相关的js文件,webpack会向一个全局二维数组中添加一个一维数组,该一维数组的第一项是chunkIds,第二项是一个modules对象,第三项是一个函数。
  • modules对象,该对象的key是moduleId, value是一个函数,该函数的参数是module, __webpack_exports__, __webpack_require__,函数内的代码是一个js文件或者多个js文件内的代码。
  1. 第二项的modules对象会复制到__webpack_modules__对象上。
  2. 执行第三项函数,该函数会通过__webpack_require__[moduleId]执行入口模块代码。
  • __webpack_require__函数会先通过moduleId__webpack_modules__对象上找到入口模块代码,然后执行它。
  1. 入口模块代码中也是通过__webpack_require__[moduleId]加载其他模块

源码详细分析

webpack打包后业务代码结构如下

js 复制代码
// push的一个数组
// 第一项是chunkIds
// 第二项是modules对象,key是moduleId, value是一个函数,函数中就是module的代码
// 第三项是一个函数,会执行入口模块代码
// 代码中用到的__webpack_require__是在runtime中定义的
"use strict";
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[201],{

/***/ 152:
/***/ (() => {

// 一个module不一定是一个js文件,可能是多个js文件合并的
;// CONCATENATED MODULE: ./src/src/test.js
function test() {
    console.log(111)
}
;// CONCATENATED MODULE: ./src/entry.js


test()

/***/ })

},
__webpack_require__ => { // webpackRuntimeModules
     var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
 var __webpack_exports__ = (__webpack_exec__(152)); // 执行入口模块
 }
]);

window.webpackChunkwebpack_demo全局二维数组

数组的每一项是一个chunk

如下图:

  • window上挂载一个变量webpackChunkwebpack_demo;该变量是一个数组

webpack的runtime代码的基本结构

代码执行顺序

  1. 首先加载runtime的js文件,执行该文件
  • 给window.webpackChunkwebpack_demo的push赋值
js 复制代码
var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
javascript 复制代码
在此复习下`Function.prototype.bind()`的用法
        
        Function的实例bind()方法创建一个新函数,
        当调用新函数时,它会调用原始函数并将其this关键字设置为给定的值,
        同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。
  1. 加载业务代码的js文件

  2. 执行入口函数,通过__webpack_require__(moduleId)加载依赖文件

  • 执行window.webpackChunkwebpack_demo.push方法,即执行webpackJsonpCallback方法
js 复制代码
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[792],{},)
  • 执行webpackJsonpCallback
  • 即执行webpackJsonpCallback方法接收的第一个参数是chunkIds,第二个参数是modules对象
  • 将moreModules加入到__webpack_require__.m模块对象中
  • moreModules对象是调用window.webpackChunkwebpack_demo.push()传入的第二个参数,是一个对象,该对象的key是moduleId(一个js文件对应的id),对象的value是该js文件内的代码,被一个函数包裹。webpack将所有用到的文件到存在该对象中。最后放到__webpack_require__.m对象中。
  • 执行window.webpackChunkwebpack_demo.push()传入的第三个参数,第三个参数是一个函数,在该函数中会执行入口文件。
js 复制代码
// 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__);
/******/ 			}
             ....
/******/ 		}

window.webpackChunkwebpack_demo.push()传入的第三个参数如下:

  • 定义了__webpack_exec__,它是一个函数, 参数是moduleId,内部执行__webpack_require__方法,参数是moduleId
  • 执行__webpack_exec__(960),即执行入口js文件。
  • 执行结果赋值给__webpack_exports__
js 复制代码
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
/******/ var __webpack_exports__ = (__webpack_exec__(960));
/******/ }

以下是__webpack_require__函数的定义:

  • __webpack_module_cache__是一个对象,key是moduleId,value是一个对象,该对象上有id和exports两个key,idmoduleIdexports一个引用,指向模块的导出对象。如果一个js模块已经加载,就直接从缓存中取出使用。
  • __webpack_modules____webpack_require__.m是同一个对象
  • 执行__webpack_modules__[moduleId]()就是执行对应的js文件代码
js 复制代码
// 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] = {
/******/ 			id: moduleId,
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
//__webpack_modules__ 就是当前chunkIds下的所有模块集合,用对象表示,key是moduleId,value是一个函数,函数内是js模块代码,执行该函数就是记载一个模块。
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}

以下是将一个js文件中的代码封装到一个方法中的实现

js 复制代码
const fs = require('fs');
const path = require('path');

let file = path.resolve(__dirname, 'bind.js')
fs.readFile(file, 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading the file:', err);
    return;
  }

  let obj = {}
  obj[1] = new Function("" + data)
  obj[1]()
});
相关推荐
掘金者阿豪35 分钟前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen1 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端1 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger3 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4533 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4533 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174463 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css