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]()
});
相关推荐
zqx_737 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai3 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端