webpack和vue热更新

article/2025/8/27 6:18:47

目录

webpack一些概念介绍

webpack热更新流程

1. 启动阶段 ①->②->A->B

2. 更新阶段 ①->②->③->④

vue的组件热更新模块

总结


提到热更新,首先我们要有一个概念:Vue有热更新模块,而webpack也有它的HRM模块(HotModuleReplacement)。Vue热更新是基于webpack的热更新之下的粒度更小的更新,它是依托于webpack-dev-middleware对文件的监听的,是整个webpack热更新的一部分。

所以想要理解Vue的热更新,必须先要了解webpack HMR的一个流程。

webpack一些概念介绍

先介绍一些比较重要的概念:

概念

作用

备注

Webpack Compiler将 JS 编译成 Bundle
Bundle Server提供文件在浏览器端以服务器的方式的访问(正常是通过文件目录来访问)比如说编译好的 bundle.js,其实在浏览器里面正常访问是以文件目录的形式来访问的,然后使用 BundleServer 可以让我们以类似于服务器的方式来访问,比如说 localhost:8080
HMR Server将热更新的文件输出给 HMR Runtime
HMR Runtime在项目打包阶段,会被注入到浏览器端的bundle.js里面开发阶段,打包阶段,会注入到浏览器中的 bundle.js里面,这样的话浏览器端的 bundle.js 会和服务器建立一个链接,通常会是 websocket。这样就能动态更新了。
bundle.js构建后最终输出的文件

webpack热更新流程

webpack热更新流程图解:

为什么 webpack 没有将文件直接打包到 output.path 目录下呢?文件又去了哪儿?

webpack 将 bundle.js 文件打包到了内存中,不生成文件的原因就在于访问内存中的代码比访问文件系统中的文件更快,而且也减少了代码写入文件的开销,这一切都归功于memory-fs,memory-fs 是 webpack-dev-middleware 的一个依赖库,webpack-dev-middleware 将 webpack 原本的 outputFileSystem 替换成了MemoryFileSystem 实例,这样代码就将输出到内存中。

从图中可以看到,从模块文件到浏览器页面显示的流程主要分为了两种情况,分别是第一次启动项目,和项目启动完之后的热更新

1. 启动阶段 ①->②->A->B

  • 代码文件通过 webpack Compile 进行打包
  • 将打包好的文件传输给 Bundle Server(使用了内存文件系统,无实际的文件生成)
  • 然后Bundle Server会让文件以服务器的方式让浏览器可以访问到
  • 代码展示到浏览器

2. 更新阶段 ①->②->③->④

  • 变更后的代码同样会通过 webpack Compile 进行打包编译(依赖了webpack-dev-middleware来监听变化和获取文件)
  • 编译好之后会发送给 HMR Server
  • HMR Server即可知道哪些资源、js模块、文件发生了改变
  • 然后通过 websorket 传输.hot-update.json的形式,通知 HMR Runtime
  • HMR Runtime 在接收到文件变化后,就会更新代码
  • 最终代码更新,且无需刷新浏览器


在其中最需要我们关心的,应该是 HMR Runtime 在接收到文件变化后,就会更新代码 这一步,在获取到了变更的文件后,到底是通过怎么样的方式对页面进行刷新的呢?

在这一步里,HMR Runtime通过JSONP获取到了更新的代码,并触发所有模块中的module.hot.accept()方法,换言之,只要模块中具有这个方法,就可以知道有新文件到来的时机了。

而在查看开启了热更新选项的编译后的vue文件,会发现里面被插入了这么一段代码:

/* hot reload */
Component.options.__file = "app/web/page/about/about.vue"
if (true) {(function () {var hotAPI = __webpack_require__(3)hotAPI.install(__webpack_require__(0), false)if (!hotAPI.compatible) returnmodule.hot.accept()if (!module.hot.data) {hotAPI.createRecord("data-v-aafed0d8", Component.options)} else {hotAPI.reload("data-v-aafed0d8", Component.options)
' + '  }module.hot.dispose(function (data) {disposed = true})
})()}

