开始讲解之前,先说明,热更新是只适用于开发环境的,不要用于生产环境。
(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.html
src
|-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 模块做些事情...
});
}
// or
if (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。