webpack热更新原理-连阿珍都看懂了

news2024/11/18 5:51:29

前言

在旧开发的时代,前端项目在开发的过程中修改代码,很有可能是手动切到浏览器刷新页面来看到改动效果。操作不方便且页面之前的编辑记录也都丢失,体验可以说为0。想象一下一个表达你努力填满了所有输入项,结果因为调了一下样式就丢失了所有输入内容,你的心情如何?

随后变出现live reload自动刷新,解决了手动切的尴尬操作但页面的状态问题仍未解决。

随着前端工程化的推进,HMR(Hot Module Replace)热模块替换的技术的出现急不可耐,我们可以很简单的配置便能感受这优雅的开发体验。>

下面就让我们一起分析HMR是如何工作的吧!

什么是浏览器的热更新?

什么是浏览器的热更新?有些同学可能认为是保存自动编译、或者是自动刷新浏览器就叫热更新;又或者代码能够热替换就是热更新。

这些说的都对,但不全面。浏览器热更新技术严格的讲,指的是我们开发时能够在浏览器页面中实时看到我们代码的变化产生效果的流程。热更新技术包含了监听本地文件、增量编译构建、代码热替换、socket通信等技术点。

HMR最简单的理解

相信你已经或多或少看到过一些监听开发代码改动,浏览器与静态服务器socket通信的一些HMR相关点。

请添加图片描述

这个图描绘了大致的工作原理。能够帮助你站在更高的角度去俯视这个过程中的流转、以及关键角色。
接下来就是对其中的流程一步一步进行剖析。

HMR流程原理

先了解webpack热更新的重要插件

  • webpack-dev-server
  • webpack-dev-middleware
  • webpack-hot-middleware

这里可能有很多同学会混淆这几个插件的作用。

首先来看webpack-dev-middleware插件,他最主要是能够将webpack编译产物提交给服务器,并且他将产物放置在内存中【这也是为啥我们在开发过程中在文件夹中找不到我们的打包代码】;

webpack-hot-middleware插件则提供热更新的能力【通过使用webpack提供的热更新API实现,所以背后干活的还是webpack】;这个插件在新版本中似乎已经不再需要了

这两个插件一般都是配套使用的,以提供完整的热更新能力。

webpack-dev-server是【node-express[启动服务] + webpack-dev-middleware + webpack-hot-middleware】的集成封装,有了他,你就可以方便的启动一个具有热更新功能的本地服务器。不用你自己手写什么let app = new express();app.listen(8080);这样的启动服务代码了。

再补充一些知识点:

  • webSocket:长连接双向通信【让服务器与浏览器连接起来,方便通信】
  • webpack的构建可以选择watch模式,他自身已经具备了监听文件变化,持续构建的能力。但还未有将自动刷新客户端的能力,并且对新构建的文件未有进行热替换的能力。
  • 而webpack-dev-server则是提供自动刷新客户端的能力,他启动一个静态资源托管服务器,客户端访问该服务器,并且通过socket与客户端长连接以便在新构建后通知到客户端进行更新
  • 而webpack内置插件HotModuleReplacement.plugin插件则是提供热替换的能力:在客户端拿到新代码后,将新代码与旧代码替换

tips: webpack热更新的过程中涉及到比较多的角色,一定要区分好各个角色在过程中发乎着不同的作用。

让我们运行npm run serve,开始分析这个过程吧!

package.json

...
  "scripts": {
    "build": "webpack",
    "serve": "webpack-dev-server"
  },
...

第一阶段:初始化环境

兜兜转转便来到了webpack-dev-server/lib/Server.js

webpack-dev-server做了非常关键的3个动作:

  • 启动静态服务器
  • 开通长连接
  • 为打包产物注入HMR代码【使得客户端具备热更新能力】

启动静态服务器、开通长连接这两个动作很好理解,也可以对应上图去理解。
那么【 为打包产物注入HMR代码【使得客户端具备热更新能力】是啥意思呢?

想象一下,当你的代码在浏览器运行的时候,怎么跟服务器进行长连接呢?这部分代码你肯定是没有写过。这是由webpack-dev-server插件注入到我们产物代码中的。此外除了长连接代码,还有热更新的代码替换等代码都是要被注入的。

我们来看看插件是如何在我们的产物中注入代码的?

过程比想象的要简单,通过在我们的webpack入口entry中新增文件,在webpack进行构建的时候从entry开始顺藤摸瓜组织代码便一同打包进来。

