webpack异步加载原理梳理解构

背景:

这几天重新梳理了一下 webpack异步加载的原理,并对实现细节进行了一番拆解,再次让我感叹:真是万变不离其宗,基础知识真的是构建上层建筑的坚实底座,在此也分享给大家,希望大家可以领略到webpack实现异步加载之美

基座一:webpack模块化方案

你可能也吐槽过 webpack产物为啥这么丑?

原始源代码

javascript 复制代码
// src/index.js (入口文件)
let title = require('./title.js')
console.log(title);
// src/title.js
module.exports = 'bu';

构建后产物结构概览

javascript 复制代码
/******/ (() => { // webpack bootstrap 启动函数
/******/ 	var __webpack_modules__ = ([
/* 0 */ /* title.js 模块 */
((module) => {
module.exports = 'bu';
}),
/* 1 */ /* index.js 模块 */
((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
let title = __webpack_require__(0); // 加载模块0
console.log(title);
})
/******/ 	]);
/******/ 	/* 模块缓存 */
/******/ 	var __webpack_module_cache__ = {};
/******/ 	/* Webpack 自实现的 require 函数 */
/******/ 	function __webpack_require__(moduleId) {
/******/ 		/* 检查缓存 */
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		/* 创建新模块并加入缓存 */
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			exports: {}
/******/ 		};
/******/ 		/* 执行模块函数 */
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 		/* 返回模块的 exports 对象 */
/******/ 		return module.exports;
/******/ 	}
/******/ 	/* 启动入口模块 */
/******/ 	return __webpack_require__(1); // 加载入口模块(index.js)
/******/ })()
;

✅ 核心原因:规范转换成本(模块系统适配)

浏览器原生不支持模块规范

浏览器无法直接执行 CommonJS/AMP/UMD 模块语法(如 require() / module.exports / define())。Webpack 必须将这些规范统一转换为浏览器可执行的函数包装形式

  • 整个打包产物 被包裹在一个外层 IIFE 中(立即执行)

    • 意义:创建独立作用域
    javascript 复制代码
     // 未包裹:变量暴露全局
    var utils = {...} // 可能覆盖其他脚本的同名变量
    
    // IIFE 包裹后:
    (function() {
      var utils = {...} // 安全隔离
    })();
  • 每个模块 被转换为标准函数(非立即执行),作为参数传递给运行时:function(module, exports...)

    • 意义模块环境隔离 -每次调用模块函数时都会创建新的:
    javascript 复制代码
    const module = { exports: {} };
    modules[moduleId].call(module.exports, ...);
  • 模块路径被替换为 数字 ID

    • 提升性能 & 减少体积
      • 大幅缩短引用路径
        './src/utils/string-format.js'__webpack_require__(17)
      • 避免路径解析开销:浏览器无需处理文件路径逻辑
  • 原生 require/module 被替换为 Webpack 自实现的 __webpack_require__ 函数

    • 规范统一:将 ESM/CommonJS/AMD 转为浏览器可执行格式
  • 生成复杂的 运行时(runtime)代码 处理模块加载/缓存

    • 避免重复执行(如多次 require 同一模块)
  • 模块字典(所有代码被打包成键值对(键:上面提到的数字ID,值:上面提到的每个模块 被转换的标准函数))

    • 快速索引:通过数字 ID 实现 O(1) 复杂度的模块查找

基座二:jsonp

传统的 JSONP 流程:

sequenceDiagram participant Client as 客户端 participant Server as 服务器 Client->>Client: 创建回调函数 Note right of Client: window.myCallback =
function(data){...} Client->>Server: 动态创建