玖叶教程网

前端编程开发入门

前端脚本和-前端脚本语言

转发链接:

本文将针对微后端框架qiankun的源码进行深入解析,在源码讲解之前,我们先来了解一下哪些是微后端。

微后端是一种类似于微服务的构架,它将微服务的理念应用于浏览器端,将要单页面后端应用由单一的单体应用转变为多个大型后端应用聚合为一的应用。各个后端应用还可以独立开发、独立布署。同时,它们也可以在共享组件的同时进行并行开发——这些组件可以通过NPM或则GitTag、GitSubmodule来管理。

qiankun(乾坤)就是一款由蚂蚁金服推出的比较成熟的微后端框架,基于single-spa进行二次开发,用于将Web应用由单一的单体应用转变为多个大型后端应用聚合为一的应用。(见右图)

这么,话不多说,我们的源码解析即将开始。

初始化全局配置-start(opts)

我们从两个基础API-registerMicroApps(apps,lifeCycles?)-注册子应用和start(opts?)-启动主应用开始,因为registerMicroApps函数中设置的反弹函数较多,而且读取了start函数中设置的初始配置项,所以我们从start函数开始解析。

我们从start函数开始解析(见右图):

我们对start函数进行逐行解析:

从里面可以看出,start函数负责初始化一些全局设置,之后启动应用。这种初始化的配置参数有一部份将在registerMicroApps注册子应用的反弹函数中使用,我们继续往下看。

注册子应用-registerMicroApps(apps,lifeCycles?)

registerMicroApps函数的作用是注册子应用,但是在子应用激活时,创建运行沙箱,在不同阶段调用不同的生命周期钩子函数。(见右图)

从里面可以看出,在第70~71行处registerMicroApps函数做了个处理,避免重复注册相同的子应用。

在第74行调用了single-spa的registerApplication方式注册了子应用。

我们直接来看registerApplication方式,registerApplication方式是single-spa中注册子应用的核心函数。该函数有四个参数,分别是

这种参数都是由single-spa直接实现,这儿可以先简单理解为注册子应用(这个我们会在single-spa篇展开说)。在符合activeRule激活规则时将会激活子应用,执行反弹函数,返回一些生命周期钩子函数(见右图)。

注意,这种生命周期钩子函数属于single-spa,由single-spa决定在何时调用,这儿我们从函数名来简单理解。(bootstrap-初始化子应用,mount-挂载子应用,unmount-卸载子应用)

假如你还是认为有点懵,没关系,我们通过一张图来帮助理解。(见右图)

获取子应用资源-import-html-entry

我们从里面剖析可以看出,qiankun的registerMicroApps方式中第一个入参apps-Array>有三个参数name、activeRule、props都是交给single-spa使用,还有entry和render参数还没有用到。

我们这儿须要关注entry(子应用的entry地址)和render(子应用被激活时触发的渲染规则)这两个还没有用到的参数,这两个参数延后到single-spa子应用激活后的反弹函数中执行。

那我们假定此时我们的子应用已激活,我们来瞧瞧这儿做了哪些。(见右图)

从上图可以看出,在子应用激活后,首先在第81~84行处使用了import-html-entry库从entry步入加载子应用,加载完成后将返回一个对象(见右图)

我们来解释一下这几个数组

数组解释template将脚本文件内容注释后的html模板文件assetPublicPath资源地址根路径,可用于加载子应用资源getExternalScripts方式:获取外部引入的脚本文件getExternalStyleSheets方式:获取外部引入的款式表文件execScripts方式:执行该模板文件中所有的JS脚本文件,而且可以指定脚本的作用域-proxy对象

我们先将template模板、getExternalScripts和getExternalStyleSheets函数的执行结果复印下来,疗效如下(见右图):

从上图我们可以看见我们外部引入的三个js脚本文件,这个模板文件没有外部css款式表,对应的款式表字段也为空。

之后我们再来剖析execScripts方式,该方式的作用就是指定一个proxy(默认是window)对象,之后执行该模板文件中所有的JS,并返回JS执行后proxy对象的最后一个属性(见右图1)。在微后端构架中,这个对象通常会包含一些子应用的生命周期钩子函数(见右图2),主应用可以通过在特定阶段调用那些生命周期钩子函数,进行挂载和销毁子应用的操作。

在qiankun的importEntry函数中还传入了配置项getTemplate,这个显然是对html目标文件的二次处理,这儿就不作展开了,有兴趣的可以自行去了解一下。

主应用挂载子应用HTML模板