我们可以看到注入了两个文件:

  • webpack-dev-server/client 【socket管理】
  • webpack/hot/dev-server 【热更新能力】
    请添加图片描述

假如你想写个代码弄崩你们项目,是不是可以考虑写个无限递归的代码用这种代码注入的方式去执行呢。这样一来排查就相对困难啦,到时候再化身救火秃神!想想都觉得厉害。

image.png

紧接着构建过程以compiler.watch()的方式运行,这种模式下打包完不会结束掉打包进程,而是持续运行,监听文件变化后进行增量构建【前面有提到这是webpack的能力之一】。一般npm run build的方式就是执行compiler.run()打包一次就结束了。

compiler.watch()构建后的产物通过webpack-dev-middleware插件提交到服务器。

而webpack提供的持续监控文件变化的能力是基于node的fs.watch()实现的。他的原理是通过轮询文件的修改时间比对去判断该文件是否发生变化。

至此我们的项目处于一种运行的状态,能够对代码改动触发增量构建并更新到服务器。

以上的过程可以理解为 环境初始化阶段。

第二阶段:客户端热更新

接着我们打开一个浏览器访问:localhost:XXX时,就能够访问到我们的页面。

请添加图片描述

我们可以看到浏览器已经跟服务器进行了长连接。

先来看看我们的项目代码:

入口文件index.js // 导入writeA.js并处理writeA.js的热更新逻辑

import './writeA'
if(module.hot) {
    module.hot.accept('./writeA.js', () => {
        console.log('index.js 捕获到writeA.js的变化...');
        let writeAEl = document.getElementById('ttt');
        if(writeAEl) {
            writeAEl.parentNode.removeChild(writeAEl)
        }
    })
}

writeA.js // 给body插入元素

let myElement = document.createElement('div')
myElement.innerText = 'writeA.js 123456'
myElement.id = 'ttt'
document.body.appendChild(myElement);
console.log('writeA.js run...');

代码很简单,module.hot.accept的意思就是捕获writeA.js的改动,如果他改动了就触发逻辑。module.hot.accept的触发时机会在后面提到。

然后修改writeA.js文件并保存,可以看到浏览器请求了两个文件:
请添加图片描述

在修改文件后,触发webpack增量构建【只构建了writeA.js模块,所以速度极快】,当构建结束后会提交到服务器,服务器通过socket通知给浏览器,浏览器收到通知后下载更新文件,下载后对文件进行热替换。大概就是这么一个过程。

这里补充一个知识点:如何知道构建结束并调用socket通知浏览器的?

请添加图片描述

代码层面也很好理解,webpack-dev-server插件监听hooks.done【这里需要你对webpack的tapable机制有一定了解,类似于发布订阅模式,当编译完成就会触发钩子hooks.done,插件可以监听钩子做自己的逻辑】,触发则调用socket发送消息给客户端,此时会把本次构建生成的hash一起传给客户端。

这里重点关注浏览器收到这个构建hash后的动作,也是热更新过程的重中之重。

客户端socket收到hash消息后,先将hash值保存起来。再收到ok消息后调用reloadApp()

伪代码:

// 路径 webpackd-dev-server/client/reloadApp.js
reloadApp() {
  if(hot) {
      hotEmitter.emit("webpackHotUpdate", hash); // 走这里
  }esle {
      window.location.reload()
  }
}

发出一个通知事件hotEmitter.emit(""webpackHotUpdate),这时候webpack/hot/dev-server.js【这个文件是上面提到被注入到客户端的代码】监听到该事件:

请添加图片描述

他最主要要是执行了module.hot.check()函数,那个这个函数又是哪里来的?
这是webpack/lib/hmr/HotModuleReplacement.runtime.js在构建的过程中给module对象添加hot属性,对应上面webpack-dev-server插件的能力:为打包产物注入HMR代码。

module.hot.check函数调用hmrDownloadManifest利用fetch下载我们常见到的XXX.hot-update.json更新清单

// XXX.hot-update.json
  {
    "c":["index"], // 更新chunk
    "r":[],
    "m":[]
    // 有些是有"h":hash,版本不同略微不一样
  }

然后回调hmrDownloadUpdateHandlers通过插入标签的方式下载并执行**XXX.hot-update.js文件

// XXX.hot-update.js

请添加图片描述

xxx.hot-update.js文件执行了一个函数window.webpackHotUpdatewebpack_study_1,该函数也是webpack注入代码执行的过程中给window添加的函数,他的作用大致就是将更新的chunk、modules信息保存下来先,以便后续处理这些信息。

