微服务


1.微服务概念

微服务:微服务架构(通常简称为微服务)是指开发应用所用的一种架构形式。通过微服务,可将大型应用分解成多个独立的组件,其中每个组件都有各自的责任领域。在处理一个用户请求时,基于微服务的应用可能会调用许多内部微服务来共同生成其响应。

通常跟微服务相对的是单体应用,即将所有功能都打包成在一个独立单元的应用程序。从单体应用到微服务并不是一蹴而就的,这是一个逐渐演变的过程

知乎上一个小案例加深印象:https://www.zhiimportmaphu.com/question/65502802

个人的理解是将拆分的思想贯彻到底(模块也是拆!数据库也是拆!),分工明确,将每个功能细致化分配任务。再加上兼容一下老应用的逻辑,和新框架开发的代码的一套架构理念

但是缺点就是:在微服务架构中,一个服务故障可能会产生雪崩效用,导致整个系统故障

  • 微服务架构整个应用分散成多个服务,定位故障点非常困难。
  • 稳定性下降。服务数量变多导致其中一个服务出现故障的概率增大,并且一个服务故障可能导致整个系统挂掉。事实上,在大访问量的生产场景下,故障总是会出现的。
  • 服务数量非常多,部署、管理的工作量很大。
  • 开发方面:如何保证各个服务在持续开发的情况下仍然保持协同合作。
  • 测试方面:服务拆分后,几乎所有功能都会涉及多个服务。原本单个程序的测试变为服务间调用的测试。测试变得更加复杂。

所以使用微服务的架构要有要有强大的监控体系

2.沙箱(sand box)

传统意义上:Sandbox(沙箱)是指一种技术,在这种技术中,软件运行在操作系统受限制的环境中。由于该软件在受限制的环境中运行,即使一个闯入该软件的入侵者也不能无限制访问操作系统提供设施;获得该软件控制权的黑客造成的损失也是有限的

然而,在计算机安全中,沙箱(Sandbox)是一种用于隔离正在运行程序的安全机制,通常用于执行未经测试或不受信任的程序或代码,它会为待执行的程序创建一个独立的执行环境,内部程序的执行不会影响到外部程序的运行

抽象化:对于操作系统来说,浏览器就是一个沙箱,对于浏览器来说,每个页面就是一个独立的沙箱

实际运用:

  • Vue 模板表达式的计算是运行在一个沙盒之中的,在模板字符串中的表达式只能获取部分全局对象,这一点官方文档有提到,详情可参阅源码
  • CodeSanbox ,在线编辑器
  • 许多应用程序带的插件,有很多的限制条件,这些应用程序在运行插件时需要遵循宿主程序制定的运行规则,插件的运行环境和规则就是一个沙箱
  • ….

从知乎这篇文章可以看到沙箱的基本介绍https://zhuanlan.zhihu.com/p/428039764

里面的《简陋沙箱》、《非常简陋的沙箱(With)》、《没那么简陋的沙箱(With+Proxy)》循序渐进,都可以帮助读者加深印象

里面主要通过new Functionevalwith修改上下文作用域

然后就是天然沙箱:iframe、和 ifame+with+proxy的结合,但是现在简单的沙箱都有可能被使用者使用某种代码漏洞,进行 “沙箱逃逸”,从而获取沙箱之外环境的内容进行读取、修改等,这时候就要去找开源库了

JS隔离

Js 沙箱做的事情可以用两句话概括:

  1. 为每一个子应用创建一个专属的 “window 对象”(虚假的);
  2. 执行子应用时,将新建的 “window 对象” 作为子应用脚本的全局变量,子应用对全局变量的读写操作都作用到这个 “window 对象”中。

沙箱类型:

  1. LegacySandbox(依赖 Proxy),父子状态隔离,但是window不隔离
  2. ProxySandbox (依赖 Proxy)稳定后会取代 LegacySandbox
  3. SnapshotSandbox(不依赖Proxy)

参考字节跳动的技术文章:https://juejin.cn/post/7099339595233361934#heading-24

3.微前端

微前端(Micro-Frontends)同样是一套体系架构,是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以无关技术栈地独立运行、独立开发、独立部署。

在如今越来越注重拆分和细化的时代,微前端的到来并没有在意料之外,而微前端的意义就:

  • 将这些庞大应用进行拆分,并随之解耦,每个部分可以单独进行维护和部署,提升效率。
  • 整合历史系统,将老架构框架项目兼容性放在新的架构体系中

