产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

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

评论

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

企业社会责任先行,公益课程推动环保科普教育

科技之家

10分钟开发Kubernetes Operator

俞凡

架构 Kubernetes 云原生

32天高效突击:狂刷《Java权威面试指南(阿里版)》,offer拿到手软!

Geek_0c76c3

Java 数据库 开源 程序员 开发

干货|仅需3步完成酷炫数据可视化大屏制作!

云智慧AIOps社区

大前端 低代码 数据可视化 智慧大屏可视化 可视化大屏

openGauss 社区 2022 年 9 月运作报告

openGauss

知道了web的攻击方式,还不快防起来?

CoderBin

前端 安全 10月月更

【活动报名】共建云原生开源生态 PolarDB × Curve 线下 Meetup 来袭!(杭州站)

阿里云数据库开源

数据库 阿里云 开源 polarDB

defi质押挖矿存币生息理财系统开发

开发微hkkf5566

旺链科技入选2022“科创中国”创新成果名单

旺链科技

区块链 金融科技 产业区块链

易操作、可观测、可扩展,EMQX如何简化物联网应用开发

EMQ映云科技

运维 物联网 IoT emqx 10月月更

OpenHarmony有氧拳击之应用端开发

OpenHarmony开发者

OpenHarmony

啃完这些Spring知识点,我竟吊打了阿里面试官(附面经+笔记)

Geek_0c76c3

Java 数据库 开源 面试 开发

大数据ELK(十九):使用FileBeat采集Kafka日志到Elasticsearch

Lansonli

Filebeat 10月月更

软件测试 | 测试开发 | Google 测试总监聊如何经营成功的测试职业生涯

测吧(北京)科技有限公司

测试

【一Go到底】第九天---进制

指剑

Go golang 10月月更

欢迎数造科技加入openGauss社区

openGauss

带你认识什么是“回流重绘”

华为云开发者联盟

html 前端 浏览器 企业号十月 PK 榜

软件测试 | 测试开发 | Java or Python?测试开发工程师如何选择合适的编程语言?

测吧(北京)科技有限公司

测试

如何优雅地编写一个高逼格的JS插件?

茶无味的一天

JavaScript 前端 js JS插件

C语言中的内存模型

C++后台开发

内存模型 C语言 C/C++ linux开发 C++开发

携手武汉白鱀豚保护基金会,英特尔以责任为先多举推动环保公益

科技之家

独家巨献!阿里技术专家兼Github贡献者,整理的Spring Security入门到成神

Geek_0c76c3

Java 数据库 开源 程序员 开发

SQL抽象语法树及改写场景应用

京东科技开发者

sql SQL优化 场景应用 SQL语言 抽象语法树

你好,广州!openGauss广州用户组招募计划正式开启

openGauss

《编程的原则》读书笔记(二): 编程理论的三个思想和六个实现原则

Chares

软件工程 软件开发 编程原理 软件开发原则

Flowable 中 ReceiveTask 怎么玩?

江南一点雨

Java springboot workflow flowable JavaEE

openGauss 3.1.0版本正式发布 | 七个方面全面增强

openGauss

云图说丨带你了解GaussDB(for Redis)双活解决方案

华为云开发者联盟

数据库 数据资产 云数据库 企业号十月 PK 榜

算法统治者!打破传统方式,即将爆火的Leetcode刷题指南

Geek_0c76c3

Java 数据库 开源 程序员 开发

Google 发布:DevOps 2022现状报告

SEAL安全

DevOps 研发效能 软件交付 软件供应链

创新公司iLabService 释普科技启示录

B Impact

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