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:只更新受影响模块链
相关推荐
福兮说15 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理15 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66616 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)16 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan17 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_17 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒18 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技18 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu19 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
默_笙19 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript