写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址

要做到实时预览,除了采用刷新整个网页的方法,DevServer还支持模块热替换(Hot Module Replacement)的技术可在不刷新整个网页的情况下做到超灵敏实时预览。原理是:在一个源码发生变化时,只需重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块。

模块热更新的优势:

  1. 实时预览反应更快,等待时间更短;
  2. 不刷新浏览器时能保留当前网页的运行状态,例如在使用Redux管理数据的应用中搭配模块热替换能做到在代码更新时Redux中的数据保持不变。

总的来说,模块热替换技术在很大程度上提升了开发效率和体验。

需要注意:

  • WDS(webpack-dev-server)不刷新浏览器;
  • WDS不输出文件,而是放在内存中;
  • 使用HotModuleReplacementPlugin插件

WDS和HotModuleReplacementPlugin结合使用才能开启热更新的功能。 WDS与watch模式(将打包结果放在本地磁盘中)不同,不存在磁盘IO,打包的结果是放在内存中的,构建速度更快。

模块热替换配置

模块热替换的原理和自动刷新的原理类似,都需要向要开发的网页中注入一个代理客户端来连接DevServer和网页,不同在于模块热替换的独特的模块替换机制。

DevServer默认不会开启模块热替换模式,要开启该模式,只需要在启动时带上参数--hot,完整命令为webpack-dev-server --hot

除了通过在启动时带上--hot参数,还可以通过接入Plugin实现,相关代码如下:

const webpack = require('webpack');
module.exports = {
    // 为每个入口都注入代理客户端
    entry: {
        'app': [
            'webpack-dev-server/client?http://localhost:3000/',
            'webpack/hot/dev-server',
            './src/index.js'
        ]
    },
     plugins: [
        // 该插件的作用是实现模块热替换,实际上若启动时带上--hot参数,就会注入该插件,生成.hot-update.json文件
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        // 告诉DevServer要开启模块热替换模式
        hot: true
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

需要注意的是:在启动Webpack时带上参数--hot,其实就是自动完成以上配置。

// 入口文件index.js
import './style/index.css';
import React, {Fragment} from 'react';
import { render } from 'react-dom';
import Hello from 'components/Hello';

const renders = (Component) => {
    render(
        <Fragment>
            <h1>我是首页111222</h1>
            {Component}
        </Fragment>,
        document.getElementById('root')
    );
}

renders(<Hello />);
// 只有当开启了模块热替换时module.hot才存在
if (module.hot) {
    // accept函数的第一个参数指出当前文件接收哪些子模块的替换,这里表示只接收./components/Hello这个子模块
    // 第2个参数用于在新的子模块加载完毕后需要执行的逻辑
    module.hot.accept(['./components/Hello'], () => {
        // 在新的Hello加载成功后重新执行组件渲染逻辑
        renders(<Hello />);
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

其中module.hot是当开启模块热替换后注入全局的API,用于控制模块热替换的逻辑。

现在修改Hello.jsx文件,我们会发现模块热替换生效了。但是在编辑入口文件index.js时,我们会发现整个网页被刷新了。为什么修改这两个文件会有不一样的表现呢?

这是因为: 当子模块发生更新时,更新事件会一层层地向上传递,也就是从Hello.jsx文件传递到index.js文件,直到有某层的文件接收了当前变化的模块,即index.js文件中定义的module.hot.accept(['./components/Hello'], callback),这时就会调用callback函数去执行自定义逻辑。如果事件一直往上抛,到最外层都没有文件接收它,则会直接刷新网页。

那为什么没有地方接收.css文件,但是修改所有.css文件都会触发模块热替换呢?原因在于:style-loader会注入用于接收css的代码。

特别注意:请不要将模块热替换技术用于线上环境,它是专门为提升开发效率而生。

模块热替换优化

监听更少的文件,忽略node_modules目录下的文件。

其中的Updated modules: 68是指ID68的模块被替换了,这对开发者来说很不友好,因为开发者不知道ID和模块之间的对应关系,最好是把替换了的模块的名称输出出来。 Webpack内置的NamedModulesPlugin插件可以解决该问题,修改Webpack配置文件接入该插件:

const NamedModulesPlugin = require('webpack');

module.exports = {
  plugins: [
    // 显示出被替换模块的名称
    new webpack.NamedModulesPlugin(),
  ],
};
1
2
3
4
5
6
7
8

除此之外,模块热替换还面临着和自动刷新一样的性能问题,因为它们都需要监听文件变化和注入客户端。 要优化模块热替换的构建性能,思路和在使用自动刷新中提到的很类似:监听更少的文件,忽略掉node_modules目录下的文件。 但是其中提到的关闭默认的inline模式手动注入代理客户端的优化方法不能用于在使用模块热替换的情况下,原因在于:模块热替换的运行依赖在每个Chunk中都包含代理客户端的代码。

使用webpack-dev-middleware实现热更新

WDM将webpack输出的文件传输给服务器,适用于灵活的定制场景。

HMR原理

参考文档

  1. 如何使用模块热替换 HMR 来处理 CSS