Real User Monitoring(真实用户监控,简称 RUM)是指采集真实用户在访问网页时产生的性能数据,例如首屏渲染时间、资源加载耗时、接口响应延迟等。Webpack 5 作为当前主流的前端构建工具,在模块联邦、持久化缓存以及资源处理方面做了大量改进,这些能力可以被巧妙用于降低 RUM 接入成本。过去我们常常需要在业务代码中手动埋点,或使用外部脚本监听性能事件,而 Webpack 5 允许在编译产物中直接植入采集逻辑,让监控与构建流程深度结合。

Webpack 5 构建期织入监控钩子的原理
Webpack 5 的插件体系在 emit 和 compilation 阶段能够访问到完整的模块依赖图与 chunk 信息。通过编写一个简单的插件,我们可以在每一个入口 chunk 的顶部注入一段通用的性能采集代码,而不需要开发者在源码中显式引入。这种做法的核心优势在于监控逻辑与业务代码解耦:当监控方案升级时,只需重新构建即可全量生效,避免了修改分散在各个页面中的埋点文件。
在 RuntimeModule 的机制下,Webpack 5 支持自定义运行时模块。我们可以在运行时模块中读取 __webpack_require__.p 等内部变量,从而得知当前加载的 publicPath 与 chunk 名称。结合浏览器原生的 PerformanceObserver,便能精确记录每一个 chunk 从请求到执行完成的耗时。相比在业务里用 fetch 计时,这种构建期注入的方式不会遗漏动态导入的脚本,也不会受业务异常捕获的影响。
下面的示例展示了一个最简化的插件结构,它在入口文件中追加了监控初始化调用。注意代码中的尖括号在 pre 块内已全部转义,实际编译时插件会将字符串写入资源文件。
class RumInjectPlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap('RumInjectPlugin', (compilation) => {
compilation.hooks.processAssets.tap(
{
name: 'RumInjectPlugin',
stage: compilation.constructor.PROCESS_ASSETS_STAGE_ADDITIONAL
},
(assets) => {
for (const path in assets) {
if (path.endsWith('.js') && path.includes('entry')) {
let source = assets[path].source();
source = '(function(){window.__RUM__=window.__RUM__||{};window.__RUM__.t=Date.now();})();' + source;
assets[path] = new compilation.constructor.sources.RawSource(source);
}
}
}
);
});
}
}
利用 magic comment 标记关键 chunk 并采集命中率
Webpack 5 支持在动态导入语法中使用魔法注释(magic comment),例如 /* webpackChunkName: "chart" */。这些注释不仅影响产物命名,也能被插件读取,用于区分不同功能模块的重要程度。在 RUM 场景中,我们可以给核心业务 chunk 打上特定标记,然后在真实用户访问时统计该 chunk 的加载成功率与缓存命中率,从而判断分包策略是否合理。
很多团队在迁移到 Webpack 5 后盲目开启模块联邦或过度拆分,导致首屏需要串行请求过多小文件。通过 RUM 回收的数据,如果发现标记为高优先级的 chunk 在弱网环境下平均耗时超过预期,就说明应该将该模块合并或改为预加载。我们可以用 import(/* webpackPreload: true */ 'lodash') 这样的语法让 Webpack 5 自动生成 preload 标签,而监控端只需比对带有 preload 与不带 preload 的耗时差异。
下面代码演示了在业务中使用魔法注释,并由运行时将 chunk 名称上报的逻辑片段。这段代码运行在浏览器端,借助 PerformanceResourceTiming 拿到对应脚本的耗时。
// 动态导入并标记名称
import(/* webpackChunkName: "report" */ './report-engine').then(mod => {
const entries = performance.getEntriesByType('resource');
const target = entries.find(e => e.name.includes('report'));
if (target) {
navigator.sendBeacon('/rum', JSON.stringify({
chunk: 'report',
duration: target.duration,
startTime: target.startTime
}));
}
});
结合 PerformanceObserver 与 Webpack 运行时上报真实指标
真实用户监控的价值不在于收集海量数据,而在于拿到可行动的指标。Webpack 5 产物中的运行时记录了模块执行顺序,我们可以在 window 上监听 longtask 与 largest-contentful-paint 事件,再关联当前页面加载了哪些 chunk。举例来说,若用户在某次访问中 LCP 超过四秒,同时监控显示某个未预加载的图表 chunk 耗时占了一半,那么优化方向就非常明确:调整该 chunk 的加载时机。
PerformanceObserver 相比旧的 performance.timing 接口能拿到更细粒度的资源级数据,且不会阻塞主线程。在 Webpack 5 项目中,我们通常把观察者实例化逻辑放在前面提到的注入插件里,这样每个页面都自动具备能力。上报时建议使用 navigator.sendBeacon,它能在页面卸载时可靠发送数据,而不会像 fetch 那样被浏览器取消。
以下示例展示了在运行时模块中注册观察者并关联 Webpack chunk 信息的完整思路。我们在回调里读取全局的 __webpack_chunk_load__ 记录,将性能条目与具体模块绑定后回传。
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
const chunks = window.__LOADED_CHUNKS__ || [];
navigator.sendBeacon('/rum', JSON.stringify({
metric: 'LCP',
value: entry.renderTime || entry.loadTime,
chunks: chunks
}));
}
}
});
obs.observe({ type: 'largest-contentful-paint', buffered: true });
// Webpack 运行时补充记录
__webpack_require__.f.j = function(chunkId, promises) {
window.__LOADED_CHUNKS__ = window.__LOADED_CHUNKS__ || [];
window.__LOADED_CHUNKS__.push(chunkId);
};
通过上述三层配合——构建期注入、魔法注释标记、运行时观察者上报,Webpack 5 项目能够以极低的业务侵入性建立一套真实用户监控体系。团队在检视 RUM 面板时,看到的不再是模糊的整页耗时,而是精确到每个 chunk 的加载轨迹,这对于持续打磨前端体验具有切实意义。
Webpack_5Real_User_Monitoring前端性能监控修改时间:2026-08-16 15:24:35