一、为什么需要HMR?就像给汽车换轮胎不用停车

每次修改代码都要手动刷新浏览器?这就像开车时爆胎非要停下来才能换。HMR(Hot Module Replacement)就是那个让你边开边换轮胎的黑科技。在Angular开发中,保存代码后自动局部更新模块,状态保持、样式即时生效,调试效率直接翻倍。

举个真实场景:你在调试商品详情页的评分组件,传统模式下:

  1. 改代码 → 2. 按F5 → 3. 重新登录 → 4. 找到测试商品 → 5. 展开评分区域
    用了HMR后:改代码 → 看到变化(其他交互状态全保留)

二、Angular HMR配置实战(基于Angular 15+)

2.1 核心配置三件套

先安装关键依赖(技术栈:Angular + Webpack):

npm install @angularclass/hmr --save-dev

然后创建hmr.ts配置文件:

// src/hmr.ts
import { NgModuleRef, ApplicationRef } from '@angular/core';
import { createNewHosts } from '@angularclass/hmr';

export const hmrBootstrap = (
  module: any,
  bootstrap: () => Promise<NgModuleRef<any>>
) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  bootstrap().then(mod => {
    ngModule = mod;
    // 启用HMR状态保持
    if (module.hot.data) {
      // 这里会保留组件状态
      const restoreState = module.hot.data.restoreState;
      restoreState && restoreState();
    }
  });
  module.hot.dispose(() => {
    const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    const elements = appRef.components.map(c => c.location.nativeElement);
    // 销毁前保存状态
    const restoreState = createNewHosts(elements);
    ngModule.destroy();
    module.hot.data = { restoreState };
  });
};

2.2 修改main.ts启用HMR

// src/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { hmrBootstrap } from './hmr';

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);

if (environment.production) {
  bootstrap().catch(err => console.error(err));
} else {
  // 开发环境启动HMR
  if (module['hot']) {
    hmrBootstrap(module, bootstrap);
  } else {
    console.error('HMR未启用,请检查webpack配置');
  }
}

2.3 Webpack配置调整(angular.json片段)

"build": {
  "builder": "@angular-builders/custom-webpack:browser",
  "options": {
    "customWebpackConfig": {
      "path": "./webpack.hmr.config.js"
    }
  }
}

三、那些你必须知道的坑与技巧

3.1 状态保持的边界条件

HMR不是魔法,这些情况会失效:

  • 修改@Injectable()服务内部逻辑(需手动处理状态)
  • 修改路由配置(建议使用module.hot.invalidate()强制刷新)

3.2 性能优化配置

webpack.hmr.config.js中添加:

module.exports = {
  devServer: {
    hot: true,
    // 关键:禁用全量重载
    liveReload: false,
    // 减少检测频率
    watchOptions: {
      aggregateTimeout: 300,
      poll: 1000
    }
  }
};

四、从原理到实践的全景分析

4.1 技术实现原理

Webpack在背后做了这些事:

  1. 建立WebSocket连接监听文件变化
  2. 差异编译改动的模块
  3. 通过module.hotAPI进行热替换

4.2 对比其他方案

方案 优点 缺点
传统刷新 实现简单 丢失所有状态
HMR 状态保持 配置稍复杂
NG Live Reload 自动刷新 仍需重新渲染整个应用

4.3 最佳实践场景推荐

  • 适合:频繁调整UI样式的开发阶段
  • 不适合:修改核心路由配置或全局状态逻辑

五、终极解决方案:HMR+状态管理联调

当使用NgRx时,需要额外处理:

// 在hmr.ts的dispose回调中添加
module.hot.dispose(() => {
  const store = ngModule.injector.get(Store);
  const state = store.getState();
  module.hot.data = { ...module.hot.data, state };
});

这样在重新加载模块时,可以还原Redux状态:

if (module.hot.data?.state) {
  store.dispatch({ type: 'HMR_RESTORE', payload: module.hot.data.state });
}