issue-blog
issue-blog copied to clipboard
webpack HMR 折腾记
前言
什么是 HMR:全称 Hot Module Replacement,当你在更改并保存代码时,webpack 将会重新进行打包,并将新的包模块发送到浏览器端,浏览器用新的包模块替换旧的,从而可以在不刷新浏览器的前提下就可以到修改的功能。
HMR 是提升开发体验的一个关键点,最近接触的项目中没有配置这个,导致在开发过程中,每次都要手动刷新,体验不太好,所以折腾一下,发现居然还有这么多门道...
首先介绍一下热重载和热更新的区别:
- 热重载(live reload): 修改文件之后,webpack 自动编译,并强制刷新浏览器,属于全局(整个应用)刷新,相当于 window.location.reload(),
- 热更新(HMR): 修改文件之后,webpack 自动编译,但是刷新时可以记住应用的状态,从而做到局部刷新。
举个🌰,比如你在某个页面打开了一个弹窗,并且修改了弹窗的内容,如果是热重载(live reload),那么整个页面重新刷新了,弹窗消失,你需要重新触发打开弹窗的操作。而热更新(HMR),刷新弹窗内容的同时,弹窗不关闭,直接可以看到更新后的效果。是不是明显后者开发体验更好?
回到我的踩坑过程,我前后试了三种方式,下面一一介绍一下配置和三者优劣:
live reload
这其实就是上面说的热重载(live reload) 的实现方式了,虽然有点简单粗暴,但是比"刀耕火种"时期的完全需要手动刷新要好点吧。
配置方式
使用的是 webpack-livereload-plugin:
- 安装依赖
npm install --save-dev webpack-livereload-plugin
- 在 webpack.config.js 中添加 plugin 配置
// webpack.config.js
var LiveReloadPlugin = require('webpack-livereload-plugin');
module.exports = {
plugins: [
new LiveReloadPlugin({
port: "12345" // 配置 live-reload 的端口号
})
]
}
- 在页面文件中加入 live-reload 生成的 js 文件,如果使用了 HtmlWebpackPlguin,可以直接在模板中加入这行。
// 端口号为上面配置的
<script src="http://localhost:12345/livereload.js"></script>
这种方式有一定的代码侵入性,而且因为是热重载(live reload) 的方式,所以开发体验欠佳。
react-hot-loader
使用的是 react-hot-loader,这种方式使用的是热更新(HMR),会检测你的改动点并局部刷新,而且支持 React Hooks。比上面热重载的体验会好一点。
配置方式
- 安装依赖
npm install --save-dev react-hot-loader
npm install --save-dev @hot-loader/react-dom // 如果要支持 React Hooks 需要安装这个
- 设置 webpack
// webpack.config.js
module.exports = {
// 1. 在入口添加 react-hot-loader/patch, 保证 react-hot-loader 在 react 和 react-dom 之前加载
entry: ['react-hot-loader/patch', './src'],
...
// 2. 设置 ts/tsx 的编译方式,使用 babel-loader 时在 options 中添加 plugins
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
cacheDirectory: true,
babelrc: false,
presets: [
[
"@babel/preset-env",
],
"@babel/preset-typescript",
"@babel/preset-react",
],
plugins: [
// plugin-proposal-decorators is only needed if you're using experimental decorators in TypeScript
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
"react-hot-loader/babel", // 添加 react-hot-loader 插件
],
},
},
}
// ... other configuration options
resolve: {
// 3. 设置 @hot-loader/react-dom,支持 React Hooks
alias: {
"react-dom": "@hot-loader/react-dom",
},
},
};
- 入口组件使用 hot 包裹
// App.js
import { hot } from 'react-hot-loader/root';
const App = () => <div>Hello World!</div>;
export default hot(App);
这种方式虽然实现了我们的终极目标热更新(HMR),但是代码侵入性较大,需要包使用 hot 包裹入口组件,当是 multiple entries 的场景,就需要改动较多的业务代码。其次,如果使用 ts-loader(没有使用 babel-loader) 去编译 ts 文件的话,使用 react-hot-loader 会不成功,因为 react-hot-loader 依赖于 babel-loader,所以对于使用 ts-loader 的项目来说其实不太友好。
Fast ReFresh
使用的是 React Refresh Webpack Plugin,该插件是 React 官方提供的,将热重载(live reload) 和 HMR 进行了整合,而且相比于 react-hot-loader,容错能力更高。
配置方式
- 安装依赖
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
- 修改 webpack 配置
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const webpack = require('webpack');
// ... your other imports
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
// ... other rules
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
// ... other loaders
{
loader: require.resolve('babel-loader'),
options: {
// ... other options
plugins: [
// ... other plugins
isDevelopment && require.resolve('react-refresh/babel'),
].filter(Boolean),
},
},
],
},
],
},
plugins: [
// ... other plugins
isDevelopment && new webpack.HotModuleReplacementPlugin(),
isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
// ... other configuration options
};
从配置可以看出,只需要改动 webpack 相关配置即可,对业务代码没有侵入性。为什么说它是将热重载(live reload) 和 HMR 进行了整合,主要是它在处理 HMR 时分为了三种情况:
- 如果所编辑的模块仅导出了 React 组件,Fast Refresh 就只更新该模块的代码,并重新渲染对应的组件。此时该文件的所有修改都能生效,包括样式、渲染逻辑、事件处理、甚至一些副作用
- 如果所编辑的模块导出的东西不只是 React 组件,Fast Refresh 将重新执行该模块以及所有依赖它的模块
- 如果所编辑的文件被 React(组件)树之外的模块引用了,Fast Refresh 会降级成整个刷新(Live Reloading)
总结
三种方式对比如下:
方式 | 体验 | 侵入性 | 配置难度 |
---|---|---|---|
live reload | ✅ | ✅✅✅ | ✅ |
react-hot-reload | ✅✅✅ | ✅✅✅✅ | ✅✅✅ |
fast refresh | ✅✅✅ | ✅ | ✅ |
由上表可以得出,fast refresh 无疑是最优选择。而且 fast refresh 被视为 react-hot-loader 下一代的解决方案,除此之外它还与平台无关,既支持 React Native,也支持 Web,所以最终选择了它。不过它也有一些局限性,比如当设置了 externals,HMR 会失效,所以开发环境可先关闭 externals 配置。
参考资料
有错别字,代码’倾入‘性
@xuedafei 感谢指出,已修正
👍
👍