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:只更新受影响模块链
相关推荐
apcipot_rain2 小时前
【应用密码学】实验五 公钥密码2——ECC
前端·数据库·python
ShallowLin2 小时前
vue3学习——组合式 API:生命周期钩子
前端·javascript·vue.js
Nejosi_念旧2 小时前
Vue API 、element-plus自动导入插件
前端·javascript·vue.js
互联网搬砖老肖2 小时前
Web 架构之攻击应急方案
前端·架构
pixle03 小时前
Vue3 Echarts 3D饼图(3D环形图)实现讲解附带源码
前端·3d·echarts
麻芝汤圆3 小时前
MapReduce 入门实战:WordCount 程序
大数据·前端·javascript·ajax·spark·mapreduce
juruiyuan1115 小时前
FFmpeg3.4 libavcodec协议框架增加新的decode协议
前端
Peter 谭5 小时前
React Hooks 实现原理深度解析:从基础到源码级理解
前端·javascript·react.js·前端框架·ecmascript
LuckyLay7 小时前
React百日学习计划——Deepseek版
前端·学习·react.js
gxn_mmf7 小时前
典籍知识问答重新生成和消息修改Bug修改
前端·bug