微前端

134 阅读7分钟

微前端是什么

微前端的灵感来源于服务端微服务的理念,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立运行、独立开发、独立部署。

目的或者是使用场景

  1. 后台比较分散,体验差别大,因为要频繁跳转导致操作效率低,希望能统一收口的一个系统内
  2. 单页面应用非常庞大,多人协作成本高,开发/构建时间长,依赖升级回归成本高
  3. 系统有二方/三方接入的需求

微前端优缺点

优点:

  1. 应用自治:独立开发,独立部署,独立运行。
  2. 单一职责:每个前端应用可以专注于完成特定的功能。
  3. 技术栈无关:微前端架构支持多种技术栈,如Angular、React和Vue等可以并存,这对于拥有不同技术偏好的团队来说是一个优势。
缺点:
  1. 拆分的粒度越小,便意味着架构变得复杂、维护成本变高,测试和部署变复杂。
  2. 用户体验不连贯:不同的团队可能使用不同的技术栈和框架。
  3. 子应用间的资源共享能力较差(某些静态资源需要单独拷贝一份)

本质上来说,我们是在用整个系统的复杂度代价换取单个前端的简洁度。

现有微前端解决方案:

方案描述技术栈优点缺点
iframe通过 iframe的方式将这些应用嵌入到父应用系统中无限制1. 技术栈无关,子应用独立构建部署 2. 实现简单,子应用之间自带沙箱,天然隔离,互不影响多域名鉴权问题,体验差、路由无法记忆、页面适配困难、依赖无法复用等都具有局限性,资源开销巨大,通信困难
Nginx 路由转发通过Nginx配置实现不同路径映射到不同应用无限制简单、快速、易配置在切换应用时触发页面刷新,通信不易
Web Components是一个浏览器原生支持的组件封装技术无限制自定义元素 shadow DOM 支持隔离shadow 兼容性支持度不够好
webpack5 模块联邦webpack5 模块联邦 去中心模式、脱离基座模式。每个应用是单独部署在各自的服务器,每个应用都可以引用其他应用,也能被其他应用所引用统一技术栈学习成本低,各个应用的资源都可以相互共享需要升级Webpack5技术栈必须保持一致改造旧项目难度大
single-spa通过路由路径来在dom上加载不同的子应用无限制轻量级社区规模可能较小,主要关注微应用的加载和管理,对于通信、状态管理等高级功能,可能需要结合其他库或自行实现

微前端框架选型

常见实现框架:

