Webpack热更新模块-HMR

一、HMR 核心架构

1.1 核心组件

  1. Webpack Dev Server:提供 HTTP 服务和 WebSocket 通信
  2. Webpack HMR Runtime:注入到 bundle 中的客户端代码
  3. Webpack Compiler:负责文件监听和编译
  4. 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 运行效果

  1. 启动 dev server:webpack serve
  2. 修改 counter.js 中的 render 函数
  3. 观察控制台输出和页面自动更新(无需刷新)

五、核心源码调试技巧

  1. node_modules/webpack/lib/HotModuleReplacement.runtime.js 设置断点

  2. 使用 Chrome DevTools 的 Network 面板观察 WebSocket 通信: ws://localhost:3000/ws hot-update.json *.hot-update.js

  3. 监控模块缓存变化:

    javascript 复制代码
    // 在控制台输入
    Object.keys(require.cache).filter(k => k.includes('counter'))

六、性能优化点

  1. 增量编译:通过文件 hash 比对实现 O(1) 复杂度更新检测
  2. 模块级缓存:避免全量重新加载
  3. WebSocket 二进制传输:使用 msgpack 格式压缩传输数据
  4. Tree Shaking:只更新受影响模块链
相关推荐
weifexie29 分钟前
ruby可变参数
开发语言·前端·ruby
千野竹之卫31 分钟前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
sunbyte31 分钟前
初识 Three.js:开启你的 Web 3D 世界 ✨
前端·javascript·3d
半兽先生1 小时前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
南星沐2 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
孙_华鹏3 小时前
手撸一个可以语音操作高德地图的AI智能体
前端·javascript·coze
zhangxingchao3 小时前
Jetpack Compose 动画
前端
@PHARAOH3 小时前
HOW - 缓存 React 自定义 hook 的所有返回值(包括函数)
前端·react.js·缓存
拉不动的猪3 小时前
设计模式之--------工厂模式
前端·javascript·架构
前端开发张小七3 小时前
16.Python递归详解:从原理到实战的完整指南
前端·python