我们回到qiankun源码部份继续看(见右图)

从上图看出,在第85~87行处,先对单实例进行测量。在单实例模式下,新的子应用挂载行为会在旧的子应用卸载以后才开始。

在第88行中,执行注册子应用时传入的render函数,将HTMLTemplate和loading作为入参,render函数的内容通常是将HTML挂载在指定容器中(见右图)。

在这个阶段,主应用早已将子应用基础的HTML结构挂载在了主应用的某个容器内,接出来还须要执行子应用对应的mount方式(如Vue.$mount)对子应用状态进行挂载。

此时页面还可以依照loading参数开启一个类似加载的疗效,直到子应用全部内容加载完成。

沙箱运行环境-genSandbox

我们回到qiankun源码部份继续看,此时还是子应用激活时的反弹函数部份(见右图)

在第90~98行是qiankun比较核心的部份,也是几个子应用之间状态独立的关键,那就是js的沙箱运行环境。假如关掉了useJsSandbox选项,这么所有子应用的沙箱环境都是window,就很容易对全局状态形成污染。

我们步入到genSandbox内部,瞧瞧qiankun是怎样创建的(JS)沙箱运行环境。(见右图)

从上图可以看出genSandbox内部的沙箱主要是通过是否支持window.Proxy分为ProxySandbox和SnapshotSandbox两种(多实例还有一种LegacySandbox沙箱,这儿我们不作讲解)。

ProxySandbox

我们先来瞧瞧ProxySandbox沙箱是如何进行状态隔离的(见右图)

我们来剖析一下ProxySandbox类的几个属性:

数组解释updateValueMap记录沙箱中更新的值,也就是每位子应用中独立的状态池name沙箱名称proxy代理对象,可以理解为子应用的global/window对象sandboxRunning当前沙箱是否在运行中active激活沙箱,在子应用挂载时启动inactive关掉沙箱,在子应用卸载时启动constructor构造函数,创建沙箱环境

我们如今从window.Proxy的set和get属性来详尽讲解ProxySandbox是怎样实现沙箱运行环境的。(见右图)

注意:子应用沙箱中的proxy对象可以简单理解为子应用的window全局对象(代码如下),子应用对全局属性的操作就是对该proxy对象属性的操作,带着这份理解继续往下看吧。

// 子应用脚本文件的执行过程:
eval(
  // 这里将 proxy 作为 window 参数传入
  // 子应用的全局对象就是该子应用沙箱的 proxy 对象
  (function(window) {
    /* 子应用脚本文件内容 */
  })(proxy)
);

当调用set向子应用proxy/window对象设置属性时,所有的属性设置和更新还会命中updateValueMap,储存在updateValueMap集合中(第38行),进而防止对window对象形成影响(旧版本则是通过diff算法还原window对象状态快照,子应用之间的状态是隔离的,而母子应用之间window对象会有污染)。

当调用get从子应用proxy/window对象取值时,会优先从子应用的沙箱状态池updateValueMap中取值,假如没有命中才从主应用的window对象中取值(第49行)。对于非构造函数的取值将会对this表针绑定到window对象后,再返回函数。

这么一来,ProxySandbox沙箱应用之间的隔离就完成了,所有子应用对proxy/window对象值的存取都遭到了控制。设置值只会作用在沙箱内部的updateValueMap集合上,取值也是优先取子应用独立状态池(updateValueMap)中的值,没有找到的话,再从proxy/window对象中取值。

我们对ProxySandbox沙箱画一张图来加深理解(见右图)

SnapshotSandbox

在不支持window.Proxy属性时,将会使用SnapshotSandbox沙箱,我们来瞧瞧其内部实现(见右图)

我们来剖析一下SnapshotSandbox类的几个属性:

数组解释name沙箱名称proxy代理对象,此处为window对象sandboxRunning当前沙箱是否激活windowSnapshotwindow状态快照modifyPropsMap沙箱运行期间被更改过的window属性constructor构造函数,激活沙箱active激活沙箱,在子应用挂载时启动inactive关掉沙箱,在子应用卸载时启动

SnapshotSandbox的沙箱环境主要是通过激活时记录window状态快照,在关掉时通过快照还原window对象来实现的。(见右图)

我们先看active函数,在沙箱激活时,会先给当前window对象打一个快照,记录沙箱激活前的状态(第38~40行)。打完快照后,函数内部将window状态通过modifyPropsMap记录还原到先前的沙箱运行环境,也就是还原沙箱激活期间(历史记录)更改过的window属性。

