【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

看完这篇,再也不怕被问 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:213918

评论

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

Node.js 做 Web 后端的优势在哪?为什么是明智的选择?

高端章鱼哥

node.js Web 后端开发

低代码平台技术分享官丨业务流那些事之单据追踪

inBuilder低代码平台

从技术角度聊聊2023年怎么入局小游戏赛道?

FN0

小游戏 小游戏开发 小游戏引擎 小游戏运营

06. 机器学习入门2 - 理解特征和向量

茶桁

人工智能 机器学习 特征向量

软件测试|Python删除列表元素的3种方法,你都会吗?

霍格沃兹测试开发学社

软件测试|一步到位教会你Python字典操作(一)

霍格沃兹测试开发学社

软件测试|pip命令,你真的会了吗?

霍格沃兹测试开发学社

与创新者同行!Apache Doris 首届线下峰会即将开启,最新议程公开!|即刻预约

SelectDB

数据库 大数据 数据仓库 数据分析 实时数仓

软件测试|Python实用炫酷技能——推导式

霍格沃兹测试开发学社

软件测试|f-string格式化输出的这些用法,90%的Pythoner不知道

霍格沃兹测试开发学社

天翼云GPU云主机:共享信息技术与虚拟机的完美融合

天翼云开发者社区

云计算 云主机

软件测试|最全的Python for循环和while循环使用介绍

霍格沃兹测试开发学社

软件测试|教你轻松玩转Python日期时间

霍格沃兹测试开发学社

软件测试|一篇文章教你SQL与NoSQL、数据库重要概念、SQL的基本语句

霍格沃兹测试开发学社

软件测试|Python高手教你玩转 Excel 自动化

霍格沃兹测试开发学社

软件测试|神操作!用 Python 操作 xmind 绘制思维导图

霍格沃兹测试开发学社

语音识别技术的挑战与机遇

来自四九城儿

低代码助力企业数字化转型:实现高效应用开发与部署

互联网工科生

低代码 数字化

OP链质押挖矿系统开发源码搭建

l8l259l3365

软件测试|教你如何用Python获取昨天今天明天的日期

霍格沃兹测试开发学社

X2RTC正式上线!抢先体验已开启

X2Rtc

开源 音视频 RTC

软件测试|PC端应用自动化最佳解决方案——Pywinauto

霍格沃兹测试开发学社

软件测试|什么是Python函数及名称空间?

霍格沃兹测试开发学社

KDD 2023 | 蚂蚁“优化器三部曲”之 WSAM

AI Infra

人工智能 开发者 算法 优化器 KDD

FaceFusion:探索无限创意,创造独一无二的面孔融合艺术!

汀丶人工智能

人工智能 深度学习 计算机视觉 图像生成

语音识别技术的应用及优化

来自四九城儿

FaceFusion:探索无限创意,创造独一无二的面孔融合艺术!

汀丶人工智能

人工智能 深度学习 计算机视觉

软件测试|Python神器logging,你真的了解吗?

霍格沃兹测试开发学社

软件测试/测试开发丨App自动化—高级控件交互方法

测试人

Python 程序员 软件测试 自动化测试

智慧公厕建设的好处和意义?提高城市形象和吸引力的秘密武器

光明源智慧厕所

智慧厕所 智慧公厕

低代码开发那些事儿

这我可不懂

低代码

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