基于本文回答

播面 播面

文解图解播客视频,多维讲透八股文
0
评论

Webpack 打包出来的 bundle 结构是怎样的?

Webpack 打包出来的 Bundle(bundle.js)本质上是一个 自执行函数(IIFE, Immediately Invoked Function Expression),它在浏览器端模拟实现了一套类似于 CommonJS / ES Module 的模块加载机制。

为了让你直观理解,我们可以将 Webpack 打包后的 bundle 结构拆解为 四大核心部分


一、 Bundle 的总体骨架(以未压缩的开发模式为例)

简化后的 Bundle 结构如下:

javascript
/********************************************************************/
/**/ (function(modules) { // 1. 接收所有模块组成的字典/数组
/**/ 	// 2. 模块缓存对象
/**/ 	var installedModules = {};
/**/
/**/ 	// 3. Webpack 自己实现的 require 函数(核心 loader 逻辑)
/**/ 	function __webpack_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, __webpack_require__);
/**/
/**/ 		// 标记模块已加载
/**/ 		module.l = true;
/**/
/**/ 		// 返回模块导出内容
/**/ 		return module.exports;
/**/ 	}
/**/
/**/    // 附加一些辅助方法(如处理 ES Module 兼容、异步加载等)
/**/    __webpack_require__.d = function(exports, name, getter) { ... };
/**/    __webpack_require__.r = function(exports) { ... };
/**/
/**/ 	// 4. 执行入口模块(Entry Point)并返回
/**/ 	return __webpack_require__(__webpack_require__.s = "./src/index.js");
/**/ })
/********************************************************************/
/**/ ({
            // 5. 模块字典 (Module Map)
          "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
             // 源码被编译后的代码...
          }),
          "./src/utils.js": (function (module, __webpack_exports__, __webpack_require__) {
             // 源码被编译后的代码...
          })
/****/ });

二、 四大核心组成部分详解

1. 模块字典(Modules Map)

  • 形式:自执行函数的参数,通常是一个 Key-Value 对象(Webpack 5 中可能会优化为数组以提高性能)。
  • Key:模块的唯一标识(通常是相对于项目根目录的路径,如 "./src/index.js",生产环境会被压缩为数字 ID)。
  • Value:一个被匿名函数包裹的模块体。
    • 作用:利用函数作用域隔离模块变量,防止全局污染。
    • 传入参数module(当前模块对象)、exports(导出对象)、__webpack_require__(内部 require 函数)。

2. 模块缓存(Module Cache / installedModules

  • 形式:一个对象,例如 var installedModules = {}(Webpack 5 中为 __webpack_module_cache__)。
  • 作用:记录已经执行过的模块。
  • 效果:当同一个模块被多次 importrequire 时,第二次会直接从缓存中读取 exports不会重复执行模块代码,同时也解决了循环引用的问题。

3. 核心加载器(__webpack_require__

这是 Webpack 实现模块化的基石,它替代了原生的 requireimport

  1. 检查 moduleId 是否在缓存中,在则直接返回。
  2. 不在缓存中,则初始化一个 module = { i: moduleId, l: false, exports: {} }
  3. 从“模块字典”中找到对应的函数并执行,把 module.exports 作为作用域上下文传进去。
  4. 模块内部通过修改 module.exportsexports 来导出内容。
  5. 返回 module.exports

4. 辅助函数(Helper Tools)

Webpack 会在 __webpack_require__ 上挂载很多工具方法,用于处理现代 JS 特性,比如:

  • __webpack_require__.r:给导出对象打上 __esModule 标记,表示这是一个 ES6 模块。
  • __webpack_require__.d:通过 Object.defineProperty 的 getter 实现 ES Module 的动态绑定(Live Bindings)
  • __webpack_require__.e:处理代码分割(Code Splitting)异步加载(import(),本质是通过创建 <script> 标签动态加载 JSONP 文件。

三、 源码转换对比(模块是如何被改写的?)

假设你的源码是 ES Module 规范:

原始源码 (./src/utils.js):

javascript
export const add = (a, b) => a + b;

被 Webpack 打包进 Bundle 后的代码:

javascript
"./src/utils.js": (function (module, __webpack_exports__, __webpack_require__) {
    // 标记这是一个 ES Module
    __webpack_require__.r(__webpack_exports__); 
    // 导出 add 变量
    __webpack_require__.d(__webpack_exports__, "add", function() { return add; });

    const add = (a, b) => a + b;
})

四、 Webpack 5 的 Bundle 结构变化

如果你使用的是 Webpack 5,打出的 bundle 结构会进一步优化:

  1. 更精简的 Runtime:不再强制生成包含所有逻辑的大 IIFE。Webpack 5 会根据你用到的功能(如 Tree Shaking、异步加载),按需生成 Runtime 代码
  2. 顶层作用域清理:使用了更现代的 ES6+ 语法(如箭头函数、const/let)替代传统的函数闭包,体积更小,执行更快。
  3. 更高效的模块标识:在生产模式(production)下,路径 ID 被彻底替换为极短的数字 ID( deterministic 策略)。

总结

Webpack 打包出来的 Bundle 就像一个自我包含的微型操作系统

  • 源码被拆分成一个个被函数包裹的模块(模块字典)。
  • Runtime(运行时脚本)扮演“调度员”的角色,利用 __webpack_require__缓存机制,按照依赖图谱依次执行模块,最终启动整个应用。