在沙箱关掉时,调用inactive函数,在沙箱关掉前通过遍历比较每一个属性,将被改变的window对象属性值(第54行)记录在modifyPropsMap集合中。在记录了modifyPropsMap后,将window对象通过快照windowSnapshot还原到被沙箱激活前的状态(第55行),相当于是将子应用运行期间对window导致的污染全部消除。

SnapshotSandbox沙箱就是借助快照实现了对window对象状态隔离的管理。相比较ProxySandbox而言,在子应用激活期间,SnapshotSandbox将会对window对象导致污染,属于一个对不支持Proxy属性的浏览器的向上兼容方案。

我们对SnapshotSandbox沙箱画一张图来加深理解(见右图)

挂载沙箱-mountSandbox

我们继续回到这张图,genSandbox函数除了返回了一个sandbox沙箱,还返回了一个mount和unmount方式,分别在子应用挂载时和卸载时的时侯调用。

我们先瞧瞧mount函数内部(见右图)

首先,在mount内部先激活了子应用沙箱(第26行),在沙箱启动后开始绑架各种全局窃听(第27行),我们这儿重点瞧瞧patchAtMounting内部是如何实现的。(见右图)

patchAtMounting内部调用了下边四个函数:

前面四个函数实现了对window指定对象的统一绑架,我们可以挑一些解析瞧瞧其内部实现。

计时器绑架-patchTimer

我们先来瞧瞧patchTimer对计时器的绑架(见右图)

从上图可以看出,patchTimer内部将setInterval进行重载,将每位启用的定时器的intervalId都搜集上去(第23~24行),便于在子应用卸载时调用free函数将计时器全部消除(见右图)。

我们来瞧瞧在子应用加载时的setInterval函数验证即可(见右图)

从上图可以看出,在步入子应用时,setInterval早已被替换成了绑架后的函数,避免全局计时器窃取污染。

动态添加款式表和脚本文件绑架-patchDynamicAppend

patchWindowListener和patchHistoryListener的实现都与patchTimer实现类似,这儿就不作复述了。

我们须要重点对patchDynamicAppend函数进行解析,这个函数的作用是绑架对head元素的操作(见右图)

从上图可以看出,patchDynamicAppend主要是对动态添加的style款式表和script标签做了处理。

我们先瞧瞧对style款式表的处理(见右图)

从上图可以看出,主要的处理逻辑在第68~74行,假如当前子应用处于激活状态(判定子应用的激活状态主要是由于:当主应用切换路由时可能会手动添加动态款式表,此时须要防止主应用的款式表被添加到子应用head节点中造成出错),这么动态style款式表都会被添加到子应用容器内(见右图),在子应用卸载时款式表也可以和子应用一起被卸载,因而防止款式污染。同时,动态款式表也会储存在dynamicStyleSheetElements链表中,在前面都会提及其益处。

我们再来看看对script脚本文件的处理(见右图)

对动态script脚本文件的处理较为复杂一些,我们也来解析一波:

在第83~101行处对外部引入的script脚本文件使用fetch获取,之后使用execScripts指定proxy对象(作为window对象)后执行脚本文件内容,同时也触发了load和error两个风波。

在第103~106行处将注释后的脚本文件内容以注释的方式添加到子应用容器内。

在第109~113行是对内嵌脚本文件的执行过程,就不作复述了。

我们可以看出,对动态添加的脚本进行绑架的主要目的就是为了将动态脚本运行时的window对象替换成proxy代理对象,使子应用动态添加的脚本文件的运行上下文也替换成子应用自身。

HTMLHeadElement.prototype.removeChild的逻辑就是多加了个子应用容器判定,其他无异,就不展开说了。

最后我们来瞧瞧free函数(见右图)

这个free函数与其他的patches(绑架函数)实现不太一样,这儿缓存了一份cssRules,在重新挂载的时侯会执行rebuild函数将其还原。这是由于式样元素DOM从文档中删掉后,浏览器会手动消除款式元素表。假如不那么做的话,在重新挂载时会出现存在style标签,而且没有渲染款式的问题。

卸载沙箱-unmountSandbox

我们再回到mount函数本身(见右图)

从上图可以看出,在patchAtMounting函数中绑架了各种全局窃听,并返回了解除绑架的free函数。在卸载应用时调用free函数解除这种全局窃听的绑架行为(见右图)

从上图可以看见sideEffectsRebuilders在free后被返回,在mount的时侯又将被调用rebuild重建动态款式表。这块环环相扣,是稍稍有点绕,没太看明白的朋友可以翻起来再看一遍。

