写点什么

看完这篇,再也不怕被问 Webpack 热更新

  • 2021-01-28
  • 本文字数:4234 字

    阅读完需:约 14 分钟

看完这篇,再也不怕被问 Webpack 热更新

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


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

刷新分为两种:一种是页面刷新,不保留页面状态,就是简单粗暴,直接 window.location.reload();另一种是基于 WDS(Webpack-dev-server)的模块热替换,只需要局部刷新页面上发生变化的模块,同时可以保留当前的页面状态,比如复选框的选中状态、输入框的输入等。


HMR 的好处,在日常开发工作中体会颇深:节省宝贵的开发时间、提升开发体验。引用官网的描述来概述一下:


模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:


  • 保留在完全重新加载页面期间丢失的应用程序状态。

  • 只更新变更内容,以节省宝贵的开发时间。

  • 在源代码中对 CSS / JS 进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。


在开发环境,可以将 HMR 作为 LiveReload 的替代。那么,HMR 到底是怎么实现热更新的呢?下面通过观察编译构建过程,以及分析源代码来了解一下。


本次探索依赖公司前端 Vue 项目开发脚手架配置:Webpack + Webpack-Dev-Middleware + Webpack-Hot-Middleware + Express。


webpack 构建


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


image-20190925192508536


在每次代码的修改后,保存时都会在控制台上出现 compiling…字样,可以在控制台中观察到:


  • Hash 值更新:4f8c0eff7ac051c13277;

  • 新生成文件:3606e1ab1ddcf6626797.hot-update.json

  • main1.3606e1ab1ddcf6626797.hot-update.js


image-20190925192526730


如果没有任何改动,直接保存,控制台输出编译打包信息,Hash 值没有发生变化,仍旧是 4f8c0eff7ac051c13277。


image-20190925192738074


再次修改代码保存,控制台输出编译打包信息。根据文件名可以发现,上次输出的 Hash 值被作为本次编译新生成的 Hmr 文件标识。同样,本次输出的 Hash 值会被作为下次热更新的标识。


image-20190925192752213


Webpack Watch


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


