[TOC]

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

在开发阶段,修改源码是不可避免的操作。对于开发网页来说,要想看到修改后的效果,就需要刷新浏览器让其重新运行最新的代码。虽然这相比于开发原生iOS和Android应用来说要方便很多,因为那需要重新编译这个项目再运行,但我们可以将这个体验优化得更好。 借助自动化的手段,可以将这些重复的操作交给代码去帮我们完成,在监听到本地源码文件发生变化时,自动重新构建出可运行的代码后再控制浏览器刷新。

Webpack将这些功能都内置了,并且提供了多种方案供我们选择。

文件监听

文件监听是在发现源码文件发生变化时,自动重新构建出新的输出文件。

Webpack官方提供了两大模块,一个是核心的webpack;另一个是webpack-dev-server。文件监听功能是webpack提供的。

Webpack支持文件监听相关的配置项:

module.exports = {
  // 只有在开启监听模式时,watchOptions才有意义
  // 默认为false,也就是不开启
  watch: true,
  // 监听模式运行时的参数
  // 在开启监听模式时才有意义
  watchOptions: {
    // 不监听的文件或文件夹,支持正则匹配
    // 默认为空
    ignored: /node_modules/,
    // 监听到变化发生后等300ms再去执行动作,截流
    // 防止文件更新太快而导致重新编译频率太快。默认为300ms
    aggregateTimeout: 300,
    // 判断文件是否发生变化是通过不停地询问系统指定文件有没有变化实现的
    // 默认每秒询问1000次
    poll: 1000
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Webpack开启监听模式有以下两种方式:

  1. 在配置文件webpack.config.js中设置watch: true
  2. 在执行启动webpack的命令时带上--watch参数,完整命令为webpack --watch或者webpack -w

文件监听的工作原理

Webpack中监听一个文件发生变化的原理是:定时获取这个文件的最后编辑时间,每次都存下最新的最后编辑时间,如果发现当前获取的和最后一次保存的最后编辑时间不一致,就认为该文件发生了变化。配置项中的watchOptions.poll用于控制定时检查的周期(具体含义是每秒检查多少次)。

当发现某个文件发生了变化时,并不会立刻告诉监听者,而是先缓存起来,收集一段时间的变化后,再一次性告诉监听者。配置项中的watchOptions.aggregateTimeout用于配置这个等待时间。这样做的目的在于:我们在编辑代码的过程中可能会高频地输入文字,导致文件变化的事件高频地发生,如果每次都重新执行构建,就会让构建卡死。

对于多个文件来说,其原理相似,Webpack会对列表中的每个文件都定时执行检查。但是怎么确定这个需要监听的文件列表呢?在默认情况下,Webpack会从配置的Entry文件出发,递归解析出Entry文件所依赖的文件,将这些依赖的文件都加入监听列表中。而不是粗暴地直接监听项目目录下的所有文件。

需要注意的:由于保存文件的路径和最后的编辑时间需要占用内存,定时检查周期检查需要占用CPU及文件I/O,所以最好减少需要监听的文件数量和降低检查频率。

优化文件监听的性能

在开启监听模式后,默认情况下会监听配置的Entry文件和所有Entry递归依赖的文件。在这些文件中会有很多存在于node_modules下,因为现如今的项目会依赖大量的第三方模块。但是,在大多数情况下,我们不可能去编辑node_modules下的文件,而只是编辑自己的项目文件。

方法一:忽略node_modules下的文件,不监听它们。采用这种方法优化后,Webpack消耗的内存和CPU将会大大减少。

module.exports = {
    watch: true,
    watchOptions: {
        // 不监听node_modules目录下的文件
        ignored: /node_modules/
    }
}
1
2
3
4
5
6
7

方法二:watchOptions.aggregateTimeout的值越大性能越好,因为这能降低重新构建的频率。 方法三:watchOptions.poll的值越小越好,因为这能降低检查的频率。

但是后两种优化方法会导致监听模式的反应和灵敏度降低。

自动刷新浏览器

监听到文件更新后的下一步是刷新浏览器,Webpack模块负责监听文件,webpack-dev-server模块负责刷新浏览器。在使用webpack-dev-serve模块去启动webpack模块时,webpack模块的监听模式默认会被开启。webpack模块会在文件发生变化时通知webpack-dev-server模块。

自动刷新的原理

控制浏览器刷新有如下三种方法:

  1. 借助浏览器扩展去通过浏览器提供的接口刷新,WebStorm IDELiveEdit功能就是这样实现的;
  2. 向要开发的网页中注入代理客户端代码,通过代理客户端去刷新整个页面;
  3. 将要开发的网页装进一个iframe中,通过刷新iframe去看到最新效果。 webpack-dev-server模块支持第二、三种方法,其中第二种是webpack-dev-server模块默认采用的刷新方法。

优化自动刷新的性能

devServer.inline配置项用来控制是否向Chunk中注入代理客户端,默认会注入。事实上,在开启inline时,DevServer会向每个输出的Chunk中注入代理客户端的代码,当我们的项目需要输出很多Chunk时,就会导致构建缓慢。其实要完成自动刷新,一个页面只需要一个代理客户端,DevServer之所以粗暴地为每个Chunk都注入,是因为它不知道某个网页依赖哪几个Chunk,索性全部都注入一个代理客户端。网页只要依赖了其中任何一个Chunk,代理客户端就被注入网页中。

优化思路:关闭inline模式,只注入一个代理客户端。关闭方式有两种:

  1. webpack-dev-server --inline false
  2. 在配置文件中设置:inline: false

和前面的不同在于:

  1. 入口网址变成了http://localhost:8080/webpack-dev-server/
  2. 打包后的bundle.js中不再包含代理客户端的代码。

在浏览器中打开网址http://localhost:8080/webpack-dev-server/后,发现:要开发的网页被放进了一个iframe中,编辑源码后,iframe会被自动刷新。同时我们会发现构建的时间从1566ms减少到了1130ms,说明优化生效了。要输出的Chunk数量越多,构建性能提升的效果越明显。

在关闭inline后,DevServer会自动提示通过新网址http://localhost:8080/webpack-dev-server/去访问,这一点做得很人性化。

如果不想以iframe的方式去访问,但同时想让网页保持自动刷新的功能,则需要手动向网页中注入代理客户端的脚本,向index.html中插入以下标签:

<!--注入DevServer提供的代理客户端脚本,这个服务是DevServer内置的-->
<script src="http://localhost:8080/webpack-dev-server.js"></script>
1
2

向网页注入以上脚本后,独立打开的网页就能自动刷新了。但是在发布到线上时要删掉这段用于开发环境的代码。

module.exports = {
    watch: true,
    watchOptions: {
        ignored: /node_modules/,
        aggregateTimeout: 300,
        poll: 1000
    }
}
1
2
3
4
5
6
7
8
devServer: {
    port: 3000,
    historyApiFallback: true,//不跳转
    // inline: true, //实时刷新,
    compress: true,
    overlay: true, // 在浏览器页面上显示错误
    open: true, // 自动打开浏览器
    hot: true
}
1
2
3
4
5
6
7
8
9

如上图所示:当在devServer中配置overlay: true时,将会在页面上显示webpack编译错误的信息。

参考文档

  1. 使用自动刷新