当然,没有免费的午餐——一切都是有代价的,一些微前端实现可能会导致依赖项重复,但是我们相信这些风险是可以控制的,而且微前端的好处往往超过成本。 ——–Cam Jackson

微前端解决的问题:

image-20220515182302822

实际上,一个典型的基于vue-router的Vue应用与这种架构存在着很大的相似性

微前端方案的核心是“主从”思想,即包括一个基座MainApp)应用和若干个微(MicroApp)应用,基座应用大多数是一个前端SPA项目,主要负责应用注册,路由映射,消息下发等,而微应用是独立前端项目,这些项目不限于采用React,Vue,Angular或者JQuery开发,每个微应用注册到基座应用中,由基座进行管理,但是如果脱离基座也是可以单独访问

方案 描述
NPM式 子工程以NPM包的形式发布源码;打包构建发布还是由基座工程管理,打包时集成。
iframe式 子工程可以使用不同技术栈;子工程之间完全独立,无任何依赖;基座工程和子工程需要建立通信机制;无单页应用体验;路由地址管理困难。
通用中心路由基座式 子工程可以使用不同技术栈;子工程之间完全独立,无任何依赖;统一由基座工程进行管理,按照DOM节点的注册、挂载、卸载来完成。比如qiankun之类的,qiankun甚至能加载jQuery编写的页面
特定中心路由基座式 子业务线之间使用相同技术栈;基座工程和子工程可以单独开发单独部署;子工程有能力复用基座工程的公共基建。比如React-router、Vue-router