到这儿,qiankun的最核心部份-沙箱机制,我们就早已解析完毕了,接出来我们继续分析别的部份。

在这儿我们画一张图,对沙箱的创建过程进行一个总梳理(见右图)

注册内部生命周期函数

在创建好了沙箱环境后,在第100~106行注册了一些内部生命周期函数(见右图)

在上图中,第106行的mergeWith方式的作用是将外置的生命周期函数与传入的lifeCycles生命周期函数。

这儿的lifeCycles生命周期函数指的是全子应用共享的生命周期函数,可用于执行多个子应用间相同的逻辑操作,比如加载疗效之类的。(见右图)

不仅外部传入的生命周期函数外,我们还须要关注qiankun外置的生命周期函数做了些哪些(见右图)

我们对上图的代码进行逐一解析:

通过前面的剖析我们可以得出一个推论,我们可以在子应用中获取该环境变量,将其设置为__webpack_public_path__的值,继而使子应用在主应用中运行时,可以匹配正确的资源路径。(见右图)

触发beforeLoad生命周期钩子函数

在注册完了生命周期函数后,立刻触发了beforeLoad生命周期钩子函数(见右图)

从上图可以看出,在第108行中,触发了beforeLoad生命周期钩子函数。

随即,在第110行执行了import-html-entry的execScripts方式。指定了脚本文件的运行沙箱(jsSandbox),执行完子应用的脚本文件后,返回了一个对象,对象包含了子应用的生命周期钩子函数(见右图)。

在第112~121行对子应用的生命周期钩子函数做了个测量,假若在子应用的导入对象中没有发觉生命周期钩子函数,会在沙箱对象中继续查找生命周期钩子函数。假如最后没有找到生命周期钩子函数则会抛出一个错误,所以我们的子应用一定要有bootstrap,mount,unmount这三个生命周期钩子函数能够被qiankun正确嵌入到主应用中。

这儿我们画一张图,对子应用挂载前的初始化过程做一个总梳理(见右图)

步入到mount挂载流程

在一些初始化配置(如子应用资源、运行沙箱环境、生命周期钩子函数等等)打算就绪后,qiankun内部将其组装在一起,返回了三个函数作为single-spa内部的生命周期函数(见右图)

single-spa内部的逻辑我们前面再展开说,这儿我们可以简单理解为single-spa内部的三个生命周期钩子函数:

我们可以看出,在bootstrap阶段调用了子应用曝露的bootstrap生命周期函数。

我们这儿对mount阶段进行展开,瞧瞧在子应用mount阶段执行了什么函数(见右图)

我们进行逐行解析:

我们在里面很详尽的分析了整个子应用的mount挂载流程,假如你还没有搞清的话,没关系,我们再画一个流程图来帮助理解。(见右图)

步入到unmount卸载流程

我们刚刚梳理了子应用的mount挂载流程,我们如今就步入到子应用的unmount卸载流程。在子应用激活阶段,activeRule未命中时将会触发unmount卸载行为,具体的行为如下(见右图)

从上图我们可以看出,unmount卸载流程要比mount简单好多,我们直接来梳理一下:

我们对unmount卸载流程也画一张图,帮助你们理解(见右图)。

总结

到这儿,我们对qiankun框架的总流程梳理就差不多了。这儿应当做个总结,你们看了那么多文字,恐怕你们也看累了,最后用一张图对qiankun的总流程进行总结吧。

展望

传统的云控制台应用,几乎就会面临业务快速发展以后,单体应用进化成巨石应用的问题。我们要怎样维护一个巨无霸中台应用?

前面这个问题引出了微后端构架理念,所以微后端的概念也越来越火,我们团队近来也在尝试变革微后端构架。

工欲善其事必先利其器,所以本文针对qiankun的源码进行剖析,在分享知识的同时也是帮助自己理解。

这是我们团队对微后端构架的最佳实践(见右图),倘若有需求的话,可以在评论区留言,我们会考虑出一篇《微后端框架qiankun最佳实践》来帮助你们搭建一套微后端构架。

最后一件事

这篇文章我花了大概半个月的时间来进行排版、梳理、画图,坚持出来一路写完确实很不容易。

倘若您早已听到这儿了,希望您还是点个赞再走吧~

倘若本文对您有帮助的话,请点个赞和收藏吧!

您的点赞是对作者的最大鼓励,也可以让更多人听到本篇文章!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言