Webpack原理系列(一)HMR

1,036 阅读6分钟

前言

相信很多人都使用过WebPack的热更新或者叫热替换(Hot Module Replacement),它让浏览器做到当我们修改源代码之后能够实时预览到修改之后的效果,也就是说我们不用对浏览器进行手动刷新就能看到效果,这在我们的开发过程尤为重要。

  • 原理 就是当一个源码发生变化时,只需重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的模块。

听起来很简单对吧,那我们具体的看看在这个过程中它到底做了哪些事情?

一、基本使用

开启功能

首先我们先来学会怎么使用它,DevServer默认不会开启模块热替换模式,要开启的话主要有俩种方式

  1. 在启动的时候带上参数--hot ,完整命令如下

webpack-dev-server --hot

  1. 通过接入Plugin实现
plugins:{
//插件作用就是实现模块热替换
     new HotModuleReplacementPlugin(),
}
devServer:{
    hot:true
}

作用范围

除此之外,我们需要了解热更新作用的范围

  • 样式文件 可以使用HMR功能,因为style-loader内部实现了,当我们修改css文件时,会出现以下的状态,可以看到明确显示出css文件的修改,浏览器局部更新替换模块

image.png

  • js文件 默认情况下是不能使用HMR功能的,它会去重新刷新整个页面,而不是局部刷新

image.png

如果我们需要js文件也具有HMR功能,为相应的js文件添加以下代码,但是需要注意,该方法只能处理非入口js文件的其他文件

if (module.hot) {
//为true,说明开启了HMR功能
  module.hot.accept('./index.js', function() {
//方法会监听index.js文件,一旦发生变化了,其他默认不会重新打包构建而执行后面的回调函数
  })
}


  • html文件 html文件不能进行热跟新,当我们修改html文件时,并没有去局部更新或者重新刷新浏览器。

image.png

如果我们需要进行热更新的话,将html文件添加到entry入口可以解决不能热更新的问题

二、原理解析

image.png

上图底部红色框内是服务端,而上面的橙色框是浏览器端。

绿色的方框是 webpack 代码控制的区域。蓝色方框是 webpack-dev-server 代码控制的区域,红色的方框是文件系统 要想了解上面的工作原理,先来简单认识一下相关的名称:

  • Webpack-dev-server:一个服务器插件,相当于express服务器,启动一个web服务器,只适用于开发环境
  • Webpack-dev-middleware: Webpack-dev-server 的中间件,作用是监听资源变更,然后进行打包
  • Webpack-hot-middleware:中间件,可以实现浏览器的无刷新更新

流程

接下来,我们详细的看一下HMR整个的工作原理:

1. 监控代码变化,重新编译打包

在Webpack的watch模式下,文件系统中某个文件的修改,监听到文件的变化,根据配置文件对模块重新编译打包

2.保存编译结果

中间件Webpack-dev-middleware 调用 Webpack 的 向外暴露的API 对代码变化进行监控,并且告诉 webpack,将打包后的代码通过简单的JavaScript对象保存在内存中。

  • 为什么不将编译后的结果放在指定的输出目录?

因为访问内存的代码比访问文件系统中的文件要快,而且可以减少写入代码的开销

3.监听文件变化,刷新浏览器

Webpack-dev-server 开始监控文件变化,注意:这里并不是监控代码变化重新编译打包。 当我们在配置文件中配置了 devServer.watchContentBase 为 true ,Webpack-dev-server 会监听配置文件夹中静态文件的变化,发生变化时,通知浏览器端对应用进行浏览器刷新,这和HMR是两个概念

4.建立websocket协议,同步编译状态

通过webpack-dev-server的依赖sockjs在浏览器和服务端之间建立一个websocket长连接,然后将webpack编译打包的各个阶段状态信息同步到浏览器端,同样也包括第三步中Webpack-dev-server 监听文件变化的信息。

在这里主要关注服务端传递的重要信息------新模块的hash值(Webpack-dev-server 通过 _sendStats 方法将编译后新模块的 hash 值用 socket 发送给浏览器端。),后续的步骤浏览器会根据hash值来进行模块热替换

5.浏览器端发布消息

由于webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack。

6.传递hash到HMR

HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收上一步传递给它的hash值。

  • webpack-dev-server/client 调用check方法检测更新,根据 webpack-dev-server/client 传递的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。,如果是浏览器刷新的话,就没有下面的步骤了
7.检查是否存在更新

HotModuleReplacement.runtime调用check()方法时,会调用JsonpMainTemplate.runtime中的两个方法

hotDownloadUpdateChunk (获取最新模块代码)和 hotDownloadManifest (获取是否有更新文件)

8.请求更新文件列表

调用hotDownloadManifest 方法向服务器发起AJAX请求获取是否有更新文件,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值

9.请求更新最新模块

调用hotDownloadUpdateChunk 方法通过上一步获得的更新列表,通过jsonp请求来获取最新的模块代码。并且将代码返回给HMR runtime,它会去判断是浏览器刷新还是模块热更新

10.更新模块和依赖

通过HMR runtime的hotApply方法,移除过期的模块和代码,添加上新的模块和代码从而实现热更新

11.热更新错误处理

在更新过程中,hotApply过程中可能会出现abortfail错误,则热更新退回到刷新浏览器,这样整个模块热更新就完成了。 以上就是HMR的整个原理分析。

总结

接下来我们简化总结一下整体的流程

  • HMR是通过Webpack配合Webpack-dev-server进行应用开发的,通过webscoket维持一个长连接从而使客户端和服务端进行通信的

  • 服务器端的处理主要为监听变化,主要为以下几点:

  1. Webpack负责监听文件变化,重新打包,保存编译结果到内存中
  2. Webpack-dev-server负责通知webpack保存编译结果,监听文件变化,通知浏览器端进行刷新
  • 浏览器端的处理主要为根据类型实现更新,主要有以下几点:
  1. webpack-dev-server负责建立一个长连接以便同步状态信息(hash)

  2. webpack负责接收浏览器发送的hash信息并且判断刷新类型,以决定HMR流程是否继续,如果继续就转发hash信息至HMR核心中枢

  3. HRM核心中枢负责通过两个方法hotDownloadUpdateChunk(模块代码更新) 和 hotDownloadManifest(文件更新)与服务端webpack-dev-server进行请求交互,从而获取更新操作需要的资源。通过hotApply实现热更新。 如果在此过程出现错误回退到浏览器更新。

以上就是HMR的原理解析的全部了,如果想要更深入的了解HMR的,建议去慢慢阅读源码