使用自动刷新
文件监听
让webpack开启监听模式时,有如下两种方式:
- 在配置文件webpack.config.js中设置watch: true
- 在执行启动webpack的命令时带上--watch参数,完整的命令是webpack--watch
文件监听的工作原理
在webpack中监听一个文件发生变化的原理,是定时获取这个文件的最后编辑时间,每次都存下最新的最后编辑时间,如果发生当前获取的和最后一次保存的最后编辑时间不一样,就认为该文件发生了变化,配置项中的watchOptions.poll用于控制定时检查的周期,具体含义是每秒检查多少次。
当发生某个文件发生变化时,并不会立即告诉监听者,而是先缓存起来,收集一段时间的变化后,再一次性告诉监听者。配置项中的watchOptions.aggregateTimeout用愈发配置这个等待时间(为了防止边系带吗时候高频的输入文字,导致文件发生便不啊的事件高频发生,每次都重新执行构建,会让构建卡死
由于保存文件的路径和最后的编辑时间需要占用内存,定时检查周期需要占用CPU及文件I/O,所以最好减少需要监听的文件数量和降低检查频率。
优化文件监听的性能
一般情况下我们会忽略node_modules下的文件,不监听它们,webpack消耗的内存和cpu将会大大减少。
有时我们可能会在自己的项目中修改node_modules下(第三方模块)的文件,就需要重启构建。
除了忽略部分文件的优化,还有如下两种方法
- watchOptions.aggregateTimeout的值越大性能越好,因为这能降低重新构建的频率
- watchOptions.poll的值越小越好,因为这能降低检查频率。 可是两种优化方法的后果是监听模式的反应和灵敏度降低了。
自动刷新浏览器
监听到文件更新后的下一步是刷新浏览器,webpack模块负责监听文件,webpack-dev-server模块则负责刷新浏览器,在使用webpack-dev-server模块去启动webpack模块时,webpack模块的监听模式默认会被开启。webpack模块会在文件发生变化时通知webpack-dev-server模块
自动刷新的原理
控制浏览器有以下三种方法:
- 借助浏览器扩展去通过浏览器提供的接口刷新,webstrom IDE的LiveEdit功能就是这样实现的
- 向要开发的网页中注入代理客户端代码,通过代理客户端去刷新整个页面
- 将要开发的网页装进一个iframe中,通过刷新iframe去看到最新的效果 DevServer支持2,3种方法
优化自动刷新的性能
inline配置项用来控制是否向Chunk中注入代理客户端,默认注入。事实上,在开启inline时,DevServer会向每一个输出的Chunk中注入代理客户端的代码,当我们的项目需要输出很多Chunk,会导致构建速度缓慢。其实只要完成自动刷新,一个页面只需要一个代理客户端,DevServer之所以粗暴的为每个Chunk都注入,因为他不知道某个网页都依赖哪几个Chunk,就全部注入一个代理客户端,网页只要以来了其中一个Chunk,代理客户端就被注入到网页中。
优化思路:关闭还不够优雅的inline模式,只注入一个代理客户端(可以使用wepack-dev-server --inline false完成):
在浏览器打开http://localhost:8080/webpack-dev-server/后,会发现,要开发的网页被放入一个iframe中,编辑源码后,iframe会被自动刷新,同时会发现构建时间减少了(优化生效)(要输出的Chunk数量越多,构建性能提升的效果越明显)
如果不想以iframe的方式去访问,但同时想让网页保持自动刷新功能,需要向网页中注入代理客户端的脚本,向index.html中插入以下标签
<!--注入DevServer提供的代理客户端脚本,这个服务是DevServer内置的-->
<script src="http://localhost:8080/webpack-dev-server.js"></script>
向网页注入以上脚本后,独立打开的网页就能自动刷新了,但是注意发布到线上时删掉这段用于开发环境的代码
开启模块热替换
要做到实时预览,除了以上方法,DevServer还支持一种叫模块热替换的技术可以在不刷新整个网页的情况下做到实时预览。原理:一个代码发生变化时,只需重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块。 优势:
- 实时预览反应更快,等待时间更短。
- 不刷新浏览器时能保留当前网页的运行状态。
模块热替换的原理
原理和自动刷新的原理相似,都需要向要开发的网页中注入一个代理客户端来连接DevServer和网页,不同在于模块热替换的独特的模块替换机制。
DevServer默认不会开启模块热替换模式,要开启模式,则只需在启动时带上参数--hot,完整的命令是webpack-dev-server --hot。
除了通过在启动时带上--hot参数,还可以通过接入Plugin实现,相关代码如下:
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = {
entry: {
// 为每个入口都注入代理客户端
main: ['webpack-dev-server/client?http://localhost:8080/', 'webpack/hot/dev-server', './src/main.js'],
},
plugin: [
// 该插件的作用就是实现模块热替换,实际上若启动时带上--hot参数,就会注入该插件,生成.hot-update.json文件
new HotModuleReplacementPlugin(),
],
devServer: {
// 告诉Devserver要开启模块热替换模式
hot: true,
}
}
在启动webpack时带上参数--hot,其实就是自动完成以上配置。 启动后根据bundle.js日志可以看出,与自动刷新的代理客户端相比,多出三个用于模块热替换的文件,也就是说代理客户端更大了。
修改源码main.css文件后,DevServer重新生成一个替换老模块的补丁文件,这个补丁文件中包含了main.css文件新编译出来的css代码,网页中的样式也立刻变成了源码中描述的那样。
但是在修改main.js文件时候,热模块替换没有生效,而是整个页面被刷新了,为什么?
为了让使用者在使用模块热替换功能时能灵活的控制老模块被替换时的逻辑,webpack允许在源码中定义一些代码去做响应的处理:
将main.js文件修改如下:
import React from 'react';
import {render} from 'react-dom';
import {AppComponent} from './AppComponent';
import './main.css';
render(<AppComonent />, window.document.getElementById('app'));
// 只有当开启了模块热替换时module.hot才存在
if(module.hot) {
// accept函数的第一个参数指出当前文件接收哪些子模块的替换,这里表示只接收,/AppComponent这个子模块
// 第二个参数用于在新的子模块加载完毕后需要执行的逻辑
module.hot.accept(['./AppComponent'], () => {
// 在新的AppComponent加载完毕后需要执行的逻辑
render(<AppComponent />, window.document.getElementById('app'));
})
}
其中的module.hot是当开启模块热替换后注入全局的API,用于控制模块热替换的逻辑。 现在修改AppComponent.js文件,模块热替换生效,但是编辑main.js不会发生模块热替换,而是整个网页被刷新
因为在与当前子模块发生更新时,更新事件会一层层向上传递,从AppComponent到main.js文件,直到某层文件接收了当前变化的模块,即main.js文件中定义的module.hot.accept(['./AppComponent'], callback),这时就会调用callback函数去执行,这时会调用callback函数去执行自定义逻辑。如果事件一直向上抛,到最外层没有文件接收它,那么它会直接刷新网页。
问题又来了,没有地方接收css文件,但是修改所有css文件都会触发模块热替换,为啥?style-loader会注入用于接收css代码,所以他是有地方接收的
请不要将模块热替换技术用于线上环境,它是专门为提升开发效率而生的
优化模块热替换
在发生模块热替换时,我们会在浏览器控制台看到指明id为xx的模块被替换了,但是,我们怎么知道xx指的是哪个模块(不友好)。 所以webpack内置的NameModulesPlugin插件可以解决该问题,修改webpack配置文件接入该插件
const NameModulesPlugin = require('webpack/lib/NameModulesPlugin');
module.exports = {
plugin: [
// 显示出被替换模块的名称
new NameModulesPlugin(),
]
}
nice!!!
除此之外,模块热替换也面临和自动刷新一样的性能问题,因为他们都需要监听文件的变化和注入客户端,类似优化思路在上面提过,监听更少的文件,忽略node_modules目录下的文件,
但是关于关闭默认inline模式且手动注入代理客户端的优化方法,不能用于模块热替换的情况,因为模块热替换的运行依赖在每个Chunk中都包含代理客户端的代码