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]()
});
相关推荐
mldong8 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排9 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60619 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角10 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!10 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰11 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi12 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen13 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马14 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒14 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端