一、HMR 核心架构

1.1 核心组件
- Webpack Dev Server:提供 HTTP 服务和 WebSocket 通信
- Webpack HMR Runtime:注入到 bundle 中的客户端代码
- Webpack Compiler:负责文件监听和编译
- Webpack HMR Plugin:生成热更新清单(manifest)
二、源码级实现流程
2.1 WebSocket 通信建立
服务端代码 (webpack-dev-server/lib/Server.js)
javascript
// 创建 WebSocket 服务器
setupWebSocketServer() {
this.websocketServer = new WebSocket.Server({
noServer: true,
path: this.options.hot ? "/ws" : undefined
});
// 监听客户端连接
this.websocketServer.on("connection", (client) => {
this.clientConnected(client);
});
}
客户端代码 (webpack/hot/dev-server.js)
javascript
var socket = new WebSocket(
url.format({
protocol: window.location.protocol === "https:" ? "wss" : "ws",
hostname: window.location.hostname,
port: window.location.port,
pathname: "/ws",
slashes: true
})
);
2.2 文件变化监听
javascript
// webpack/lib/Watching.js
compiler.watch(
{
aggregateTimeout: 20,
ignored: /node_modules/
},
(err, stats) => {
// 编译完成回调
this._done(stats);
}
);
2.3 增量更新生成
生成更新清单 (webpack/lib/HotModuleReplacementPlugin.js)
javascript
compilation.hooks.additionalChunkAssets.tap("HotModuleReplacementPlugin", () => {
const records = compilation.getRecords();
const newHash = compilation.hash;
// 生成差异清单
const diff = compareRecords(records, compilation);
const manifest = {
hash: newHash,
updatedChunks: diff.updatedChunks,
removedChunks: diff.removedChunks
};
// 写入 .hot-update.json
this._writeRecords(compilation, manifest);
});
三、模块替换核心逻辑
3.1 客户端更新流程
javascript
// webpack/lib/HotModuleReplacement.runtime.js
function hotCheck(applyOnUpdate) {
return hotDownloadManifest().then(update => {
// 1. 下载差异清单
const chunkIds = Object.keys(update.c);
// 2. 加载更新模块
return Promise.all(chunkIds.map(chunkId => {
return hotDownloadUpdateChunk(chunkId);
})).then(() => {
// 3. 执行模块替换
hotApply(applyOnUpdate);
});
});
}
3.2 模块热替换算法
javascript
function hotApply() {
// 1. 找出失效模块
const outdatedModules = [];
for (const id in hotUpdate) {
if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
outdatedModules.push(id);
}
}
// 2. 删除旧模块缓存
outdatedModules.forEach(id => {
delete require.cache[id];
});
// 3. 执行 accept 回调
outdatedModules.forEach(id => {
const module = require.cache[id];
if (module && module.hot._acceptedDependencies) {
module.hot._acceptedDependencies.forEach(callback => {
callback();
});
}
});
}
四、完整案例演示
4.1 项目结构
lua
project/
├── src/
│ ├── index.js
│ └── counter.js
├── webpack.config.js
└── package.json
4.2 Webpack 配置
javascript
// webpack.config.js
module.exports = {
entry: './src/index.js',
devServer: {
hot: true,
port: 3000
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
4.3 客户端代码
javascript
// src/counter.js
let count = 0;
export function increment() {
count++;
render();
}
function render() {
document.body.innerHTML = `Count: ${count}`;
}
// HMR 接受处理
if (module.hot) {
module.hot.accept('./counter.js', () => {
console.log('Counter module updated');
render();
});
}
4.4 运行效果
- 启动 dev server:
webpack serve
- 修改 counter.js 中的 render 函数
- 观察控制台输出和页面自动更新(无需刷新)
五、核心源码调试技巧
-
在
node_modules/webpack/lib/HotModuleReplacement.runtime.js
设置断点 -
使用 Chrome DevTools 的 Network 面板观察 WebSocket 通信: ws://localhost:3000/ws hot-update.json *.hot-update.js
-
监控模块缓存变化:
javascript// 在控制台输入 Object.keys(require.cache).filter(k => k.includes('counter'))
六、性能优化点
- 增量编译:通过文件 hash 比对实现 O(1) 复杂度更新检测
- 模块级缓存:避免全量重新加载
- WebSocket 二进制传输:使用 msgpack 格式压缩传输数据
- Tree Shaking:只更新受影响模块链