image-20190925003133799


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


   const webpack = require('webpack');   const webpackConfig = require('./webpack.dev.conf');   const compiler = webpack(webpackConfig);   debug('webpack编译完成');   debug('将编译流通过webpack-dev-middleware');   const devMiddleware = require('webpack-dev-middleware')(compiler, {   // self-define options   });
复制代码


上面代码可以看到,Webpack 编译打包之后得到一个 Compilation ,并将 Compilation 传递到 Webpack-dev-middleware 插件中,Webpack-dev-middleware 可以通过 Compilation 调用 Webpack 中 的 Watch 方法实时监控文件变化,并重新编译打包写入内存。


留意一下浏览器端,在 Network 中可以看到几个请求:


/__Webpack_hmr 请求返回的消息包含了首次 Hash 值,每次代码变动重新编译后,浏览器会发出 hash.hot-update.json、fileChunk.hash.hot-update.js 资源请求。


image-20190924112630734


点开查看 hash.hot-update.json 请求,返回的结果中,h 是一个 hash 值,用于下次文件热更新请求的前缀,c 表示当前要热更新的文件是 main1 。


image-20190924112716610


继续查看 fileChunk.hash.hot-update.js,返回的内容是使用 webpackHotUpdate 标识的 fileChunk 内容。


image-20190924113157232


那么 Webpack 服务器和浏览器端是怎么建立起通信的呢?这就是 Webpack-hot-middleware 插件的功劳了。Webpack-hot-middleware 的 README.md 文档中有这样一段描述:


This module is only concerned with the mechanisms to connect a browser client to a Webpack server & receive updates.

Webpack-hot-middleware 插件的作用就是提供浏览器和 Webpack 服务器之间的通信机制、且在浏览器端接收 Webpack 服务器端的更新变化。


为了更好的理解这一段话,打开浏览器开发者调试工具,可以看到在 Webpack 打包好的 Js 中主要包含了以下几部分。下面截取关键部分进行说明:


  • Webpack-hot-middleware/client.js


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


 var options = {   path: '/__Webpack_hmr',   timeout: 20 * 1000,   overlay: true,   reload: false,   log: true,   warn: true,   name: '',   autoConnect: true,   overlayStyles: {},   overlayWarnings: false,   ansiColors: {}, };
复制代码


继续向下查看 Client.js 代码,发现这完全就是一个只要浏览器支持就可以自发建立通信通道的客户端。


 if (typeof window === 'undefined') {   // do nothing } else if (typeof window.EventSource === 'undefined') {   // warning } else {       if (options.autoConnect) {       // 建立通信连接         connect();       } }
复制代码


在建立通信的过程中,浏览器端会初始化一个 EventSource 实例并通过 onmessage 事件监听消息。浏览器端在收到服务器发来的数据时,就会触发 onmessage 事件,可以通过定义 onmessage 的回调函数处理接收到的消息。


// 浏览器端建立连接 function EventSourceWrapper() {     var source;     var listeners = [];     // 初始化EventSource实例      source =  new window.EventSource(options.path);     // 定义onmessage事件监听服务器端消息返回     source.onmessage = handleMessage;     function handleMessage(event) {         for (var i = 0; i < listeners.length; i++) {             listeners[i](event);         }     }     return {         addMessageListener: function(fn) {             listeners.push(fn);         }   }; } // 浏览器端建立通信通道,监听处理服务器端推送的消息 function connect() {       EventSourceWrapper().addMessageListener(handleMessage);       function handleMessage(event) {           try {            processMessage(JSON.parse(event.data));          } catch (ex) {            // handler exception          }       } }
复制代码


Client.js 监听的消息有:


  • building/built:构建中,不会触发热更新

  • sync:开始更新的流程


在 processUpdate 方法中,处理一切异常/错误的方法都是直接更新整个页面即调用 window.location.reload(),首先调用 module.hot.check 方法检测是否有更新,然后进入 HotModuleReplacement.runtime 的 Check 阶段。


 function processMessage(obj) {   switch (obj.action) {     case 'building':       // tell you rebuilding       break;     case 'built':       // tell you rebuilt in n ms     // fall through     case 'sync':        // 省略...       var applyUpdate = true;       if (applyUpdate) {         processUpdate(obj.hash, obj.modules, options);       }       break;     default:       // do something   } }
复制代码


热更新过程


改动页面代码保存之后,Webpack 会重新编译文件并发消息通知浏览器,浏览器在 Check 之后触发 WebpackHotUpdateCallback,具体 HotModuleReplacement.runtime.js 会做以下几个操作:


  • 进入 HotCheck,调用 hotDownloadManifest 发送 /hash.hot-update.json 请求;

  • 通过 Json 请求结果获取热更新文件,以及下次热更新的 Hash 标识,并进入热更新准备阶段;


hotAvailableFilesMap = update.c;// 需要更新的文件hotUpdateNewHash = update.h;// 下次热更新hash值hotSetStatus("prepare");// 进入热更新准备状态
复制代码


  • HotCheck 确认需要热更新之后进入 hotAddUpdateChunk 方法,该方法先检查 Hash 标识的模块是否已更新,如果没更新,则通过在 DOM 中添加 Script 标签的方式,动态请求 js:/fileChunk.hash.hot-update.js,获取最新打包的 js 内容;

  • 最新打包的 js 内容如何更新的呢?HotModuleReplacement.runtime.js 在 window 对象上定义了 WebpackHotUpdate 方法;在这里定义了如何解析前面 fileChunk.hash.hot-update.js 请求返回的 js 内容 webpackHotUpdate(main1, { moreModules }),直接遍历 moreModules,并且执行 hotUpdate 方法更新;

image-20190925145730597


image-20190925150635231

结语


至此页面已经完成热更新,Webpack 如何实现热更新的呢?首先是建立起浏览器端和服务器端之间的通信,浏览器会接收服务器端推送的消息,如果需要热更新,浏览器发起 http 请求去服务器端获取打包好的资源解析并局部刷新页面。


头图:Unsplash

作者:米亚

原文:https://mp.weixin.qq.com/s/Vh7jyg9yWHabw_a2pjm8rQ

原文:看完这篇,面试再也不怕被问 Webpack 热更新

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-01-28 23:214292

评论

发布
暂无评论
发现更多内容

博客之星:我去,你竟然还不会用 synchronized

Java 程序员 后端

又一巅峰神作!14年工作经验大佬出品“JVM&G1 GC深入学习手册”

Java 程序员 后端

活动预告|ArchSummit全球架构师峰会

第四范式开发者社区

双非本科毕业的我,为何能在金九银十期间斩获京东、字节、快手的offer

Java 程序员 后端

发量能决定一个程序员的水平吗

Java 程序员 后端

数据服务基础能力之元数据管理

数据分析 数据 元数据 数据管理 业务数据

南邮《网络技术与应用》4次作业

Java 程序员 后端

工作10年,面试超过500人想进阿里的同学,总结出的108道面试题

Java MySQL redis spring JVM

双非本科七面成功入职阿里面经分享!(附面试原题+复盘笔记)

Java 程序员 后端

如何避免企业在碳排放数据上造假?

石云升

学习笔记 碳中和 碳交易

史上最全Java面试266题:算法+缓存+TCP+JVM

Java 程序员 后端

可以回答一下:Redis和mysql数据是怎么保持数据一致的嘛?(1)

Java 程序员 后端

同程内网流传的分布式凤凰缓存系统手册,竟遭GitHub强行开源下载

Java 程序员 后端

Apache Pulsar 在 BIGO 的性能调优实战(下)

Apache Pulsar

分布式 中间件 BIGO Apache Pulsar 消息系统 Apache BookKeeper

怎样选择最合适的Linux发行版?23个版本横向对比,总有适合你的?

奔着腾讯去

Linux

吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?

Java 程序员 后端

吐血总结——90%程序员面试都用得上的索引优化手册

Java 程序员 后端

听我讲完GET、POST原理,面试官给我倒了杯卡布奇诺

Java 程序员 后端

厉害!腾讯T3-2都还在学的微服务+MySQL+Kafka+boot2

Java 程序员 后端

企业数字化转型的起手式是什么?

百度大脑

人工智能 百度

可以回答一下:Redis和mysql数据是怎么保持数据一致的嘛?

Java 程序员 后端

【高并发】SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)

冰河

Java 并发编程 多线程 高并发 异步编程

同一个Spring-AOP的坑,我一天踩了两次,深坑啊

Java 程序员 后端

网络安全漏洞复现与分析

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 漏洞挖掘

同事问我如何Java实现,搞定分析栈和队列数据结构的实现过程不就好了

Java 程序员 后端

哭了,我居然回答不出来女同事的问题:索引为什么能提供查询性能---

Java 程序员 后端

原来书中说的JVM默认垃圾回收器是错的!

Java 程序员 后端

双非本科进不了大厂?阿里技术四面+交叉面+HR面,成功拿到offer

Java spring 程序员 mybatis

【Redis源码分析专题】(1)从本质分析你写入Redis中的数据为什么不见了?

洛神灬殇

redis Redis 核心技术与实战 11月日更 缓存驱逐

同一份数据,Redis为什么要存两次

Java 程序员 后端

哪有什么中年危机,不过是把定目标当成了有计划

Java 程序员 后端

看完这篇,再也不怕被问 Webpack 热更新_语言 & 开发_政采云前端团队_InfoQ精选文章