1)qiankun乾坤: 蚂蚁金服开发维护,基于 Single-SPA, 提供了更加开箱即用的 API ( single-spa + sandbox + import-html-entry ) 做到了,技术栈无关、并且接入简单

   (2)MicroApp:京东出品,一款基于WebComponent的思想,轻量、高效、功能强大的微前端框架

   (3single-spa:微服务化的 JavaScript 前端解决方案 (本身没有处理样式隔离, js 执行隔离) 实现了路由劫持和应用加载

   (4)无界:腾讯开发维护,实现预加载,应用保活(类似keep-alive),配置成本低;但是2022年推出,时间太短,缺乏社区打磨

     除此持外还有piral,mosaic等框架,综合对比考虑后,选择使用更成熟的蚂蚁qiankun接入项目,原因如下:

    (注:截止2023,qiankun官方不支持vite,目前使用qiankun集成vite构建的vue项目需要依赖插件)      

微前端:乾坤 VS 无界

qiankun实现原理

1、监视路由

  • 通过监听hashChange和popState这两个原生事件来检测路由变化

2、匹配子应用

  • 重写路由window.popState, replaceState,在保留原有功能的基础上,增加子应用entry映射相关逻辑
  • 针对变化的路由,匹配子应用

3、加载子应用

  • import-html-entry 解析入口文件中的html 和 script
  • 动态创建script去执行jsCode
  • 通过umd模块获取子应用,调用子应用mount方法,render子应用

4、渲染子应用

  • 把js和html,渲染到提前预留的#app容器中

无界(iframe+ web component)

使用iframe有三个难以解决的问题,

  • 路由状态丢失,刷新一下,iframe的url状态就丢失了
  • dom割裂严重,弹窗只能在iframe内部展示,无法覆盖全局
  • 通信非常困难,只能通过postmessage传递序列化的消息

无界微前端框架通过继承iframe的优点,解决iframe的缺点,打造一个接近完美的iframe方案

在应用A中构造一个shadow和iframe,然后将应用B的html写入shadow中,js运行在iframe中,注意 iframe url,iframe保持和主应用同域但是保留子应用的路径信息,这样子应用的js可以运行在iframe的location和history中保持路由正确。

在iframe中拦截document对象,统一将dom指向shadowRoot,此时比如新建元素、弹窗或者冒泡组件就可以正常约束在shadowRoot内部。

  • dom割裂严重的问题,主应用提供一个容器给到shadowRoot插拔,shadowRoot内部的弹窗也就可以覆盖到整个应用A
  • 路由状态丢失的问题,浏览器的前进后退可以天然的作用到iframe上,此时监听iframe的路由变化并同步到主应用,如果刷新浏览器,就可以从url读回保存的路由
  • 通信非常困难的问题,iframe和主应用是同域的,天然的共享内存通信,而且无界提供了一个去中心化的事件机制

qiankun应用间通信

  1. 第一种是 qiankun 官方提供的通信方式 - Actions 通信,适合业务划分清晰,比较简单的微前端应用,一般来说使用第一种方案就可以满足大部分的应用场景需求。
  2. 第二种是基于 redux 实现的通信方式 - Shared 通信,适合需要跟踪通信状态,子应用具备独立运行能力,较为复杂的微前端应用。
  3. 官方提供的 props 主应用: import { registerMicroApps, start, } from "qiankun"; import router from '@/router' const apps = [ { name: "App1MicroApp", entry: '//localhost:9001', container: "#app1", activeRule: "/app1", props: { parentRouter: router } } ];

registerMicroApps(apps); start(); 子应用: let instance = null let router = null

function render (props) { router = new VueRouter({ base: window.POWERED_BY_QIANKUN ? '/app1' : '', mode: 'history', routes: routes }) instance = new Vue({ router, store, // 挂载在根节点上 data(){ return { parentRouter: props.parentRouter, } }, render: (h) => h(App) }).$mount('#app') }

export async function mount(props) { render(props); }

// 在子应用中使用就可以访问到这个parentRouter了 this.$root.parentRouter

沙箱隔离机制

框架  特点
乾坤js隔离机制: SnapshotSandboxLegacySandboxProxySandbox css隔离机制:strictStyleIsolationexperimentalStyleIsolation
无界js通过iframe来隔离,css通过webcomponent + shadowdom来隔离

沙箱隔离类型和原理

SnapshotSandbox 快照沙箱

顾名思义,快照沙箱就是,给你拍一张照片,记录你此时的状态,乾坤的快照沙箱是根据diff 来实现的,主要用于不支持window.Proxy的低版本浏览器,而且只是使用单个的自用

缺点
  1. 需要遍历window上的所有属性,性能差
  2. 同时间内只能激活一个微应用

代理沙箱

Qiankun 基于es6 的Proxy 实现了两种应用场景不同的沙箱,一种是legacySandbox(单例),一种是proxySandbox(多例)。因为都是基于Proxy实现的,所以称为代理沙箱。

legacySandbox实现原理

legacySandbox 设置三个参数来记录全局变量,分别记录沙箱新增的全局变量addedPropsMapInSandbox,记录沙箱更新的全局变量modifiedPropsOriginalValueMapInSandbox,用于在任意时刻做snapshot的currentUpdatePropsValueMap。

proxySandbox(多例沙箱)

激活沙箱后,每次对window 取值的时候,先从自己沙箱环境的fakeWindow 里面找,如果不存在,就从rawWindow(外部的window)里去找;当对沙箱内部window 对象赋值的时候,会直接操作fakeWindow,而不会影响到rawWindow

参考链接

juejin.cn/post/723602… juejin.cn/post/724407… www.cnblogs.com/weichen913/… zhuanlan.zhihu.com/p/578093950