开始讲解之前,先说明,热更新是只适用于开发环境的,不要用于生产环境。
(HMR: Hot Module Replacement)

开启和关闭

在 webpack 5 中热更新是默认开启的,并且不需要引用任何插件,如果想要关闭如下设置:

  1. /** ./build/webpack.dev.js **/
  2. module.exports = {
  3. mode: 'development',
  4. //...
  5. devServer: {
  6. //..
  7. hot: false
  8. //..
  9. }
  10. //...
  11. }

在 webpack 4 中热更新需要手动开启,并且引用 webpack 中的 HotModuleReplacementPlugin 插件使用方式如下:

  1. /** ./build/webpack.dev.js **/
  2. let webpack = require('webpack')
  3. module.exports = {
  4. mode:'development',
  5. //...
  6. devServer: {
  7. //..
  8. hot: true,
  9. //..
  10. },
  11. //...
  12. plugins:[
  13. new webpack.HotModuleReplacementPlugin()
  14. ]
  15. }

什么是热更新?

我们造一个例子。

  1. public
  2. |-index.html
  3. src
  4. |-index.js
  5. |-index.css
  6. |-other.js

我们在 public 下新建一个 html 模版:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="app"></div>
  11. </body>
  12. </html>

我们在 src 下新建三个文件:

  1. /** ./src/index.js **/
  2. import './index.css';
  3. import other from './other';
  4. const btn = document.createElement('button');
  5. btn.innerHTML = '点我啊';
  6. btn.addEventListener('click', () => {
  7. const p = document.createElement('p');
  8. p.innerHTML = 'hello';
  9. window.app.appendChild(p);
  10. });
  11. window.app.appendChild(btn);
  1. /** ./index.css **/
  2. body {
  3. background: red;
  4. }
  1. /** ./other.js **/
  2. export default (a, b) => a + b;

什么是关闭状态?

我们先照着上面的操作,把 webpack 热更新改成关闭状态。
我们运行服务,yarn dev,页面如下:
image.png
我们点击 点我啊,插入几条 hello,
image.png
然后我们在 css 中把 red 改为 blue,我们会发现页面进入 loading 状态,并且刷新了整个页面。如下:
image.png
这就是没有热更新的状态。修改代码直接刷新页面来进行更新。

什么是开启状态?

我们在 webpack 中把热更新开启。
yarn dev
再次点击 点我啊。如下:
image.png
然后去修改 css 中的颜色为 blue,效果如下:
image.png
我们会发现页面并没有刷新,js 插入的内容也还在,并没有被初始化,只是更新了 css,这就是热更新。

css 热更新

webpack 是默认支持 css 的热更新的( 通过 style-loader 中的热更新接口实现)不需要我们写额外的代码。如上面的例子。
但是我们见 style-loader 替换成 MiniCssExtractPlugin.loader 后, css 就不支持热更新了,所以我们只能把 MiniCssExtractPlugin.loader 放到生产环境,就像前几章拆分 config 文件那样。

javascript 热更新

js 的热更新就相对复杂一点,webpack 默认是不会对 js 文件进行热更新的。因为他并不知道怎么更新js,需要我们告诉他如何来更新。
js 这边我们默认 hmr 都是开启的

不配置的状态

我们先来验证下不配置的状态。
还是刚才那个例子,修改一下 index.js 方便观察变化,如下:

  1. /** ./src/index.js **/
  2. import './index.css';
  3. import other from './other';
  4. const btn = document.createElement('button');
  5. btn.innerHTML = other(1, 2);
  6. btn.addEventListener('click', () => {
  7. const p = document.createElement('p');
  8. p.innerHTML = 'hello';
  9. window.app.appendChild(p);
  10. });
  11. window.app.appendChild(btn);

我们将 btn 的 innterHTML 的内容改成计算的数字,如下初始状态:
image.png
我们点击按钮插入几个元素,如下:
image.png
然后我们到js 文件中修改一下:

  1. /** ./src/other.js **/
  2. export default (a, b) => a + b + 2; // 这边多加一个 2

image.png
发现刷新按钮还是转了一下,插入的元素也不见了,说明修改 js 直接导致了整个页面的刷新,这不符合 热更新的特性,但是我们已经开启的热更新了,所以说明webpack 默认对 js 是不支持热更新的。
如果想要实现热更新需要

配置热更新

官方文档
先说明一点,在 node 和 esModule 中热更新的语法是不一样的,如下:

  1. if (module.hot) {
  2. module.hot.accept('./library.js', function() {
  3. // 对更新过的 library 模块做些事情...
  4. });
  5. }
  6. // or
  7. if (import.meta.webpackHot) {
  8. import.meta.webpackHot.accept('./library.js', function () {
  9. // Do something with the updated library module…
  10. });
  11. }

每当当前文件的依赖发生变更时,就可以在 accept 的回调函数中接收到这个变化。所以我们做以下变更:

  1. import './index.css';
  2. import other from './other';
  3. const btn = document.createElement('button');
  4. btn.innerHTML = other(1, 2);
  5. btn.addEventListener('click', () => {
  6. const p = document.createElement('p');
  7. p.innerHTML = 'hello1';
  8. window.app.appendChild(p);
  9. });
  10. window.app.appendChild(btn);
  11. if (import.meta.webpackHot) {
  12. import.meta.webpackHot.accept(['./other'], () => {
  13. console.log('更新');
  14. btn.innerHTML = other(1, 2);
  15. });
  16. }

accept 的第一个参数是,要接受的变化的依赖模块,当这个模块变化时,第二个回调函数就会响应这个变化做出对应的变更,当然这个变化得我们来写。
上述第 15 行的说明的就是当 other 文件变化时,把 btn 的 innerHTML 重新获取放上去。
效果如下:
image.png
插入几个元素
image.png
然后我们修改 js 看 js 的热更新。
image.png
我们把数字加了 2,发现触发了更新并且页面没有刷新而且保留了插入的元素。
这就说明热更新成功。

不传回调函数

也有不传回调函数的特殊情况,如下:

  1. import './index.css';
  2. import other from './other';
  3. const btn = document.createElement('button');
  4. btn.innerHTML = other(1, 2);
  5. btn.addEventListener('click', () => {
  6. const p = document.createElement('p');
  7. p.innerHTML = 'hello1';
  8. window.app.appendChild(p);
  9. });
  10. window.app.appendChild(btn);
  11. if (import.meta.webpackHot) {
  12. import.meta.webpackHot.accept();
  13. }

这种情况下会刷新这个模块,也就是这个模块重新执行一边,如下:
image.png
会又插入一个 btn。