module.hot.check()函数代码:
请添加图片描述

在下载完更新XXX.hot-update.js完成后,最后就是应用上这些新代码:
请添加图片描述

应用新代码的核心方法:
请添加图片描述

这个函数大致就是替换并运行模块代码,执行热替换函数【module.hot.accept】!

替换的过程涉及到webpack的关于module、chunk、chunkGraph、moduleGraph这样的知识,没有再去深入讲解module如何被替换。建议看之前写过的一篇文章全面的了解webpack的构建原理# webpack原理解析【长文万字】。

可以看到,修改后的writeA.js重新运行一次,然后index.js对writeA.js的hot.accept()被触发。
请添加图片描述

至此整个热更新的过程结束!!!

下面我们再梳理一遍客户端从收到socket消息ok之后的调用流程:

请添加图片描述

这么看好像整个热更新过程也好像没那么容易理解不是吗?

上帝视角看流程

请添加图片描述

写多少字都不如一个好图易理解、易记忆。

最后

知道的越多,不知道的也越多。在阅读源码的过程中,也发现了很多坑点,例如看到了很多$XXXX$这样的命名,不理解为什么要这么做。也看到很多“console.log(url)”这样字符串包裹的代码

请添加图片描述

阅读难度巨增,不知道为啥会设计成这样,也没有思考会不会有更好的解决办法。一些函数名都是动态生成计算等等,都给我在阅读源码时增加了很多难度。还有就是webpack构建进程与静态服务器之间的交互过程目前理解也相对有限。在阅读文章的时候,一定要对各个角色有所区分,不要把那些功能混淆了。

另外:

热更新的moduel.hot.accept()函数来实现视图更新、页面状态保存等热更新效果的逻辑实际开发中是非常复杂的,不像上图中那样简单。但所幸,webpack中的很多loader都帮我们实现了该函数的效果。如style-loader、vue-loader等等,有了这些,我们才能够更加专注于开发业务代码。此时,不由得深深佩服这些造轮子的大佬,自愧不如。
如果本篇文章能够让你了解到HMR的运行机制的话,请麻烦东东你的小手点个赞再走!❤👍 ~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/409628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3.0全新文档快速上手学习内容整理

目录 ## 1.Vue3简介 ## 2.Vue3带来了什么 ### 1.性能的提升 ### 2.源码的升级 ### 4.新的特性 1. Composition API(组合API) 2. 新的内置组件 3. 其他改变 # 一、创建Vue3.0工程 ## 1.使用 vue-cli 创建 ## 2.使用 vite 创建 # 二、常用 Com…

vue3导出表格excel(支持多sheet页),并自定义导出样式

前期准备 npm install file-saver npm install xlsx npm install xlsx-js-style 先说一说这里为什么选择用xlsx-js-style插件设置导出excel的样式。因项目需要,我在网上找了很多关于导出excel自定义样式的文章,用的最多最普遍的插件就是xlsx-style&#…

unipush2.0教程

解释一下名词 透传消息:无论手机app,是否在运行(打开了),还是清了后台(关闭),都可以收到消息 通知消息:只能app打开了,才能收到 1.开通unipush 2.点击上图的unipush2.0下面的配置&am…

【VSCode】调试器debugger详细使用手册

Visual Studio Code 的主要功能之一是其出色的调试支持。VS Code 的内置调试器有助于加速您的编辑、编译和调试循环。 调试器扩展 VS Code 具有对Node.js运行时的内置调试支持,并且可以调试 JavaScript、TypeScript 或任何其他转换为 JavaScript 的语言。 开始调…

【CSS】CSS 特性 ③ ( CSS 优先级 | 权重叠加计算公式 )

文章目录一、权重叠加计算公式1、后代选择器权重计算2、后代选择器权重计算二3、链接伪类选择器权重计算二、代码示例1、标签结构2、后代选择器选择案例 12、后代选择器选择案例 23、后代选择器选择案例 3一、权重叠加计算公式 在使用 多个类型的 基础选择器 进行 组合 时 , 如…

【JavaScript】手撕前端面试题:手写new操作符 | 手写Object.freeze

🖥️ NodeJS专栏:Node.js从入门到精通 🖥️ 博主的前端之路(源创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述) 🖥️ TypeScript知识总结&…

HttpServletRequest 获取参数

