【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

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

评论

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

我喜欢的工作,喜欢我么?

escray

学习 面试

大数据技术发展(三):Spark 代替 Hadoop ? Spark Or Flink ?

cristal

Java 大数据 flink hadoop spark

a站、b站、c站、d站、e站、f站、g站、h站、i站、j站、k站、l站、m站、n站…z站?

程序员生活志

暴雪员工抗议薪酬不公,部分员工称甚至难以维持生计

程序员生活志

职场

终于可以职业规划了么?

escray

学习 面试

【Elasticsearch 技术分享】—— Elasticsearch ?倒排索引?这都是什么?

程序员小航

elasticsearch ELK 倒排索引 ES 技术分享

【API进阶之路】逆袭!用关键词抽取API搞定用户需求洞察

华为云开发者联盟

接口 软件开发 API 华为云 API Explorer平台

联邦学习初识

soolaugust

学习

SpreadJS 应用案例:电力自动化在线数据采集报表系统

葡萄城技术团队

SpreadJS 电力 报表

星火PLUS交易所打造无边界数字经济联盟,掀起币圈追捧热潮

InfoQ_967a83c6d0d7

如何使 Grafana as code

郭旭东

翻译 Grafana

史上最强DIY,手工制作一只会说话的机器狗

华为云开发者联盟

聊天机器人 nlp 华为云 语言识别 语言合成

卡丁车的后轴是如何做到差速的?

TGP大跨步

科普 卡丁车 TGP 大跨步 素材

如何选择一台打印机

别把虾米不当海鲜

90后程序员小姐姐在线征婚!年薪70w!拥有五套房!她却担心自己因为年龄大嫁不出去!

程序员生活志

程序员

我以后去做什么,技术还是业务?

escray

学习 面试 职业规划

一文读懂jar包的小秘密

程序那些事

Java jar jar包的小秘密 java解密

再见C++

Sunny.

c++ 踩坑

Linux Page Cache调优在Kafka中的应用

vivo互联网技术

大数据 kafka

芯片破壁者(十三):台湾地区半导体的古史新证

脑极体

性能全开的十代酷睿,造就惠普光影精灵 6 的电竞燃魂

最新动态

Centos7下service配置知识

3 分钟生成一个单元测试报告,这个样式爱了

程序员小富

Java 测试

28岁硕士女程序员想分手!对象专科学历,北京土著,失业3个月找不到工作!遭网友群嘲!

程序员生活志

程序员

一看就懂的三次握手

书旅

TCP 三次握手 操作系统 协议族

Docker 的前世今生

哈喽沃德先生

Docker 容器 微服务 虚拟化

一个@Transaction哪里来这么多坑?

程序员DMZ

spring 事务 读写分离

MySQL系列(一):MySQL深入学习先导篇之基础架构

z小赵

MySQL 数据库

一行错误代码:5 亿美元没了。。。项目关闭。。。

程序员生活志

他被称为"中国第一程序员",一人之力单挑微软!真牛!

程序员生活志

[8.20]leetcode每日一题,

一起搞稽

算法 DFS

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