Webpack热更新实现过程

1,617 阅读5分钟

Webpack热更新( Hot Module Replacement)

Webpack热更新( Hot Module Replacement,简称 HMR,后续均以 HMR 替代),无需完全刷新整个页面的同时,更新所有类型的模块,是 Webpack 提供的最有用的功能之一。

HMR 作为一个 Webpack 内置的功能,可以通过 --hot 或者 HotModuleReplacementPlugin 开启。

Webpack 如何实现热更新的呢?首先是通过SSE单向通道建立起浏览器端和服务器端之间的通信,浏览器会接收服务器端推送的消息,如果需要热更新(比较编译前后文件的hash值是否不同),浏览器发起http请求去服务器端获取打包好的资源解析并局部刷新页面。

下面来看具体实现吧。

webpack构建

项目启动之后,会进行首次构建打包,控制台中会输出整个的构建过程,可以观察到一个 Hash 值 3606e1ab1ddcf6626797。

image.png 在每次修改代码后,可以在控制台看到打包信息中Hash值更新了,并且上次输出的Hash值被作为本次编译新生成的Hmr文件标识。同样,本次输出的 Hash 值会被作为下次热更新的标识。

image.png 如果没有任何改动,直接保存,控制台输出编译打包信息,Hash 值不会发生变化。

服务端编译

为什么代码的改动保存会自动编译,重新打包?这一系列的重新检测编译依赖于 Webpack 的文件监听:在项目启动之后,Webpack 会通过 Compiler 类的 Run 方法开启编译构建过程,编译完成后,调用 Watch 方法监听文件变更,当文件发生变化,重新编译,编译完成之后继续监听。

image.png 页面的访问需要依赖 Web 服务器,那要如何将 Webpack 编译打包之后的文件传递给 Web 服务器呢?这就要看 Webpack-dev-middleware了。Webpack-dev-middleware 是一个封装器( wrapper ),它可以把 Webpack 处理过的文件发送到一个 Server(其中 Webpack-Dev-Server 就是内置了 Webpack-dev-middleware 和 Express 服务器)。上面有说到编译之后的文件会被写入到内存,而 Webpack-dev-middleware 插件通过 memory-fs 实现静态资源请求直接访问内存文件。

constwebpack = require('webpack');

constwebpackConfig = require('./webpack.dev.conf');

constcompiler = webpack(webpackConfig);

debug('webpack编译完成');

debug('将编译流通过webpack-dev-middleware');

constdevMiddleware = require('webpack-dev-middleware')(compiler, {

// self-define options

});

上面代码可以看到,Webpack 编译打包之后得到一个 Compilation ,并将 Compilation 传递到 Webpack-dev-middleware 插件中,之前说到Webpack中的watch方法实时监控文件变化,重新编译打包写入内存,Webpack-dev-middleware 插件通过 memory-fs 通过静态资源请求访问内存文件并将文件传递给Web服务器。

服务端向浏览器通信

Webpack-hot-middleware 插件的作用就是提供浏览器和 Webpack 服务器之间的通信机制、且在浏览器端接收 Webpack 服务器端的更新变化。 源码中有这么一段配置,看到这里瞬间想到了在开发时浏览器的 Network 中总是有一个 __Webpack_hmr 的请求,点开查看会看到EventStream 事件流(服务器端事件流,服务器向浏览器推送消息,除了 websocket 全双工通道双向通信方式还有一种 Server-Sent Events 单向通道的通信方法,只能服务器端向浏览器端通过流信息的方式推送消息;页面可以通过 EventSource 实例接收服务器发送事件通知并触发 onmessage 事件),并且以 2s 的频率不停的更新消息内容,每行消息内容都有 ❤️ 的图标,没错这就是一个心跳请求。

image.png

image.png 当我们保存代码,webpack开始重新编译打包,打包完成后服务端通过SSE长连接通知浏览器端,浏览器开始请求webpack生成的hot-update文件比较hash值是否发生变化,如果hash值相同则不请求资源,否则浏览器开始根据hot-update中需要更新的模块标识通过jsonp请求相应资源。

image.png

image.png

总结

  • 第一步,在webpack的watch模式下,webpack会监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码保存在内存中

  • 第二步是webpack-dev-serverwebpack之间的接口交互,dev-server的中间件middleware调用webpack暴露的API对代码变化进行监控,并且告诉webpack,将代码打包到内存中

  • 第三步是webpack-dev-server对文件变化的一个监控,变化后会通知浏览器端对应用进行live reload

  • 第四步也是webpack-dev-server代码的工作,该步骤主要是通过SSE在浏览器端和服务端之间建立一个长连接,将webpack编译打包的状态告知浏览器端,服务端传递的最主要信息是新模块的hash值,后面会根据hash值来进行模块热替换

  • webpack-dev-server并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了webpack,webpack的工作就是根据客户端传给它的信息以及dev-server的配置决定是刷新浏览器呢还是进行模块热更新。如果仅仅是刷新浏览器,就没有后面的步骤了

  • HMR.runtime是客户端HMR的中枢,它接收到传递给他的新模块的hash值,向服务端发送Ajax请求,服务端返回一个json,该json就是更新列表,再次通过请求,获取到最新的模块代码

  • HotModulePlugin将会对新旧模块进行对比,决定是否更新模块,如果要更新,检查模块之间的依赖关系,更新模块的同时更新依赖

  • 最后,当HMR失败后,回退到live reload操作,也就是进行浏览器刷新来获取最新打包代码 浏览器更新视图流程还将继续更新...