(图片源自美团技术团队的文章,而美团用的是基于React的中心路由基座式微前端image-20220515183129271

但是微前端仍需解决:

  1. 路由切换的分发问题。
  2. 主微应用的隔离问题。
  3. 通信问题。

路由转发:

  • 通过http请求获取所需的微应用html、css、js,然后可以采用eval的形式运行代码,而分发过程使用router对url进行监听,然后采用一些路由方法(hash方法或者pushState方法等)发送路由信息给微应用,让微应用通过它自己的路由进行接收

  • Single-Spa虽然实现了自己的一套路由来监听子工程的切换,但是还需要特定的模块管理系统,比如 systemjs来辅助,这导致了改造成本和添加一些额外的库,所以美团直接用的是 React-Router (react-router-dom)

    image-20220515190230968

隔离问题:

  • 对于css污染,可以采用CSS Module或者webpack的postcss插件,在打包时添加特定的前缀

  • 对于Js代码,我们害怕一些全局对象在不同微应用的不同表现,我们可以采用沙箱隔离机制,消除冲突和影响

通信问题:

  • 我们可以用发布订阅通信机制,事件由实践中心统一分发

子应用打包的方式

微前端架构模式下,子应用打包的方式,基本分为两种:

img

两者的优缺点也很明显:

img

很显然,要实现真正的技术栈无关跟独立部署两个核心目标,大部分场景下我们需要使用运行时加载子应用这种方案。

JS Entry vs HTML Entry

  1. 如果是 JS Entry 方案,主框架需要在子应用加载之前构建好相应的容器节点(比如这里的 “#root” 节点),不然子应用加载时会因为找不到 container 报错

    <!-- 子应用 index.html -->
    <script src="//unpkg/antd.min.js"></script>
    <body>
      <main id="root"></main>
    </body>
    // 子应用入口
    ReactDOM.render(<App/>, document.getElementById('root'))
  2. JS entry打包问题(可以看 why SystemJS 部分)

  3. HTML entry无法抽取公共依赖(很麻烦)

其他:

img

(webpack5有一个联邦模块功能(mf),对于微前端的公共依赖加载是比较好的解决方案。)

一些微前端的框架

Mooa:基于Angular的微前端服务框架

Single-Spa:最早的微前端框架,兼容多种前端技术栈。

Qiankun:基于Single-Spa,阿里系开源微前端框架。

Icestark:阿里飞冰微前端框架,兼容多种前端技术栈。

Ara Framework:由服务端渲染延伸出的微前端框架。

micro-app:京东微前端框架,借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染

你可能就不需要微前端

存在以下场景时,你可能就不需要微前端:

  1. 你/你的团队 具备系统内所有架构组件的话语权
    简单来说就是,系统里的所有组件都是由一个小的团队开发的。
  2. 你/你的团队 有足够动力去治理、改造这个系统中的所有组件
    直接改造存量系统的收益大于新老系统混杂带来的问题。
  3. 系统及组织架构上,各部件之间本身就是强耦合、自洽、不可分离的
    系统本身就是一个最小单元的「架构量子」,拆分的成本高于治理的成本。
  4. 极高的产品体验要求,对任何产品交互上的不一致零容忍
    不允许交互上不一致的情况出现,这基本上从产品上否决了渐进式升级的技术策略

满足以下几点,你才确实可能需要微前端

  1. 系统本身是需要集成和被集成的 一般有两种情况:

    1. 旧的系统不能下,新的需求还在来。
      没有一家商业公司会同意工程师以单纯的技术升级的理由,直接下线一个有着一定用户的存量系统的。而你大概又不能简单通过 iframe 这种「靠谱的」手段完成新功能的接入,因为产品说需要「弹个框弹到中间」
    2. 你的系统需要有一套支持动态插拔的机制。
      这个机制可以是一套精心设计的插件体系,但一旦出现接入应用或被接入应用年代够久远、改造成本过高的场景,可能后面还是会过渡到各种微前端的玩法。
  2. 系统中的部件具备足够清晰的服务边界
    通过微前端手段划分服务边界,将复杂度隔离在不同的系统单元中,从而避免因熵增速度不一致带来的代码腐化的传染,以及研发节奏差异带来的工程协同上的问题。

4.iframe

iframe是一个天然的沙箱

优点

  • 非常简单,使用没有任何心智负担
  • 隔离完美,无论是 js、css、dom 都完全隔离开来
  • 多应用激活,页面上可以摆放多个iframe来组合业务(适合单页面多应用)
  • 如果页面里有iframe的话,内部的iframe,如果iframe是和标签页属于同一域,那就共用渲染进程,否则会给这个iframe一个单独的渲染进程,所以单页面下(没有路由管理),可能比qiankun等通过拉取代码单线程运行要快

但是它有很多弊端:

  • url 不同步,每次进来都要加载,url状态不能保留
  • DOM 结构不共享。比如子应用里有一个 Modal(弹窗),显示的时候只能在那一小块地方展示,不能全屏展示
  • 无法跟随浏览器前进后退
  • 天生的硬隔离,无法与主应用进行资源共享,内存变量不共享,交流也很困难
  • 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程,白屏时间太长,对于SPA 应用应用来说无法接受

当然,腾讯提出了一个“完美”的iframe微前端方案,可以看一看基于 iframe 的全新微前端方案(腾讯PCG)

主要是

  • 利用shadow dom插拔html、css来解决dom割裂问题(比如toast)

  • 在iframe中执行js,然后让iframe的路由状态同步到浏览器的路由解决路由问题和通信问题(同域下通信比较简单)

  • 然后内部做了一些缓存(实例化子应用),降低子应用资源加载的成本

而官方也谈及了wujie的不足之处:

  • 内存占用较高,为了降低子应用的白屏时间,将未激活子应用的shadowRootiframe常驻内存并且保活模式下每张页面都需要独占一个wujie实例,内存开销较大
  • 兼容性一般,目前用到了浏览器的shadowRootproxy能力,并且没有做降级方案
  • iframe劫持documentshadowRoot时,某些第三方库可能无法兼容导致穿透

5.single-spa

Single SPA 将自己定义为一种“前端微服务 Javascript 框架”,它将生命周期应用于每个应用程序。每个应用程序都可以响应 url 路由事件,并且知道如何从 DOM 引导,加载和卸载自身

single-spa 仅仅是一个子应用生命周期的调度者。single-spa 为应用定义了 boostrap, load, mount, unmount 四个生命周期回调:

值得注意的是

  • Register 不是生命周期,指的是调用 registerApplication 函数这一步
  • Load 是开始加载子应用,怎么加载由开发者自己实现(等会会说到)
  • Unload 钩子只能通过调用 unloadApplication 函数才会被调用

当触发到 window.location.href 匹配到 url 时,开始走对应子 App 的这一套生命周期嘛。所以,single-spa 还要监听 url 的变化,然后执行子 app 的生命周期流程。

到此,我们就有了 single-spa 的大致框架了,无非就两件事:

  • 实现一套生命周期,在 load 时加载子 app,由开发者自己玩,别的生命周期里要干嘛的,还是由开发者造的子应用自己玩

  • 监听 url 的变化,url 变化时,会使得某个子 app 变成 active 状态,然后走整套生命周期

  • 根据传入的参数activeWhen判断哪个应用需要加载,哪个应用需要卸载或清除,并将其push到对应的数组

    • 如果应用已经启动,则进行应用加载或切换。针对应用的不同状态,直接执行应用自身暴露出的生命周期钩子函数即可。

    • 如果应用未启动,则只去下载appsToLoad中的应用。

该图作者的评价:

传统 SPA 和 Single SPA 应用程序之间的主要区别在于它们能够与其他应用程序共存,并且它们各自没有自己的 HTML 页面。

single-spa 的理念是希望主应用可以做到非常非常简单的和轻量,简单到只要一个 index.html + 一个 main.js 就可以完成微前端工程,连 Webpack 都不需要,直接在浏览器里执行

在single-spa的角度,微前端就是“微模块加载器”,它主要解决的是:如何实现前端的“微服务化”,从而让应用、组件、逻辑都成为可共享的微服务,这从single-spa关于微前端的概述中可以看出:

image-20220515174707011

翻译过来反别是微应用、微组件、微模块,而single-spa要求它们都以SystemJS的形式打包,所以本质上都是微模块

关于single-spa一部分实践可以看这个,但是由于这里的代码的很多包都比较久远,最好下载和它一致的包再进行实践 github

SystemJS

1.浏览器里用import/export。

SystemJS 的好处和优势有且仅有一点:那就是那就是不需要打包工具,直接在浏览器里使用 ES6 的 import/export,也就是说它是一个允许ES module模块运行在旧版本ES环境的库。

Single SPA 中使用到了SystemJS,SystemJS是一个运行时加载模块的工具(动态导入和导入映射),是现阶段下(浏览器尚未正式支持importMap)原生ES Module的完全替代品,对比Webpack 之类的,

其实你也可以理解为SystemJS是为了解决轻量级,不用打包,即可做到 import Vue from 'vue' 形式去导包

importmap的形式,兼容性差

<script type="importmap">
{
  "imports": {
    "dayjs": "https://cdn.skypack.dev/dayjs@1.10.7",
  }
}
</script>

systemjs形式

<script type="systemjs-importmap">
    {
      "imports": {
        "@react-mf/root-config": "//localhost:9000/react-mf-root-config.js"
      }
    }
</script>

SystemJS动态加载的模块必须是SystemJS模块或者UMD模块。

2.引入公共依赖

SystemJS 另一个好处就是可以通过 importmap 引入公共依赖。

假如,我们有三个子应用,它们都有公共依赖项 antd,那每个子应用打包出来都会有一份 antd 的代码,就显示很冗余。(qiankun就有这种冗余)

一个解决方法就是在主应用里,通过 importmap 直接把 antd 代码引入进来,子应用在 Webpack 设置 external 把 antd 打包时排除掉。子应用打包就不会把 antd 打包进去了,体积也变小了。

对于公共样式的处理;

  1. 将公共的 CSS 放到 importmap 里,也可以理解为在 index.html 里直接加个 link 获取 antd 的 CSS 完事
  2. 将所有的公共的 UI 库都 import 到 utility 里,将 antd 所有内容都 export,再把 utility 包放到 importmap 里,然后 import { Button } from '@your-org-name/utility'; 去引入里面的组件

其实上面两个方法都大同小异,思路都是在主应用一波引入,只是一个统一引入CSS,另一个统一引入 UI 库。

JS隔离

在single-spa使用

import singleSpaLeakedGlobals from 'single-spa-leaked-globals';
const leakedGlobalsLifecycles = singleSpaLeakedGlobals({
  globalVariableNames: ['$', 'jQuery', '_'], // 新添加的全局变量
})

在 mount A 子应用时,正常添加全局变量,比如 jQuery 的 $, lodash 的 _。在 unmount A 子应用时,用一个对象记录之前给 window 添加的全局变量,并把 A 应用里添加 window 的变量都删掉。下一次再 mount A 应用时,把记录的全局变量重新加回来就好了。

但是,这个库的局限性在于:每个 url 只能加一个子 app,如果多个子 app 之间还是会访问同一个 window 对象,也因此会互相干扰,并不能做到完美的 JS 沙箱。

CSS隔离

而关于css的样式隔离,官方的建议是

  • 使用 Scoped CSS
  • 要是嫌麻烦,可以在子应用 Webpack 使用 PostCSS Prefix Selector 给样式自动加前缀
  • 亦或者开发者自己用shadow-dom来处理…

why SystemJS

都2022年了,怎么还要在浏览器环境写 JS 呢?不上个 Webpack 都不好意思说自己是前端开发了…

那为什么 single-spa 还要推荐 SystemJS 呢?但是根据知乎上优质答主写代码的海怪猜测是因为 single-spa 希望主应用应该就一个空壳子,只需要管内容要放在哪个地方,所有的功能、交互都应该交由 index.html 来统一管理。(直接在html里写满script想想就可怕)

这也表明了:

  • 资源罗列:在systemjs中,我们必须手动实现应用加载逻辑,挨个罗列子应用需要加载的资源,这在大型项目里是十分困难的(特别是使用了文件名hash时);另外它只能以js文件为入口,无法直接以html为入口,这使得嵌入子应用变得很困难,也正因此,single-spa不能直接加载jQuery应用。

  • 资源加载优化:将整个微应用打包成一个 JS 文件,常见的打包优化基本上都没了,比如:按需加载、首屏资源加载优化、css 独立打包等优化措施。

  • js名称:项目发布以后出现了 bug ,修复之后需要更新上线,为了清除浏览器缓存带来的影响,一般文件名会带上 chunkcontent,微应用发布之后文件名(js bundle )都会发生变化,这时候还需要更新主应用中微应用配置,然后重新编译主应用然后发布,这是不能容忍的。

    • 你可以理解为每次文件名一变化:jlkasjfdlkj.jalkjdsflk.js,这样一来,每次子应用一发布,入口 JS 文件名肯定又要改了,导致主应用引入的 JS url 又得改了

所以,为了填平 single-spa 遗留下来的坑,阿里基于 single-spa 造出了 qiankun 微前端框架,真正实现了微前端的所有特性.

6.Qiankun

qiankun是基于single-spa开发的

Single-spa控制路由和应用入口 + qiankun JS、CSS隔离和应用通信 + import-html-entry控制应用加载(single-spa用的是system.js)

qiankun基于single-spa,加强了微应用集成能力,却抛弃了微模块的能力。所以,它们的区别就是微服务的粒度,qiankun的所能服务的粒度是应用级别,而single-spa则是模块级别。它们都能将前端进行拆分,只是拆分的粒度不同罢了。

  1. 微应用加载器:“微”的粒度是应用,也就是HTML,它只能做到应用级别的分享
  2. 微模块加载器:“微”的粒度是模块,也就是JS模块,它能做到模块级别的分享

两个框架出现的背景:

qiankun:阿里内部有大量年久失修的项目,业务侧急需工具去把他们快速、安全的集成到一起。在这个角度,乾坤根本没有做模块联邦的需求,它的需求仅仅是如何快速、安全的把项目集成起来。所以乾坤是想做一个微前端工具。

single-spa:学习后端的微服务,实现前端的微服务化,让应用、组件以及逻辑都成为可共享的微服务,实现真正意义上的微前端。所以single-spa是想做一个game-changer

css隔离

目前qiankun主要提供了两种样式隔离方案

  • 一种是基于shadowDom的;

    • 在配置的时候当启用strictStyleIsolation时,qiankun将采用shadowDom的方式进行样式隔离,即为子应用的根节点创建一个shadow root,整个应用的所有DOM将形成一棵shadow tree,它内部所有节点的样式对树外面的节点无效,所有就实现了样式隔离。
    • const supportShadowDOM = !!document.head.attachShadow || !!(document.head as any).createShadowRoot;
    • if (strictStyleIsolation) {
          if (!supportShadowDOM) {
            // 报错
            // ...
          } else {
            // 清除原有的内容
            const { innerHTML } = appElement;
            appElement.innerHTML = '';
            let shadow: ShadowRoot;
            if (appElement.attachShadow) {
              // 添加 shadow DOM 节点
              shadow = appElement.attachShadow({ mode: 'open' });
            } else {
              // deprecated 的操作
              // ...
            }
            // 在 shadow DOM 节点添加内容
            shadow.innerHTML = innerHTML;
          }
        }
    • 但是这种方案是存在缺陷的。因为某些UI框架可能会生成一些弹出框直接挂载到document.body下,此时由于脱离了shadow tree,所以它的样式仍然会对全局造成污染。
    • 基于 ShadowDOM 的严格样式隔离并不是一个可以无脑使用的方案,大部分情况下都需要接入应用做一些适配后才能正常在 ShadowDOM 中运行起来(比如 react 场景下需要解决这些 问题,使用者需要清楚开启了 strictStyleIsolation 意味着什么。后续 qiankun 会提供更多官方实践文档帮助用户能快速的将应用改造成可以运行在 ShadowDOM 环境的微应用
  • 另一种则是实验性的,思路类似于Vue中的scoped属性,给每个子应用的根节点添加一个特殊属性,用作对所有css选择器的约束。

    • qiankun首先判断当前sandbox是否支持scope css,支持的话才添加

      export function isEnableScopedCSS(sandbox: FrameworkConfiguration['sandbox']) {
        if (typeof sandbox !== 'object') {
          return false;
        }
      
        if (sandbox.strictStyleIsolation) {
          return false;
        }
      
        return !!sandbox.experimentalStyleIsolation;
      }
    • 在配置的时候当启用experimentalStyleIsolation。这种方案的策略是为子应用的根节点添加一个特定的随机属性

      // 假如子应用名字叫 child
      // 转换前
      .app-main {
          font-size: 14px;
      }
      // 转换后
      div[data-qiankun="child"] .app-main {
          font-size: 14px;
      }
  • shadowdom的css隔离开启方式:start的时候或者手动加载子应用的时候传入option,option包含sanbox属性,sanbox的strictStyleIsolation为true

    scope开启方式:start的时候或者手动加载子应用的时候传入option,option包含sanbox属性,sanbox的experimentalStyleIsolation为true

    start({
        sandbox: { strictStyleIsolation?: boolean, experimentalStyleIsolation?: true }
    })
    loadMicroApp(app, {
        sandbox: { strictStyleIsolation?: boolean, experimentalStyleIsolation?: true }
    })

    注意事项: 对于scope的css隔离方案,目前 @keyframes, @font-face, @import, @page 等规则不会支持 (i.e. 不会被改写)

js隔离

qiankun通过import-html-entry,可以对html入口进行解析,并获得一个可以执行脚本的方法execScripts(上面提及到的 execScript )。

qiankun默认启用沙箱,也就是上面css隔离中 start里的那个sanbox选项,此时会开启js隔离严格模式 strictGlobal

第一步创建一个沙箱容器 sandboxContainer

三种沙箱:

  1. 首先初始化一个沙箱ProxySandbox,里面创造一个fakeWindow,它拥有window的所有属性,并且通过new Proxy返回一个fakeWindow的代理对象

  2. SnapshotSandbox:

    针对IE11不支持proxy,所以qiankun通过快照策略来隔离js,缺点是无法支持多实例场景。(可以理解为使用single-spa的js隔离方法)

    if (window.Proxy) {
        sandbox = useLooseSandbox ? new LegacySandbox(appName, globalContext) : new ProxySandbox(appName, globalContext);
      } else {
        sandbox = new SnapshotSandbox(appName);
      }
    class SnapshotSandbox {
      ...
    
      active() {
        // 记录当前快照
        this.windowSnapshot = {} as Window;
        getKeys(window).forEach(key => {
          this.windowSnapshot[key] = window[key];
        })
    
        // 恢复之前的变更
        getKeys(this.modifyPropsMap).forEach((key) => {
          window[key] = this.modifyPropsMap[key];
        });
    
        this.sandboxRunning = true;
      }
    
      inactive() {
        this.modifyPropsMap = {};
    
        // 记录变更,恢复环境
        getKeys(window).forEach((key) => {
          if (window[key] !== this.windowSnapshot[key]) {
            this.modifyPropsMap[key] = window[key];
            window[key] = this.windowSnapshot[key];
          }
        });
    
        this.sandboxRunning = false;
      }
    }
  3. LegacySandbox: 其实看到源码还有个LegacySandbox,不过后面废弃了,因为没有了sanbox配置没有了loose这个配置

    const useLooseSandbox = typeof sandbox === 'object' && !!sandbox.loose;
    const sandbox = useLooseSandbox ? new LegacySandbox(appName, globalContext) : new ProxySandbox(appName, globalContext);

执行js代码:

通过html-import-entry得到js执行函数: execScripts

我们可以看到他通过传入proxy,执行 getExecutableScript得到js code,其中使用 function(){}(with(window){fn.bind(window.proxy)(window.proxy, window.proxy, window.proxy)})

var code = getExecutableScript(scriptSrc, rawCode, proxy, strictGlobal);
evalCode(scriptSrc, code);
function getExecutableScript(scriptSrc, scriptText, proxy, strictGlobal) {
  var sourceUrl = isInlineCode(scriptSrc) ? '' : "//# sourceURL=".concat(scriptSrc, "\n"); // 通过这种方式获取全局 window,因为 script 也是在全局作用域下运行的,所以我们通过 window.proxy 绑定时也必须确保绑定到全局 window 上
  // 否则在嵌套场景下, window.proxy 设置的是内层应用的 window,而代码其实是在全局作用域运行的,会导致闭包里的 window.proxy 取的是最外层的微应用的 proxy

  var globalWindow = (0, eval)('window');
  globalWindow.proxy = proxy; // TODO 通过 strictGlobal 方式切换 with 闭包,待 with 方式坑趟平后再合并

  return strictGlobal ? ";(function(window, self, globalThis){with(window){;".concat(scriptText, "\n").concat(sourceUrl, "}}).bind(window.proxy)(window.proxy, window.proxy, window.proxy);") : ";(function(window, self, globalThis){;".concat(scriptText, "\n").concat(sourceUrl, "}).bind(window.proxy)(window.proxy, window.proxy, window.proxy);");
} // for prefetch

此时with语法内的window就是proxy:把解析出的scriptText(即脚本字符串)用with(window){}包裹起来,然后把window.proxy作为函数的第一个参数传进来,所以with语法内的window实际上是window.proxy。此时我们的js代码执行时,全局变量都会被挂载到沙箱的proxy当中去。

然后再通过 evalCode函数全局执行js代码

export function evalCode(scriptSrc, code) {
  var key = scriptSrc;

  if (!evalCache[key]) {
    var functionWrappedCode = "window.__TEMP_EVAL_FUNC__ = function(){".concat(code, "}");
    (0, eval)(functionWrappedCode);
    evalCache[key] = window.__TEMP_EVAL_FUNC__;
    delete window.__TEMP_EVAL_FUNC__;
  }

  var evalFunc = evalCache[key];
  evalFunc.call(window);
}

应用通信

一般来说,微前端中各个应用之前的通信都是比较少的。当然部分呢少量的通信是在所难免的,qiankun官方提供了一个简要的方案,思路是基于一个全局的globalState对象。这个对象由基座应用负责创建,内部包含一组用于通信的变量,以及两个分别用于修改变量值和监听变量变化的方法:setGlobalState和onGlobalStateChange。

飞冰JS隔离

飞冰也是一款阿里的微前端方案:对于css污染(css隔离),由于微应用是不同的路由,所以暂时没有考虑(实验性的shadow dom方案)

js隔离

  • 规范
    • 微应用避免改变全局状态
  • 基于 Proxy 的运行沙箱

参考于:

微前端技术原理 —— 飞书团队

最容易看懂的微前端知识

基于 iframe 的全新微前端方案(腾讯PCG)

将微前端做到极致-无界微前端方案

vivo基于 iframe 的微前端框架 —— 擎天

微前端解决方案

微前端在美团外卖的实践

【微前端】single-spa 到底是个什么鬼

微前端框架qiankun之原理与实战

揭开 import-html-entry 面纱

微前端框架 之 qiankun 从入门到源码分析

可能是你见过最完善的微前端解决方案

【微前端】qiankun 到底是个什么鬼

更多关于微前端的相关介绍,推荐大家可以去看这几篇文章:(qiankun官网的推荐)

为什么不使用iframe:https://www.yuque.com/kuitos/gky7yw/gesexv


文章作者: Hello
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hello !
 上一篇
TypeScript TypeScript
1.结识TypeScript传统上,JS旨在用于简短,快速运行的代码片段,作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM,所以JS比较适合单线程。 但是由此导致后期维护性比较差 面向对象撰写麻烦 没有类型规范 微软于2014年
2022-05-14
下一篇 
Hexo的部分问题 Hexo的部分问题
Quick Start打开win+R进入cmd,然后在正确目录下进入:cd source_post(指定blog下) 基本操作用typora修改完毕后,1.使用“hexo clean”进行清理(1.5.若是发生主题等修改后,要接上hexo
2022-05-08 Hello
  目录