Webpack 在异步请求 JS 文件时获取 JS Bundle 的机制

Webpack 在异步请求 JS 文件时获取 JS Bundle 的机制

Webpack 在处理代码分割(Code Splitting)后产生的异步 chunk(通常是 import() 动态导入)时,浏览器最终是怎么知道要去请求哪个 .js 文件,以及请求回来后怎么正确执行,这个过程主要依赖以下几个核心机制:

核心机制概览(2024~2025 主流方式)

机制主要文件作用谁负责生成当前主流方式
manifestruntime chunkchunkId → 文件名映射表webpack(内置)绝大多数项目都有
publicPathruntime 或配置决定请求的 URL 前缀用户配置 + runtime 推断非常重要
webpack_require.pruntime就是 publicPath 的运行时变量runtime 注入核心变量
webpack_require.uruntimechunkId → chunk 文件名 的函数webpack(新版本更智能)现代主流
webpack_require.eruntime真正发起 chunk 加载的函数webpack异步加载入口
JSONP / script tag浏览器实际发起 .js 请求的方式浏览器仍然是默认(2025年)

详细流程(以最常见的 JSONP + webpackChunkName 方式为例)

1. 代码里写:
   import(/* webpackChunkName: "user-detail" */ './user-detail.js')

2. 打包后生成的文件大致如下:

   - main.js                ← 入口文件 + runtime
   - 123.user-detail.js     ← 异步 chunk(chunkId=123)
   - 456.other-page.js      ← 另一个异步 chunk

3. webpack 在 main.js(或单独的 runtime chunk)中注入了一段类似这样的代码:

   // 简化的伪代码
   var installedChunks = { 0: 0 };  // 已加载的 chunk 标记

   __webpack_require__.e = function requireEnsure(chunkId) {
       var promises = [];

       // 检查是否已经加载过
       if (!installedChunks[chunkId]) {
           var promise = new Promise(function(resolve, reject) {
               // 记录 promise,后面 onload 会 resolve
               var callbacks = installedChunks[chunkId] = [resolve, reject];

               // 重要!决定文件名的地方 ↓↓↓
               var filename = __webpack_require__.u(chunkId);     // ← 得到 "123.user-detail.js"
               var fullUrl = __webpack_require__.p + filename;    // ← publicPath + 文件名

               // 创建 script 标签
               var script = document.createElement('script');
               script.charset = 'utf-8';
               script.timeout = 120;
               script.src = fullUrl;

               // 错误处理
               script.onerror = script.onload = function(event) {
                   // ... 处理成功/失败,把 promise resolve/reject
               };

               document.head.appendChild(script);
           });

           promises.push(promise);
       }

       return Promise.all(promises);
   }

4. 当代码执行到 import() 时,实际上调用的是:
   __webpack_require__.e("123").then(function() {
       // chunk 已经加载完成,可以使用模块了
       var module = __webpack_require__("./src/user-detail.js");
       // ...
   })

几个关键问题解答

问题答案来源说明
文件名是怎么知道的?__webpack_require__.u(chunkId)webpack 打包时把 chunkId → 文件名映射写死或生成函数
请求路径前缀从哪来?__webpack_require__.p (publicPath)通常来自 output.publicPath 配置
publicPath 是相对路径怎么办?runtime 会尝试推断(script.src 位置)现代 webpack 5 有比较智能的推断逻辑
CDN + 版本号怎么办?output.publicPath = ‘https://cdn.com/v1.2.3/’直接写死或通过环境变量注入
如何知道 chunk 加载成功了?script.onload + JSONP 回调chunk 内部会调用 webpackJsonp.push
多个 chunk 同时加载会不会冲突?webpackJsonp 是全局数组,push 的时候带 chunkId基本不会冲突
开发环境和生产环境的区别?开发环境通常用 webpack-dev-server 的内存文件系统 + sockjs生产环境才是真正的 .js 文件请求

2024-2025 年现代趋势对比表

方式chunk 名控制方式publicPath 处理推荐场景备注
JSONP(默认)webpackChunkName / id自动推断 + 配置绝大多数项目兼容性最好
importScripts基本不用Service Worker特殊场景
SystemJS/Federation远程模块名由 host 决定Module Federation微前端
ESM + import()浏览器原生type=”module”实验性、全 ESM 项目未来方向,但目前还需 polyfill

总结一句话

Webpack 异步 chunk 的加载机制本质上是:
通过运行时注入的 __webpack_require__.e 函数 + chunkId → 文件名映射 + publicPath 前缀,动态创建 <script> 标签去请求对应的 js 文件,文件执行后通过全局 webpackJsonp 回调通知 webpack 该 chunk 已就绪。

如果你想深入了解某个特殊场景(CDN 部署、Module Federation、publicPath 动态计算、chunk loading 错误重试、预加载 prefetch/preload 等),可以告诉我,我可以继续展开说明。

文章已创建 3771

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部