深色模式
开发时修改代码,页面自动更新,这是 HMR(Hot Module Replacement)。平时用得理所当然,今天研究了一下原理。
HMR 的整体流程
1. Webpack 监听文件变化(watch 模式)
2. 文件变化 → 重新编译变化的模块
3. 编译完成 → 通过 WebSocket 通知浏览器(发送 hash)
4. 浏览器收到通知 → 向 dev server 请求更新的模块(manifest + chunk)
5. 浏览器接收新模块 → HMR Runtime 替换旧模块
6. 如果替换成功 → 局部更新,页面不刷新
7. 如果替换失败 → 强制刷新整个页面(fallback)webpack-dev-server 的角色
javascript
// webpack-dev-server 做了两件事:
// 1. 启动 HTTP 服务(伺服静态资源)
// 2. 启动 WebSocket 服务(推送更新通知)
// 浏览器端注入的 HMR 客户端代码(bundle 里包含了这段)
// 建立 WebSocket 连接,监听 webpack 的编译事件
const socket = new WebSocket("ws://localhost:8080");
socket.onmessage = (e) => {
const { type, data } = JSON.parse(e.data);
if (type === "hash") {
currentHash = data; // 记录最新 hash
}
if (type === "ok") {
// 编译完成,请求更新
checkForUpdates();
}
};模块替换的实现
javascript
// webpack 编译后的模块都注册在 __webpack_modules__ 对象中
// HMR Runtime 替换就是替换这个对象中的对应函数
// 假设 foo.js 修改了:
__webpack_modules__["./src/foo.js"] = function (module, exports) {
// 这里是新的 foo.js 代码
};
// 然后通知依赖 foo.js 的模块重新执行
// 如果有模块处理了 hot.accept,局部更新
// 否则向上冒泡,直到有模块处理或触发整页刷新module.hot.accept:局部热替换
javascript
// 在模块里声明接受自身的热更新
if (module.hot) {
module.hot.accept("./utils", () => {
// utils.js 更新后,这里的回调被调用
const newUtils = require("./utils");
updateUI(newUtils);
});
}Vue 和 React 的 HMR 之所以"自动",是因为 vue-loader 和 react-refresh 自动帮你注入了 module.hot.accept 逻辑。
vue-loader 怎么处理 HMR
javascript
// vue-loader 编译后,大致注入了这样的代码:
if (module.hot) {
module.hot.accept(); // 接受自身更新
if (!isFirstRender) {
// 用新的组件选项替换旧的
const newOptions = require("./MyComponent.vue");
component.options = newOptions;
// 强制重新渲染
component.__vue_hot__ = Date.now();
}
}保留状态的 HMR
Vue 的 HMR 会保留组件状态(data),只更新模板和方法。
javascript
// 修改 MyComponent.vue 的 template
// HMR 后:data 里的值不变,只有视图更新
// ❌ 但这种情况会重置状态(不得不如此):
// - 修改了 data 的初始值
// - 修改了 created/mounted 钩子CSS 的 HMR
CSS 更简单,style-loader 会直接替换 <style> 标签:
javascript
// style-loader 注入的 HMR 代码
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => {
// 移除旧的 style 标签
styleElement.remove();
});
// 添加新的 style 标签
}小结
- HMR 通过 WebSocket 推送更新通知,再 HTTP 拉取新模块
- 模块替换是更新
__webpack_modules__中对应的函数 module.hot.accept声明接受热更新,vue-loader/react-refresh 自动注入- CSS 热更新直接替换 style 标签,无状态问题
- 替换失败时 fallback 为整页刷新