随笔|闲扯前端工程化③---浏览器的热更新
上一节咱们简单了解了脚手架的相关知识,比较了两个脚手架的异同之处,以及简单介绍了如何去定制脚手架。
对脚手架的认识加深有助于我们能够快速进行技术选型,锻炼我们的技术选型思维,提升开发效率。
说到效率,就不得不提一下热更新技术,相对于每次修改完代码都需要刷新页面,热更新技术可以自动刷新界面,妥妥的提高我们的效率。
而说到热更新,我们很容易想到webpack、以及webpack-dev-server,现在的脚手架在创建项目时基本上都已经配置好了,我们可以直接启动项目进行开发。
但是,到底什么是热更新?
是保存修改后的代码然后进行编译?还是说只是简单的刷新浏览器呢?或者是不同的模块进行替换呢?
实际上,浏览器的热更新指的是我们在进行本地开发时,当代码文件发生变化时,浏览起自动更新界面内容的这么一个技术。
而刷新页面有分为两种。一种是自动刷新整个页面,另一种是只刷新部分界面内容。
早期的开发流程,每次我们修改代码后,都需要手动刷新浏览器才能看到变更后的效果,甚至是改了代码之后,需要手动编译代码打包后再刷新浏览器,而热更新就是为了解决这个问题。
我们以webpack的配置为例:
// src/index.js
function render(){
div = document.createlement('div')
div.innerhtml = "test"
document.body.appendchild(div)
}
render()
// webpack.config.basic.js
module.exports = {
entry:'./src/index.js',
mode:'development'
}
// package.json
{
"scripts":{
"build:basic":"webpack --config webpack.config.basic.js"
}
}
入口文件中简单打印一个文本,然后在配置里只有最简单的entry 和 mode, 当我们执行build:basic的时候,webpack会将index.js打包到dist中,过程非常简单。
但是当我们改动index.js 文件之后,我们会发现,由于我们的配置没有其他处理,修改保存后,打包内容并没有进行更新,我们需要重新进行build。
要解决这个问题,这就需要我们再加一个配置:
module.exports = {
entry:'./src/index.js',
mode:'development',
watch:true
}
加上watch配置之后,webpack可以监听到源文件的变化,就可以进行自动编译,我们就不用每次去手动执行打包的脚本。
但是,问题并没有解决,我们还需要在浏览器中进行预览,这个时候还是需要我们手动刷新才能看到效果。
这就需要增加新的配置live reload,当代码修改后使浏览器能够自动显示最新的效果,从而避免手动刷新。
这就需要有一种通信机制将浏览器和本地监听代码变更的逻辑联系起来。在webpack 中,提供了devServer来解决这个问题。
// webpack.config.basic.js
module.exports = {
devServer:{
contentBase:'./dist',
open:true
}
}
// package.json
{
"scripts":{
"dev":"webpack-dev-server --config webpack.config.js"
}
}
当我们执行npm run dev 后,就可以看到加载的界面。
与此同时我们还可以看到一个websocket链接:
正式有了这个websocket在网页和本地服务之间建立了一个通信机制,当代码发生变化时,通过socket通知页面进行更新。
但是还有一种情况是,当我们修改代码之后,会发现页面上之前的内容或者状态都回到了最原始的状态,我们又得重新操作一遍才能最终确认界面效果。
对于这个问题,就需要用到模块热替换也就是hmr。
// src/inde.js
import './index.css'
// index.css
.header{
background:bule
}
...
// webpack.config.hmr.js
module.exports = {
devServer:{
contentBase:'./dist',
...
open:true,
...
hot:true,
},
module:{
rules:[
{
test:/.css/,
use:['style-loader','css-loader']
}
]
}
}
// package.json
{
"scripts":{
"dev":"webpack-dev-server --config webpack.config.hmr.js"
}
}
我们在devServer中增加hot:true这个配置,module中增加样式的loader,style-loader 和css-loader 用来解析样式文件。
这个时候我们再次执行npm run dev ,我们可以看到样式被添加到页面中:
当我们修改代码中的样式后再回带页面上,我们可以看到两个文件,一个hot-update.json,一个hot -update.js
而在页面上则增加了hot-upate.js,同时页面也替换了我们修改后的样式。
对于样式文件我们可以通过上面的配置来看到热替换效果,那么js文件呢?比如我们改动js中要显示在页面中的文本。
实际上,简单改动js中要显示在页面中的文本,并不能达到热替换效果。
为什么样式文件能触发热替换,但是js反而触发不了热替换呢?
实际上,完整的热更新技术主要有三个部分组成:
一,对本地代码文件内容变更的监听。
二,reload事件中浏览器网页端与本地服务器的websocket通信
三,核心的模块解析与替换功能。
这三个内容中,代码文件监听的实现是基于node中的fs文件模块,node的文件模块中提供了watch方法,可以实现文件变动后获取文件变动内容。
而通信部分,则是实用socket技术。
模块解析和替换我们需要先了解webpack中的几个概念:
- module:指的是模块化编程中将程序分割成独立的代码模块
- chunk: 指的是模块按照引用关系组合成的代码块儿,并且,一个 chunk 可以包含多个 module
- chunk goup: 指的是通过配置入口区分的 chunk 组,同理,一个 chunk goup 可以包含多个 chunk
- bundling:指的是整个打包的过程
- bundle :指的的打包出来的东西、产物
而webpack的理念就是一切资源都可以通过各种loader转化为js模块儿,并且模块二之前可以相互引用。
webpack通过递归来处理各个模块儿之间的关系,最终输出为一个或者多个bundle。
如果我们不考虑分包的情况,一个chunk group只会包含一个chunk,而这个chunk包含所有递归后的模块儿。
它的基本流程是:首先根据entry入口创建一个group,包含第一个入口文件,然后使用解析器解析这个文件的代码,然后会发现入口文件中依赖的有别的样式,接下来,webpack会在配置文件中找到对应的 loader,对样式进行处理,转化为js能执行的模块。
同时还会输出一个样式 loader 自带的 run time运行时的api.js模块儿.
而loader处理样式或者其他内容的过程就是替换的过程。
实际上,webpack提供了一个热替换插件HotModuleReplacementPlugin,提供了热替换的基础功能,它提供了一系列的api去导入model ,当依赖模块发生变化时,对应的回调方法就被执行,从而调用 update 方法对页面进行更新。
(完)
没有关注公众号的朋友,觉得文章对您有启发的话,记得点赞、关注、评论、转发一下。