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:只更新受影响模块链
相关推荐
weixin_382395233 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__3 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.4 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~5 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华6 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子8 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅8 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni9 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学10 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端