其中的 hotAPI其实就是vue的组件热更新模块--vue-hot-reload-api

vue的组件热更新模块

这里先来重点介绍一些vue-hot-reload-api的源码部分

install 部分:

let Vue // late bind
let version
const map = Object.create(null)
if (typeof window !== 'undefined') {window.__VUE_HOT_MAP__ = map
}
let installed = false
let isBrowserify = false
let initHookName = 'beforeCreate'exports.install = (vue, browserify) => {if (installed) returninstalled = trueVue = vue.__esModule ? vue.default : vueversion = Vue.version.split('.').map(Number)isBrowserify = browserify// compat with < 2.0.0-alpha.7if (Vue.config._lifecycleHooks.indexOf('init') > -1) {initHookName = 'init'}// 只有Vue在2.0以上的版本才支持这个库exports.compatible = version[0] >= 2if (!exports.compatible) {console.warn('[HMR] You are using a version of vue-hot-reload-api that is ' +'only compatible with Vue.js core ^2.0.0.')return}
}

这一段是安装部分,主要用来判断有无重复安装、即将使用的vue的版本是否过低等。

 createdRecord创造模块纪录部分:

exports.createRecord = (id, options) => {if(map[id]) returnlet Ctor = nullif (typeof options === 'function') {Ctor = optionsoptions = Ctor.options}makeOptionsHot(id, options)map[id] = {Ctor,// 构造函数options, // vue optionsinstances: [] // 实例对象数组}
}

在这个函数中,将传入的唯一id对应其vue option, 储存到map中,并采用makeOptionsHot这个函数将这个模块对应的option注入hot,即使其hot化(即生命周期中被注入了某些代码)。

 makeOptionsHot函数和injectHook函数:

function makeOptionsHot(id, options) {if (options.functional) {// 判断组件是否函数化,若函数化,则无需injectHookconst render = options.renderoptions.render = (h, ctx) => {const instances = map[id].instancesif (ctx && instances.indexOf(ctx.parent) < 0) {instances.push(ctx.parent)}return render(h, ctx) // 给函数化组件的render加入了注入实例到map[id].instance的功能}} else {injectHook(options, initHookName, function() {const record = map[id]if (!record.Ctor) {record.Ctor = this.constructor}record.instances.push(this)})injectHook(options, 'beforeDestroy', function() {const instances = map[id].instancesinstances.splice(instances.indexOf(this), 1)})}
}// 往生命周期里注入某个方法
function injectHook(options, name, hook) {const existing = options[name];options[name] = existing? Array.isArray(existing)? existing.concat(hook): [existing, hook]: [hook];
}

先来看injectHook函数,这个函数接受三个参数,options,name,hook,分别对应vue的option,生命周期的名字,以及想要混入该生命周期函数中的操作hook。

在makeOptionsHot中,这个injectHook函数被调用了两次,将传入的id对应的map中的options的beforeCreate和beforeDestory方法中混入了新增/清除map中options的模块实例的功能。这样子就能在每个组件创建之前,拿到他们的实例对象,以便后续进行操作。

而当vue组件为函数化组件时,无需injectHook,而是给函数化组件的render加入了注入实例到map[id].instance功能。

接下来就到了比较重要的两个函数了,首先是修改render或template时触发的rerender函数。

精简版的rerender,删去了处理cache和funtional部分,只留了核心代码:

exports.rerender = (id, options) => {const record = map[id];if (!options) {// 如果没传第二个参数 就把所有实例调用 $forceUpdaterecord.instances.slice().forEach(instance => {instance.$forceUpdate();});return;}record.instances.slice().forEach(instance => {// 将实例上的 $options上的render直接替换为新传入的render函数instance.$options.render = options.render;// 执行 $forceUpdate更新视图instance.$forceUpdate();});
};

在只更新template或者是render的情况下,调用的是rerender函数。

rerender函数接受id和新options,如果options不传,则直接刷新id对应的所有实例;如果传了的话,就将该options赋予所有id对应的实例,再刷新。

接下来是直接重新渲染组件的reload函数:

exports.reload = (id, options) => {const record = map[id]if (options) {if (typeof options === 'function') {options = options.options}makeOptionsHot(id, options)// 因为这个options是新的,所以需要再次让它hot化if (record.Ctor) {const newCtor = record.Ctor.super.extend(options)// prevent record.options._Ctor from being overwritten accidentallynewCtor.options._Ctor = record.options._Ctorrecord.Ctor.options = newCtor.optionsrecord.Ctor.cid = newCtor.cid // 这一步很重要,改变了构造函数的cidrecord.Ctor.prototype = newCtor.prototype}record.instances.slice().forEach(instance => {if (instance.$vnode && instance.$vnode.context) {instance.$vnode.context.$forceUpdate()} else {console.warn('Root or manually mounted instance modified. Full reload required.')}})
}

在这一步中,首先将传入的options调用makeOptionsHot函数hot化,在生命周期注入代码。之后用原有的构造函数的父函数继承options(这里不是很懂为啥不直接用Vue.extend(options)),然后再注入诸如options,cid,prototype之类的东西。

最后对每一个实例进行更新。在Vue更新实例的时候,会去检查实例生成的组件的构造方法的cid是否有改变,有改变的话就不使用缓存,所以说cid赋值的一步很重要。

至此,vue-hot-reload-api中最重要的两个方法已经介绍完毕。

然后回到我们刚刚的浏览器的HMR runtime获取到最新代码的那步,刚刚提到所有的Vue文件在编译完后会多出的那段代码,其实是Vue loader做的。

vue-loader 源码:

  if (needsHotReload) {code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)}
const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))const genTemplateHotReloadCode = (id, request) => {return `module.hot.accept(${request}, function () {api.rerender('${id}', {render: render,staticRenderFns: staticRenderFns})})`.trim()
}exports.genHotReloadCode = (id, functional, templateRequest) => {return `
/* hot reload */
if (module.hot) {var api = require(${hotReloadAPIPath})api.install(require('vue'))if (api.compatible) {module.hot.accept()if (!api.isRecorded('${id}')) {api.createRecord('${id}', component.options)} else {api.${functional ? 'rerender' : 'reload'}('${id}', component.options)}${templateRequest ? genTemplateHotReloadCode(id, templateRequest) : ''}}
}`.trim()
}

可以看到,当热更新触发时,浏览器中的 HRM runtime会去先install,然后根据情况的不同进行createRecord,rerender或reload等操作。

至此,整个热更新的流程到vue 热更新的api实现已经讲完了。

总结

来总结一下:

webpack的热更新原理:

webpack-dev-server:
webpack-dev-server 主要包含了三个部分:
1.webpack: 负责编译代码
2.webpack-dev-middleware: 主要负责构建内存文件系统,把webpack的 OutputFileSystem 替换成 InMemoryFileSystem。同时作为Express的中间件拦截请求,从内存文件系统中把结果拿出来。
3.express:负责搭建请求路由服务。

工作流程:
1.启动dev-server,webpack开始构建,在编译期间会向 entry 文件注入热更新代码;
2.Client 首次打开后,Server 和 Client 基于Socket建立通讯渠道;
3.修改文件,Server 端监听文件发送变动,webpack开始编译,直到编译完成会触发"Done"事件;
4.Server通过socket 发送消息告知 Client;
5.Client根据Server的消息(hash值和state状态),通过ajax请求获取 Server 的manifest描述文件;
6.Client对比当前 modules tree ,再次发请求到 Server 端获取新的JS模块;
7.Client获取到新的JS模块后,会更新 modules tree并替换掉现有的模块;
8.最后调用 module.hot.accept() 完成热更新;

其中vue 的模块热更新接在了最后的8那里,module.hot.accept()是调用了vue-hot-reload-api的接口,而这个module.hot.accept的函数内容是在vue-loader的时候注入进去的。

补上一张webpack更新的流程图:


http://chatgpt.dhexx.cn/article/liXXOIVg.shtml

相关文章

JAVA实现代码热更新

JAVA实现代码热更新 引言类加载器实现热更新思路多种多样的加载来源SPI服务发现机制 完整代码类加载器共享空间机制Tomcat如何实现JSP的热更新Spring反向访问用户程序类问题补充细节推荐资源 引言 本文将带领大家利用Java的类加载器加SPI服务发现机制实现一个简易的代码热更新…

cordova打包app热更新问题

定义&#xff1a; 基于 cordova 框架能将web应用 (js, html, css, 图片等) 打包成 App。当 App 在终端上安装后&#xff0c;不需要重新下载app&#xff0c;实现内壳更新。 原理&#xff1a;1.在项目根目录的config.xml文件中添加指向服务器的地址 2.在www目录中添加chcp.json配…

【热更新】游戏热更新方案

游戏热更新方案 热更新演化热更新方案【1】 进程切换1.1 利用fork、exec切换1.2 利用网关切换1.3 微服务- 进程切换注意要点 【2】 动态库替换【3】 脚本语言热更新热更新探究最简单的实现热更的方法最简单的实现热更的方法的局限性热更新全局替换模块方法的局限性 工程实现1. …

Addressable热更新

文章目录 前提配置代码实现 前提配置 &#xff08;1&#xff09;勾选AddressableAssetSettings设置的Disable Catalog Update On Startup选项 &#xff08;2&#xff09;相应的热更戏资源分组配置&#xff08;注&#xff1a;此文采用的是动态资源更新&#xff09; Can Change …

Nacos配置热更新的4种方式、读取项目配置文件的多种方式,@value,@RefreshScope,@NacosConfigurationProperties

nacos实现配置文件的热更新&#xff0c;服务不用重启即可读取到nacos配置中心修改发布后的最新值&#xff0c;spring&#xff0c;springboot项目读取本地配置文件的各种方式&#xff1b;文章中介绍了一下注解的使用&#xff1a;NacosConfigurationProperties&#xff0c;NacosP…

Unity 热更新技术 | (一) 热更新的基本概念原理及主流热更新方案介绍

&#x1f3ac; 博客主页&#xff1a;https://xiaoy.blog.csdn.net &#x1f3a5; 本文由 呆呆敲代码的小Y 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;Unity系统学习专栏 &#x1f332; 游戏制作专栏推荐&#xff1a;游戏制作 &…

JAVA热更新

引言 知识储备先看这篇文章&#xff1a;JAVA Instrument 在这个案例中我们会利用Instrument机制实现一个简单的热更新案例。 总体来说&#xff0c;步骤如下&#xff1a; 创建一个带premain方法的jar包。这个方法定时检测某个文件然后进行热更新。命令行启动业务类时使用参数…

热更新 深度解析

APP热更新方案 为什么要做热更新 当一个App发布之后&#xff0c;突然发现了一个严重bug需要进行紧急修复&#xff0c;这时候公司各方就会忙得焦头烂额&#xff1a;重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。 重点是还会有原来的版本遗留…

webpack热更新

什么是模热更新&#xff1f;有什么优点 模块热更新是webpack的一个功能&#xff0c;它可以使得代码修改之后&#xff0c;不用刷新浏览器就可以更新。 在应用过程中替换添加删出模块&#xff0c;无需重新加载整个页面&#xff0c;是高级版的自动刷新浏览器。 优点&#xff1a…

electron 热更新

1. electron自带的整体更新方式 &#xff08;全量更新&#xff09; 这种方式为electron官方的升级更新方式&#xff0c;主要是通过主进程中的autoUpdater模块进行检测升级更新的&#xff0c;此方式也是大家常见的大多数electron应用程序的更新方式。 检测到新版本后从服务器拉…

uniApp实现热更新

热更新 热更新是开发中常见且常用的一种软件版本控制的方式&#xff0c;在uniapp进行使用热更新将软件实现更新操作 思路: 服务器中存储着最新版本号&#xff0c;前端进行查询可以在首次进入应用时进行请求版本号进行一个匹对如果版本号一致则不提示&#xff0c;反之则提示进行…

Android热更新详解

一 前言介绍 正好最近又看到热更新&#xff0c;对以前Android 热修复核心原理&#xff1a;ClassLoader类加载机制做了点补充。 从16年开始开始&#xff0c;热修复技术开始在安卓界流行&#xff0c;它以classloader类加载机制为核心&#xff0c;可以不发布新版本就修复线上 bu…

热更新原理

对于热更新的问题就是了解两个点的问题&#xff1a; 如何加载补丁包&#xff0c;也就是如何加载dex 文件的过程&#xff08;dex是补丁包&#xff0c;更改的文件都在补丁包中&#xff09;修复后的类如何替换掉旧的类 通过这篇文章给大家介绍下我理解的热更新的逻辑&#xff0c…

Cocos Creator 3.x 热更新

前言&#xff1a;游戏做热更新 是基本需求&#xff1b; 好在 cocos-creator 已经为我们做好了方案&#xff0c;相对于 U3D 的热更新方案来说&#xff0c;使用起来很简便&#xff01;&#xff0c;不用关注很多细节 本文使用的是 cocos-creator 3.5.2 版本 官方文档 &#xff1…

热更新原理及实践注意

首先要说明几个概念&#xff0c;不要混用&#xff0c;热部署&#xff0c;热加载&#xff1b; 热部署&#xff1a;就是已经运行了项目,更改之后,不需要重新tomcat,但是会清空内存,重新打包,重新解压war包运行&#xff0c;可能好处是一个tomcat多个项目,不必因为tomcat停止而停止…

热更新你都知道哪些?

热更新系列目录 热更新你都知道哪些&#xff1f;热更新Sophix的爬坑之路腾讯热更新Tinker的故事阿里热更新Sophix的故事 Android热更新 前言1. 什么是热更新&#xff1f;2. 主流热更新方案3. 腾讯系热更新4. 阿里系热更新总结 博客创建时间&#xff1a;2020.05.16 博客更新时间…

热更新技术简易原理及技术推荐

为了照顾萌新童鞋&#xff0c;最开始还是对热更新的概念做一个通俗易懂的介绍。 热更新用通俗的讲就是软件不通过应用商店的软件版本更新审核&#xff0c;直接通过应用自行下载的软件数据更新的行为。在用户下载安装App之后&#xff0c;打开App时遇到的即时更新&#xff0c;是…

热更新及其原理

热更新&#xff1a;是app常用的更新方式&#xff0c;只需下载安装更新部分的代码 工作原理&#xff1a;动态下开发代码&#xff0c;使开发者在不发布新版本的情况下修复bug和发布功能&#xff0c;绕开苹果审核机制&#xff0c;避免长时间的审核以及多次被拒绝造成的成本。 优…

HTML/CSS实现小米官网搜索框效果

效果图&#xff1a; 需求分析&#xff1a; 1、输入框焦点事件 onfocus:成为焦点, 点击输入框的时候&#xff0c;出现闪烁光标&#xff0c;此时可以输入内容。 onblur :失去焦点, 点击页面空白区域&#xff0c;光标消失。此时不可以输入内容。 2、获取元素 3、注册事件 2.1…

html中的搜索代码,Web自动化(3):网页自动搜索功能

unsplash.jpg 写在前面 如果我们需要在期刊中搜索我们想要找的文章,那么我们如何才能达到这个目的。我们首先看一下,手动和自动对比图: 网页搜索.png 其实内容全部一样,我们只是用自动化程序,来代替我们手动操作。 1. 创建webdriver驱动对象,驱动打开网页 # 导入包 from …