Skip to main content

热加载 HMR

什么是 HMR

HMR 是指 Hot Module Replacement。对于你需要更新的模块,进行一个"热"替换,所谓的热替换是指在不需要刷新页面的情况下,对某个改动进行无缝更新。如果你没有配置 HMR,那么你每次改动,都需要刷新页面,才能看到改动之后的结果,对于调试来说,非常麻烦,而且效率不高,最关键的是,你在界面上修改的数据,随着刷新页面会丢失,而如果有类似 Webpack 热更新的机制存在,那么,则是修改了代码,不会导致刷新,而是保留现有的数据状态,只将模块进行更新替换。也就是说,既保留了现有的数据状态,又能看到代码修改后的变化。

总结:

  • 加载页面时保存应用程序状态
  • 只更新改变的内容,节省调试时间
  • 修改样式更快,几乎等同于在浏览器中更改样式

基础配置

安装依赖

假设现在已经有了 webpack 的基础依赖包,想要让 HMR 生效还需要安装以下包:

  • webpack-dev-middleware
  • webpack-hot-middleware (@webapp-suite/webpack-hot-middleware)
npm install -D webpack-dev-middleware @webapp-suite/webpack-hot-middleware @pmmmwh/react-refresh-webpack-plugin react-refresh

配置 Webpack

webpack.dev.config.js
module.exports = {
mode: 'development',
entry: {
app: [
resolve(__dirname, '../src/index'),
// 必须这么写,这将连接到服务器,以便在包重新构建时接收通知,然后相应地更新客户端
'@webapp-suite/webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000'
]
},
plugins: [new webpack.HotModuleReplacementPlugin()]
};

配置 Node Server

server.js
const webpackHotMiddleware = require('@webapp-suite/webpack-hot-middleware');

if (isDevelopment) {
const urls = prepareUrls('http', 'localhost');
const openUrl = urls.localUrlForBrowser;

const compiler = webpack({...WebpackDevConfig, mode: 'development'});
const webpackDevInstance = webpackDevMiddleware(compiler, {
stats: false
});
const webpackHotInstance = webpackHotMiddleware(compiler, {
log: false, // https://github.com/geowarin/friendly-errors-webpack-plugin#turn-off-errors
path: '/__webpack_hmr',
heartbeat: 10 * 1000
});
server.use(webpackDevInstance);
server.use(webpackHotInstance);
webpackDevInstance.waitUntilValid(() => {
openBrowser(openUrl);
});
}

查看

效果已经出来,我们发现控制台已经提示热更新日志输出:

[HMR] connected

这已经很明白地告诉我们热更新已经连接上了,当我们修改源码文件后,浏览器会自动的刷新。

问题

比如弹框组件,当我点击 button,state 也会随之增加。但是这个时候如果我修改了某一个文件内容,可以看到我浏览器的确刷新了。但是!state 却重置到了 1,这并不是我们想要的。

热更新保留组件状态

React

  • @pmmmwh/react-refresh-webpack-plugin (React App)
  • react-refresh (React App)
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
webpack.dev.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
mode: 'development',
entry: {
app: [
resolve(__dirname, '../src/index'),
'@webapp-suite/webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000'
]
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
use: [
{
loader: 'babel-loader',
options: {
plugins: ['react-refresh/babel']
}
}
]
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin()
]
};

至此,我们就已经实现了,修改源码后 ,浏览器自动刷新的效果了,并且还保留了刷新前的 state 状态。

使用 TypeScript

  • @babel/core
  • @babel/plugin-syntax-decorators
  • @babel/plugin-syntax-jsx
  • @babel/plugin-syntax-typescript
  • babel-loader
npm install -D @babel/core @babel/plugin-syntax-decorators @babel/plugin-syntax-jsx @babel/plugin-syntax-typescript babel-loader
webpack.dev.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
mode: 'development',
entry: {
app: [
resolve(__dirname, '../src/index'),
'@webapp-suite/webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000'
]
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
use: [
{
loader: 'thread-loader'
},
{
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-syntax-typescript',
['@babel/plugin-syntax-decorators', {legacy: true}],
'@babel/plugin-syntax-jsx',
'react-refresh/babel'
]
}
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
}
]
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin()
]
};

虽然直接在 ts-loader 后面加一层 babel-loader 会带来一定的性能损耗,但实际开发过程中还是可以接受的,如果直接用 babel-loader 代替 ts-loader,各种语法兼容需要装插件,还可能还会有各种错,比较折腾,不如这样省事。

参考资料

  1. pmmmwh/react-refresh-webpack-plugin README
  2. Webpack: Hot Module Replacement
  3. Webpack: 模块热替换
  4. webpack 学习之路(四)webpack-hot-middleware 实现热更新, by daly42872
  5. Webpack 自动刷新和 HMR, by patrick_kibo
  6. Webpack 模块热替换(hot module replacement) API
  7. stackoverflow: webpack-dev-server: how to preserve state with module.hot.data?
  8. Facebook/create-react-app webpackHotDevClient.js
  9. react 配置 HMR(热替换 保留页面数据状态), by 哈娄
  10. react-hot-loader
  11. react-hot-loader: Performance oriented .babelrc for TS users, by RomanHotsiy
  12. 使用 webpack 实现 react 的热更新, by Nealyang
  13. 基于 webpack 的热重载 live reload 和热更新 HMR, by 快狗打车前端团队
  14. Webpack 如何配置热更新,By 发声的沉默者
  15. 看完这篇,面试再也不怕被问 Webpack 热更新, by 米亚
  16. How should we set up apps for HMR now that Fast Refresh replaces react-hot-loader?
  17. 使用 webpack 实现 react 的热更新, By isNealyang
  18. Webpack Guidebook: 模块热更新, by tsejx