Vue 项目之 Webpack 中 DevServer 的模块热替换(4)

355 阅读5分钟

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战

1. HMR 的原理解析

  • HMR 的原理是什么呢?它是如何做到只更新一个模块中的内容,而不刷新整个浏览器页面的呢?
    • webpack-dev-server 会创建两个服务:提供静态资源的服务(expressSocket 服务(net.Socket
    • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);
  • HMR Socket Server 是一个 socket 的长连接
    • 长连接有一个最大的好处是建立连接后双方可以即时通信(服务器可以直接发送文件到客户端);
    • 当服务器监听到对应的模块发生变化时,会生成两个文件:.json 文件(manifest 文件)和 .js 文件(update chunk
    • 通过长连接,可以直接将这两个文件主动发送给客户端(浏览器);
    • 浏览器拿到这两个新的文件后,通过 HMR runtime 机制,加载这两个文件,然后更新修改了的模块

下面我们画图简单理解一下 HMR 的原理:

HMR 原理.png

我们的源代码会先交给 webpack 进行编译,编译完后会打包出来对应的资源(比如 bundle.js 这个资源,或者还有一些其它的资源),这个时候,webpack-dev-server 中就会开启一个 express server(一个静态服务器),之后如果我们的浏览器发送了 http 请求(请求我们的静态资源),那么 express server 就会做 http 的一个响应,响应之后,浏览器就请求下来了对应的资源,然后就可以加载这个资源中的内容,最后在浏览器上展示出来对应的效果。以上,就是在没有开启 HMR 时浏览器从请求到加载资源的过程。

而一旦开启了 HMRwebpack-dev-server 就会另外开启一个服务,我们可以称之为 HMR Server,即模块热替换的服务器,其本质是一个 Socket ServerScoket Server 和普通的 Http Server 有什么区别呢?简单来说,Socket 一般是用来建立长连接的,而 Http 的服务器一般建立的是 Http 链接,也称为短连接(一次简单的短连接的步骤一般是这样的:客户端发送 http 请求 -> 和服务器建立连接 -> 服务器做出响应 -> 响应完后断开连接。为什么要断开呢?一直连接着不好吗?因为服务器一般能承受的连接数是有限的,所以如果同一时刻有很多连接向服务器请求资源,服务器的压力就太大了,因此一般情况下服务器在响应完成后会立马断开连接)。而 Socket 长连接一般用在即时通信中,比如微信里面朋友之间相互发消息、直播中的聊天(发送一句话立马就能在直播间显示)、直播间里的送礼物、直播间的进场消息,其实它们本质上都是 socket,只不过在 socketpackage 里面,它们的 type 是不一样的而已。Socket 通信的特点是它建立的连接是长连接(相当于经过 3 次握手或者 5 次握手等各种连接之后,通信双方之间会建立起一条通道,这条通道会通过心跳包(比如判断客户端还存不存在)一直保持着),长连接一直建立了一条通道有什么好处呢?相比于短连接是客户端必须主动发起请求,服务器才能做出响应,在短连接中很少出现服务器主动把某个消息发送给客户端,长连接则在服务器和客户端之间有了通道之后,能让双方实时地向对方发送消息,即实现了实时通信。

再来看我们这里的 HMR,假如我们源代码中的某个文件发生改变了,如果只想更新这一个文件,就必然需要让 webpack 监听文件的变化,一旦监听到了某个文件的变化,就会对这个文件重新编译,之后有两种方案可选:

  1. 告诉浏览器对整个页面进行刷新,而一旦进行刷新,就会对重新请求所有的资源,之前已经说过此方式的弊端(包括页面状态丢失、性能问题等),所以我们一般不采用这种方式;

  2. 针对这个发生变化了的文件在 HMR Socket Server 中生成对应的两个文件:

    1. manifestjson 文件(用来描述哪个文件哪个部分发生变化了);
    2. js 文件(记录具体变化的内容);

    然后由服务器通过 socket 长连接将这两个文件主动发送给客户端,发送过去之后,在客户端那边会有一个 HMR runtime 机制,它会监听到服务器传过来的这两个文件,之后就会根据这两个文件决定浏览器页面的哪一部分需要进行更新(这也是我们前面在浏览器中看到的模块热替换相关的内容)。

以上,就是 HMR 的原理。就是监听文件的变化,当某个文件发生变化后,拿到它重新编译后的内容,然后通过 socket 让服务器主动发送给客户端,客户端再做对应的更新即可。