1 HttpServletRequest获取参数方法 可以使用HttpServletRequest获取客户端的请求参数,相关方法如下: String getParameter(String name):通过指定名称获取参数值;String[] getParameterValues(String name):通过指定名…

new bing 申请加入候补

最近Chatgpt非常的火爆, 微软也是把新版必应Chatgpt的测试版已经推出。1.下载安装新必应(new bing)需要下载 Edge新版本 Edge dev下载链接: Microsoft Edge 预览体验成员 (microsoftedgeinsider.com)安装插件在设置中找到扩展-> 获取Microsoft Edge扩展搜索获取 …

vue中的nexttick

我们了解nexttick之前,我们先来看一个例子。 我们先要每一次都在点击按钮的时候,都进行字符串的累加操作,并且在该函数中计算该字符串所占的高度offsetHeight,但是我们当进行第一次点击的时候,此时打印的结果不符合,因…

Javascript Object和Map之间的转换

简单的区分Map和Object Map是ES6退出的一个类型,特点:任何值都可作为属性名 Object特点:属性名只能是字符串(一开始我也不信,测试后才发现的) 代码图片创建一个map类型 new Map([[key, value],[key1, value1] ])简单的介绍下面…

vue 获取后端数据

目录 proxy 解决本地请求问题 vite Vue CLI fetch 代码演示 Post请求 ​编辑Get请求 Axios 安装 代码演示 Post请求 Get请求 TS 封装Axios 代码演示 proxy 解决本地请求问题 为什么会出现跨域问题? 浏览器的同源策略 首先给出浏览器“同源策略”的一种经典定义…

this.$router.push跳转页面携带参数

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、params和query使用方式二、实现代码1.index.js代码2.test.vue代码3.testParams代码4.testParams代码5.效果总结前言 this.$router.push进行页面跳转时。携带…

Vue.js安装与创建默认项目(详细步骤)

前言 上一篇博文已经对Node.js的安装与配置进行了详细介绍,详见https://blog.csdn.net/qq_42006801/article/details/124830995 另外:文中项目存放的路径及项目名称可根据自身实际情况进行更改。 一、Vue.js简述 Vue是一套用于构建用户界面的渐进式Ja…

基于React的富文本编辑器——Braft Editor使用

antd 是基于 Ant Design 设计规范实现的 高质量 React 组件库,倾向于只提供符合该规范且带有视觉展现的 UI 组件,也尽量不重复造轮子。 如果要在React项目中使用富文本编辑器,官方推荐使用 react-quill 与 braft-editor。 详细点击这里 这篇…

Vue3.0项目——打造企业级音乐App(二)图片懒加载、v-loading指令的开发和优化

系列文章目录 内容参考链接Vue3.0 项目启动Vue3.0 项目启动(打造企业级音乐App)Vue3.0项目——打造企业级音乐App(一)Tab栏、轮播图、歌单列表、滚动组件Vue3.0项目——打造企业级音乐App(二)图片懒加载、…

「Vue面试题」vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?

文章目录一、是什么二、如何做接口权限路由权限控制菜单权限方案一方案二按钮权限方案一方案二小结参考文章一、是什么 权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源 而前端权限归根结底是请求的发起权,…

Code For Better 谷歌开发者之声——谷歌Web工具包(GWT)

🍎个人主页:亮点菌的博客 🍊个人信条:点亮编程之路,做自己的亮点 文章目录一、GWT简介二、运行模式1、开发模式(以前称为托管模式)2、生产模式(以前称为Web模式)三、组件…

前端开发:JS的事件冒泡和事件捕获详解

前言 在前端开发过程中,关于JS原生的核心内容使用是日常工作中的常态,关于底层和原理的掌握使用,尤其是在性能优化方面甚为重要。作为前端开发的进阶内容,在实际开发过程中事件发生的顺序称为事件流,当触发某个事件的时…

微信小程序使用 npm 包,举例图文详解

使用 npm 包前提条件: 下载安装,配置npm环境变量,不懂得可以上网查教程,本文不再描述 小程序使用 npm 包简述 1、初始化 package.json 2、勾选允许使用 npm(新版微信小程序开发工具忽略这一步) 3、下载…

Auto.js的介绍

Auto.js 是一款无需root权限的javascript自动化软件 auto.js是一款安卓手机应用,和微信一样,安装在手机上使用 Auto.is是一款自动化软件,更根据脚本内容便可自动执行相关的操作,并且手机无需root Auto.js的脚本需要使用JavaScri…