开始讲解之前,先说明,热更新是只适用于开发环境的,不要用于生产环境。
(HMR: Hot Module Replacement)
开启和关闭
在 webpack 5 中热更新是默认开启的,并且不需要引用任何插件,如果想要关闭如下设置:
/** ./build/webpack.dev.js **/module.exports = {mode: 'development',//...devServer: {//..hot: false//..}//...}
在 webpack 4 中热更新需要手动开启,并且引用 webpack 中的 HotModuleReplacementPlugin 插件使用方式如下:
/** ./build/webpack.dev.js **/let webpack = require('webpack')module.exports = {mode:'development',//...devServer: {//..hot: true,//..},//...plugins:[new webpack.HotModuleReplacementPlugin()]}
什么是热更新?
我们造一个例子。
public|-index.htmlsrc|-index.js|-index.css|-other.js
我们在 public 下新建一个 html 模版:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div id="app"></div></body></html>
我们在 src 下新建三个文件:
/** ./src/index.js **/import './index.css';import other from './other';const btn = document.createElement('button');btn.innerHTML = '点我啊';btn.addEventListener('click', () => {const p = document.createElement('p');p.innerHTML = 'hello';window.app.appendChild(p);});window.app.appendChild(btn);
/** ./index.css **/body {background: red;}
/** ./other.js **/export default (a, b) => a + b;
什么是关闭状态?
我们先照着上面的操作,把 webpack 热更新改成关闭状态。
我们运行服务,yarn dev,页面如下:
我们点击 点我啊,插入几条 hello,
然后我们在 css 中把 red 改为 blue,我们会发现页面进入 loading 状态,并且刷新了整个页面。如下:
这就是没有热更新的状态。修改代码直接刷新页面来进行更新。
什么是开启状态?
我们在 webpack 中把热更新开启。
yarn dev
再次点击 点我啊。如下:
然后去修改 css 中的颜色为 blue,效果如下:
我们会发现页面并没有刷新,js 插入的内容也还在,并没有被初始化,只是更新了 css,这就是热更新。
css 热更新
webpack 是默认支持 css 的热更新的( 通过 style-loader 中的热更新接口实现)不需要我们写额外的代码。如上面的例子。
但是我们见 style-loader 替换成 MiniCssExtractPlugin.loader 后, css 就不支持热更新了,所以我们只能把 MiniCssExtractPlugin.loader 放到生产环境,就像前几章拆分 config 文件那样。
javascript 热更新
js 的热更新就相对复杂一点,webpack 默认是不会对 js 文件进行热更新的。因为他并不知道怎么更新js,需要我们告诉他如何来更新。
js 这边我们默认 hmr 都是开启的
不配置的状态
我们先来验证下不配置的状态。
还是刚才那个例子,修改一下 index.js 方便观察变化,如下:
/** ./src/index.js **/import './index.css';import other from './other';const btn = document.createElement('button');btn.innerHTML = other(1, 2);btn.addEventListener('click', () => {const p = document.createElement('p');p.innerHTML = 'hello';window.app.appendChild(p);});window.app.appendChild(btn);
我们将 btn 的 innterHTML 的内容改成计算的数字,如下初始状态:
我们点击按钮插入几个元素,如下:
然后我们到js 文件中修改一下:
/** ./src/other.js **/export default (a, b) => a + b + 2; // 这边多加一个 2

发现刷新按钮还是转了一下,插入的元素也不见了,说明修改 js 直接导致了整个页面的刷新,这不符合 热更新的特性,但是我们已经开启的热更新了,所以说明webpack 默认对 js 是不支持热更新的。
如果想要实现热更新需要
配置热更新
官方文档
先说明一点,在 node 和 esModule 中热更新的语法是不一样的,如下:
if (module.hot) {module.hot.accept('./library.js', function() {// 对更新过的 library 模块做些事情...});}// orif (import.meta.webpackHot) {import.meta.webpackHot.accept('./library.js', function () {// Do something with the updated library module…});}
每当当前文件的依赖发生变更时,就可以在 accept 的回调函数中接收到这个变化。所以我们做以下变更:
import './index.css';import other from './other';const btn = document.createElement('button');btn.innerHTML = other(1, 2);btn.addEventListener('click', () => {const p = document.createElement('p');p.innerHTML = 'hello1';window.app.appendChild(p);});window.app.appendChild(btn);if (import.meta.webpackHot) {import.meta.webpackHot.accept(['./other'], () => {console.log('更新');btn.innerHTML = other(1, 2);});}
accept 的第一个参数是,要接受的变化的依赖模块,当这个模块变化时,第二个回调函数就会响应这个变化做出对应的变更,当然这个变化得我们来写。
上述第 15 行的说明的就是当 other 文件变化时,把 btn 的 innerHTML 重新获取放上去。
效果如下:
插入几个元素
然后我们修改 js 看 js 的热更新。
我们把数字加了 2,发现触发了更新并且页面没有刷新而且保留了插入的元素。
这就说明热更新成功。
不传回调函数
也有不传回调函数的特殊情况,如下:
import './index.css';import other from './other';const btn = document.createElement('button');btn.innerHTML = other(1, 2);btn.addEventListener('click', () => {const p = document.createElement('p');p.innerHTML = 'hello1';window.app.appendChild(p);});window.app.appendChild(btn);if (import.meta.webpackHot) {import.meta.webpackHot.accept();}
这种情况下会刷新这个模块,也就是这个模块重新执行一边,如下:
会又插入一个 btn。
