多维度从零开始介绍与分析微前端,一起探索微前端技术方案

5,275 阅读27分钟

前言

本文重点介绍一下微前端相关概念性问题。

  • 当前章节:多维度从零开始介绍与分析微前端,一起探索微前端技术方案
  • 后续章节:基于qiankun从零开始构建微前端,以及源码分析等

微前端相关介绍

1)背景

随着行业的发展,系统复杂度的上升,如何拆分大型项目的技术栈,进一步提高开发效率,以及成为行业的探索方向。

从十年前的前后分离,市场已经证明了,前端从前后混合开发独立出来的优势,让其各各自语言发挥出最高的开发效率以及最好的体验效果。前后端可以更专注的探索自己的技术领域。

然后系统还是越来越复杂。随着业务的不断扩张,需求的不断新增,项目又慢慢的臃肿起来。于是,需要承担服务器压力的服务端,市场慢慢推出"微服务"。

思路开始从,追求大而全,转化为小而美

微服务个人简单的理解,就是将系统切割成,一些可独立运行、可协同工作的小的服务。它解决了项目臃肿的问题,将其解耦成相对较小的项目,但是却不影响他们相互之间的"协同工作"。同时可以让不同的技术为一个系统服务,扩展性也针对微应用,扩展单个服务影响的范围较小,从而系统出错的概率也就越低。

再慢慢,前端工程也开始面临同样的问题:

image.png

类似阿里云,他的中台,据某个文章提起,它的中台是由15个不同团队开发合并而成。

这时候如果还是用单个项目的形式完成,过程肯定十分艰辛,光统一这15个团队的规范,就已经够吃一壶。这时候就需要重新规划前端如何工程化的问题。

先看看"单项目"的弊端:

  • 1.打包项目的等待时间,足以让人感叹时光飞逝,一早上打包几次即可开始午饭。

  • 2.羡慕新的技术栈真香,而原项目改动太大,而改动后的风险也十分大,望而祛步。

  • 3.不同团队项目的融合,不同的技术栈不兼容。从而引发不同的技术栈天下第一的争论:你为啥不用X语言?X语言天下第一。

  • 4.历史项目二次开发,技术栈可了解到前端数十年的发展史,寻找年纪比你还大的说明文档。而无法用上新的语言。

  • 欢迎补充其他吐槽...

此时,自然有大神帮你解决这些问题。大神的思路有一种思路叫创新,还有一种思路叫借鉴。而前端解决这类问题,开始借鉴"微服务"的思维。

微前端个人鉴定的理解,让其前端项目转换成多个独立运行,独立开发的项目,同时带来了不同项目通信,缓存等共用的解决方案。让不同的技术栈,不同的业务模块化开发成为可能。

其思路重点,跟微服务还是有点区别:

微服务注重解耦,微前端讲究聚合。

2)微前端存在的意义

上述也简单提到微前端存在的意义,本节重点分析。

看到有一篇文章是这样评论微前端,十分有意思:

微前端就是分而治之的手段,让「上帝的归上帝,凯撒的归凯撒」。

具体如下:

  • 应用自治:上述提到,微前端的大的项目,切割成不同的小项目。而每个项目可以有依赖关系,也可以毫无关系。从而完成各自应用各自管理的目的。

  • 单一职责:根据划分,每个应用有每个应用的定位,可参考问章问题,微前端如何划分。

  • 脱离技术栈:有应用自治,说明每个应用允许自己的特色,哪怕是框架不同,都可以融洽到同一项目中。

  • 增量升级:很多时候,我们因为更新一点鸡毛瑕疵,而重启整个系统。而微前端出现,可以让你,只更新鸡毛的项目即可,风险成本更小。

  • 解决项目太大的问题:上述提到打包项目的等待时间,足以让人感叹时光飞逝。如果引入微前端,可释放项目的大小。

这些都是微前端存在的意义,欢迎补充。


3)微前端适合的场景

来自(www.yuque.com/kuitos/gky7… ,个人觉得回答得非常好:

    1. 旧的系统不能下,新的需求还在来。 没有一家商业公司会同意工程师以单纯的技术升级的理由,直接下线一个有着一定用户的存量系统的。而你大概又不能简单通过 iframe 这种「靠谱的」手段完成新功能的接入,因为产品说需要「弹个框弹到中间」
    1. 你的系统需要有一套支持动态插拔的机制。 这个机制可以是一套精心设计的插件体系,但一旦出现接入应用或被接入应用年代够久远、改造成本过高的场景,可能后面还是会过渡到各种微前端的玩法。

4)微前端如何划分

每个公司有每个公司的场景,家家有本难练的经,而且各自不同。引入微前端的本质,就是解决这些问题。所以,你的问题是什么?微前端就如何去划分。

这里笔者分析一下微前端一些适合划分的场景:

  • 推荐1:根据组织结构划分

适合组织结构调整的企业,引入新的技术栈等。旧的还需维护,新的想用新的技术。按组织架构划分是一种可行方案。

  • 推荐2:根据子业务划分

很多前端项目前期没规划,都是一把嗦,all in模式。啥意思?类似一些商业公司,啥erp, crm, oa, tms, oms, mis等,所有业务全部混一起。

这样的项目,长期维护,页面上千个很正常。如果根据子业务划分,也是一个不错的选择。

  • 推荐3:根据权限划分

系统中有N个权限,且各自独立。为了方便资源的整合与加载,也可以按权限划分,避免引入本不应引用的包之类。

  • 推荐4:根据变更频率划分

有些业务求稳定,有些鸡毛细节要不断更新。此时,按变更频率划分也是一个不错的选择。

  • 方案5:根据跟随后端服务划分

额,好的,也是一个好的方案(虽然前端想不太受这些束缚,但还是跟着规划走)。

还有其他的划分方案,欢迎补充。


5)当前流行的微前端框架

以下分别介绍一下市场开源的微前端框架。个人的观点推荐qiankun,毕竟文档或者论坛,都相对成熟一点,api也相对简单。

  • 1)single-spa
  • 官方介绍:用于前端微服务的 javascript 路由器
  • 官网地址:single-spa.js.org/

该微前端的框架,是很多其他微前端框架的底层。构建共存的微前端,并且可以(但不需要)使用自己的框架编写。

  • 2)qiankun
  • 官方介绍:可能是你见过最完善的微前端解决方案
  • 官网地址:qiankun.umijs.org/zh/
  • 开发团队:蚂蚁金服
  • 3)microApp
  • 4)garfish
  • 官方介绍:包含构建微前端系统时所需要的基本能力,任意前端框架均可使用。接入简单,可轻松将多个前端应用组合成内聚的单个产品。
  • 官网地址:garfish.top/
  • 开发团队:字节跳动
  • 5)emp
  • 官方介绍:Micro Frontends 的一个实现,基于Webpack5 Module Federation,但已做好生产准备
  • 官网地址:github.com/efoxTeam/em…
  • 开发团队:YY
  • 6)icestark
  • 官方介绍:使用 icestark 构建您的下一个微前端应用,或无痛迁移您目前的巨型应用。如同开发 SPA 应用一样简单,不仅解决多个开发团队协同问题,还带来了安全的沙箱、优秀的性能体验。
  • 官网地址:icestark.gitee.io/
  • 开发团队:阿里巴巴

微前端的解决思路

如何实现微前端,将一个应用拆分成多个应用,

下述是个人在阅读部分文章,以及书籍,以及实践后的个人汇总,有不同的见解可以指出。

1)路由式分发

简单理解:从主应用分发路由到路由分发应用。

每个项目都是独立项目,我们可以通过HTTP反向代理的技术,将请求的路由转发到对应的应用页面上。

如下图例:

image.png

该方案是"最简单","最好理解"的微前端方案。但单纯使用该方案,只是前端页面的"聚合",看着似乎就是把不同的项目,合并成一个项目部署。

该方案,页面永远的存在唯一的一个应用,可是可能随时切换到另外一个应用中。风险较少,适用的场景也相对较少,适合交互较少,而功能较独立的中台或者应用交互。

个人举个跟人觉得比较适合的例子:(然而企鹅并没有这么干,可能是各个团队都需要自己的kpi?)

如腾讯新闻:news.qq.com/

里边内嵌nba板块,腾讯体育:sports.qq.com/nba/

两者属于完全不同的风格,很大可能性来自不同的团队。此时两者是需要重复登录两次的,但是从一个用户的角度来说,都是你家企业的网站,在同一入口中。我刚刚看完你的新闻模块已经登录,想看看NBA模块,还要我再登录一次,是否很不爽。

这里如果借鉴一下微前端的概念,他们之间就可以无缝的打通用户关系,可以给用户带来无感知,甚至在体验中没注意都没发现这是两个不同的应用。这就是路由式分发来的好处。

然而它的弊端也是十分明显:

  • 不同的项目跳转时,需要完整的重新下载新项目的资源,会有白屏等待等明显缺陷。

  • 不同项目之间无法实时通信,只能通过静态缓存通信(如cookies,localStorage等),没有状态控制器(类似vuex)的概念,赋值后还要刷对应的页面才能生效,用户效果十分不好。

  • 只能存在页面级,无法完成多个应用共存的局面。

个人总结他比较适合的场景

  • 不同的技术栈差异比较大,而且比较难兼容,迁移等

  • 旧的项目有点烂,但又十分重要,此时又不想花大量的时间在改造系统上

  • 临时才决定,不同的项目,应用需要放在一起并行时

2)前端微服务化

我们再来谈谈,应用共存的方案。

上述中的路由式分发是页面级的完成微服务,每一个页面都是独立的项目。而前端微服务化则讲究通一个页面可以嵌入多个应用的显示,来个图例更好的理解:

image.png

在同一页面上,嵌入不同的项目(非iframe)。采取这种方式,意味着,将在同一个页面上同时存在两个前端应用在运行。

当我们指向某个应用的路由时,会加载,运行一个或多个应用,且页面仍然可以保存运行的状态。且这些应用,不会受技术栈影响,如页面可以同时处在React,Vue,Angular,jsp等不同技术方案共存。

  • 前端微服务化方案具备十分的设计意义,其一脱离技术本身,让不同技术融入同一个项目成为可能。其二,让其不同的应用,流畅的存在于同一个页面,也是技术上的一个突破。

该方案的实践主要有两个步骤:

  • 1.页面适当的地方引入或者创建dom。

  • 2.用户在对应的操作时,加载对应的应用,并在需要的时候卸载。

然而在步骤2的设计上,还有诸多的问题需要我们去解决。如不同的项目插件冲突,同一页面样式如何不被全局污染,定时器如何不冲突,如何保障内存不溢出等问题。都是比较头疼难解决的问题。

3)微应用化

微应用化,指在开发的时候,应用都以单一的项目进行开发,而在运行的时候,去合并组成为一个新的应用。但有个特点,就是要求统一依赖。有点类似"公用包"的设计概念。来个图例更加的清晰。

image.png

根据图例看出,此时将应用拆分成多个子应用,借助公用依赖,最后通过软件工程的方式,在部署构建环境中,组合多个独立应用成一个单体应用。

微应用化,跟上述的前端微服务化有些类似,在开发时都是独立应用的,在构建时又可按照独立业务,独立模块,独立需求,单独加载。

但是两者还是有一点的区别:

前端微服务化无技术栈的要求,将多个应用解耦出来,只是展示的时候显示在一起。而微应用化讲究依赖一致,此时肯定技术栈是统一的,更适合同一个团队,按业务把项目切割成微应用的概念。

4)微件化

前端微件化(Widget)指的是可以直接嵌入应用,能运行的代码,需要预编译好,在使用时直接加载使用。

微前端的微件化,指每个团队编写好自己的业务代码,并将代码编译好部署再服务器上。运行时候,只需要加载对应的业务模块即可。

处理得好的话,后续更新,我们只需要更新对应的模块即可。

似乎还有点不理解?这里无需图例都可以明了。

在单页面框架中,当我们采用异步的情况下,加载某一个组件,这时候浏览器会从服务器下载一段js文件回来,然后执行后,嵌入到对应的页面。这就是典型的微件化。

看到这里,你可能会觉得微件化应用微服务化有些相同。其实不然:

微件化,即通过对构建系统的 hack,使不同的前端应用可以使用同一套依赖。它在应用微服务化的基本上,改进了重复加载依赖文件的问题。

当前常用的微件化的形式有:

  • 分包构建出来的独立代码,例如webpack构建出来的chunk文件。
  • 使用DSL方式编写出来的组件。

5)前端容器化(iframe)

此部分是所有前端的最为熟悉的技术。iframe的年龄已经超越很多前端开发者。这项,"较老"的普通技术,在今天还是比较管用的。

可以快速的将一个网站,嵌入到另外一个网站中。其中,所有的资源都是相互隔离的。如果不写父子的通信,两边的代码是不会互相影响的(但是加载还是会受影响)。

我们前端微服务化中,最难实现的沙箱环境,iframe已经都帮我们实现,相当于创建了一个全新独立的宿主环境。

有这么一个说法,如果不考虑体验,iframe是最好的微前端方案。当其他方案不靠谱或者不兼容的时候,使用iframe是成本最低,且风险最小的方案。

但也是因为他太独立,应用之间,加载机制,通信机制都是需要突破的地方。所以,在采用该方案的时候,需要认真考虑:

  • 不同应用的管理机制,如何加载卸载对应的应用问题。
  • 应用之间的通信机制。这里所有的通信,使用postMessage可能会使程序十分复杂。还需要了解iframeE1.contentWindow等知识点

下述,灵魂拷问模块,为什么不实用iframe,可以帮助区分iframe与其他方案的区别。

6)应用组件化(Web Components)

先一起看看developer.mozilla.org/zh-CN/docs/… 介绍的Web Components:

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。

Web Components旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。

  • Custom elements(自定义元素): 一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
  • Shadow DOM(影子DOM) :一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML templates(HTML模板):  <template> 和 <slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

Web Components笔者当前只写个demo,还未体验到真正的优势。他也是脱离了技术栈,是未来组件的重要发展发现。但是具论坛介绍,当前兼容性还比较一般,只支持谷歌等浏览器。市场也暂未普及。


7)总结

以上,就是微前端具体的实践方案。他们各有春秋,而且各有各使用的场景。现实中,我们用到的微前端框架,就是他们之间的结合。

这里,借助一下网友的图例,对比各个方案之间的差异:

image.png

图来自:www.pianshen.com/article/117…

浅谈微前端的具体技术实现方案

--了解实现的痛点

在研究微前端的技术如何实现之前,我们先要了解,微前端的痛点在哪。

个人的观点,搭建一个微前端的痛点:

  • 如何识别定位路由项目
  • 如何统一不同应用的生命周期等
  • JS与样式,不同的项目如何隔离
  • 父子应用通信,兄弟应用如何进行
  • 如何构建全局状态器
  • 公共资源如何复用

那我们我们围绕着这些技术痛点,一起看看具体有哪些解决方案。

1)如何识别不同项目的路由

如果一次性加载所有的项目的路由,用户在浏览器的成本是十分巨大的。

1)首先假设每个子应用的功能独立,那么需要子应用的所有路由的话,那么首先要完成子项目的实例初始化。如果子项目要十来个,这恐怕加载完这十几个子应用的资源与初始化,浏览器都"扛"不住了,即使能"扛"住,用户也没办法耐性等待。

2)再假设他是由一个大的项目配置,或者全部在主项目的模式进行遍历。大的项目,上千个路由也是家常便饭。更关键的是,由一个大的项目进行管理,一方面难于跟子应用解耦开,另一方面也增加了程序的复杂度。

那如何处理?这里的社区给的方案十分友好:

每个子应用定义一个路由前缀,先遍历路由前缀识别项目,加载子应用后再进行子应用的内部路由解析。

那么这个问题也会变得十分简单,我们先去去劫持一下路由系统,在进入主路由之前,多一套动态路由方案就可以解决这个问题。

看看图例:

image.png

2)如何统一不同应用的生命周期

每个框架的生命周期都不一致。那么如何统一?

只能由主框架定位一个规范,或者叫协议,去同步各个框架。以代表框架Single-SPA为例,bootstrap 、mount 和 unmount 三个生命周期钩子。

其中跟踪源码就可以查询到他们的实现思路:

如果你是一个 React 技术栈的子应用, mount 将会对应 ReactDOM.render。 如果你是一个 Vue 技术栈的子应用, mount 将会对应 mounted

所以我们在使用第三方微前端框架时,还要提前确定,子应用对应的框架,该框架支不支持的问题。类似vite,当前还是有一些框架不兼容,或者不支持。

3)如何隔离JS

翻译一下,就那是如何搭建一个JS的沙箱环境。用人话讲,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响。

这涉及一个沙箱环境的问题,来保证JS不会给外部影响。

直接介绍个人了解到的解决方案:

  • iframe
  • diff
  • proxy

此部分参考: blog.csdn.net/nhheajzhbjk…

  • iframe 自带独立沙箱环境,这里的方案是不用iframe,故不在分析iframe。

  • diff 有点类似diff算法,只不过参照物是window。在每个子应用中,创建一个windowSnapshot(快照),在操作对应的快照windows对象时,都记录在创建一个windowSnapshot中。

在唤起子应用时,操作的值同时记录创建一个windowSnapshot对象,并操作windows。

而在失去子应用时,保存windowSnapshot对象,还原并操作windows。这样使其起到沙箱的效果。

如果还是不明白,直接看源码:

class WindowSnapshot {

  constructor(name) {
    this.name = name;
    this.windowSnapshot = {}; // 记录进入页面时候的window中diff的对象
    this.modifyMap = {}; // 离开时临时缓存windowSnapshot的对象
  }

  active() {
    this.windowSnapshot = {};// 缓存active状态的沙箱
    for (const item in window) {
      this.windowSnapshot[item] = window[item];
    }
    Object.keys(this.modifyMap).forEach(p => {
      window[p] = this.modifyMap[p];
    })
  }

  inactive() {
    for (const item in window) {
      if (this.windowSnapshot[item] !== window[item]) {
        // 记录变更
        this.modifyMap[item] = window[item];
        // 还原window
        window[item] = this.windowSnapshot[item];
      }
    }
  }
}

const windowSnapshot = new WindowSnapshot('应用A');
windowSnapshot.active();  // 激活沙箱
window.remark = '赋值成功'
console.log('开启沙箱,备注为:',window.remark);
windowSnapshot.inactive(); //失活沙箱
console.log('离开沙箱,备注为:', window.remark);
windowSnapshot.active();   // 重新激活
console.log('再次激活,备注为', window.remark);

此时的日志为:

开启沙箱,备注为:赋值成功
离开沙箱,备注为:undfined
再次激活,备注为:赋值成功

此时,已经完成沙箱的功能。

  • proxy

借助 ES6 的 proxy 就是代理。通过劫持 window ,我们可以劫持到子应用对全局环境的一些修改。

直接看源码:

// 修改window属性的公共方法
const updateWindowProp = (prop, value, isDel) => {
    if (value === undefined || isDel) {
        delete window[prop];
    } else {
        window[prop] = value;
    }
}

class ProxySandbox {

    active() {
        // 根据记录还原沙箱
        this.currentUpdatedPropsValueMap.forEach((v, p) => updateWindowProp(p, v));

    }

    inactive() {
        // 1 将沙箱期间修改的属性还原为原先的属性
        this.modifiedPropsMap.forEach((v, p) => updateWindowProp(p, v));
        // 2 将沙箱期间新增的全局变量消除
        this.addedPropsMap.forEach((_, p) => updateWindowProp(p, undefined, true));

    }

    constructor(name) {
        this.name = name;
        this.proxy = null;
        // 存放新增的全局变量
        this.addedPropsMap  = new Map();
        // 存放沙箱期间更新的全局变量
        this.modifiedPropsMap = new Map();
        // 存在新增和修改的全局变量,在沙箱激活的时候使用
        this.currentUpdatedPropsValueMap = new Map();
        const { addedPropsMap, currentUpdatedPropsValueMap, modifiedPropsMap } = this;
        const fakeWindow = Object.create(null);
        const proxy = new Proxy(fakeWindow, {
            set(target, prop, value) {
                if (!window.hasOwnProperty(prop)) {
                    // 如果window上没有的属性,记录到新增属性里
                    // debugger;
                    addedPropsMap.set(prop, value);
                } else if (!modifiedPropsMap.has(prop)) {
                    // 如果当前window对象有该属性,且未更新过,则记录该属性在window上的初始值
                    const originalValue = window[prop];
                    modifiedPropsMap.set(prop, originalValue);
                }
                // 记录修改属性以及修改后的值
                currentUpdatedPropsValueMap.set(prop, value);
                // 设置值到全局window上
                updateWindowProp(prop, value);
                return true;

            },
            get(target, prop) {
                return window[prop];

            },
        });
        this.proxy = proxy;
    }
}

const newSandBox = new ProxySandbox('代理沙箱');
const proxyWindow = newSandBox.proxy;
proxyWindow.remark = '1'
console.log('开启沙箱:', proxyWindow.remark, window.remark);
newSandBox.inactive(); //失活沙箱
console.log('失活沙箱:', proxyWindow.remark, window.remark);
newSandBox.active(); //失活沙箱
console.log('重新激活沙箱:', proxyWindow.remark, window.remark);

4)如何隔离样式

避免不同的应用之间的样式互相影响的问题。如何隔离不同应用之间的样式,也是一个需要思考的问题。

这里不考虑"统一规范"的情况,如果不同的子应用统一了规范,使用bem等标准,甚至压根就不会重命名,那么从根源就无需解决。这里只讨论每个子应用都有只考虑了自己子应用的情况,如何不受其他应用影响。

这里介绍一下几个相对成熟的解决方案:

  • css module(推荐)
  • css-in-js
  • ShadowDOM
  • css module 该方案,spa框架的朋友们都相对熟悉。利用打包的时候,每个应用不同的选择器名称。只要子应用在选择器的子元素中,就只有当前子应用的样式会生效。其他的子应用都匹配不上。

  • css-in-js 写过react的小伙伴,会看到这样的写法:

    <h1 style={style}>
         Hello, world!
      </h1>
    

这是一种,将应用的CSS样式写在JavaScript文件里面的写法,是react内置的一种转换方法,如fontSize会转换成font-size。在今天css-in-js已经有很多种方案,比较有代表性的,是polished.js。有兴趣的小伙伴可以深入一下。

  • ShadowDOM

    再来看看ShadowDOM。ShadowDOM是写组件的神器,最大的用处应该是隔离外部环境用于封装组件。 来自www.cnblogs.com/coco1s/p/57… 的介绍: Shadow-dom 是游离在 DOM 树之外的节点树,但是他的创建基于普通 DOM 元素(非 document),并且创建后的 Shadow-dom 节点可以从界面上直观的看到。更重要的是,Shadow-dom 具有良好的密封性。 弊端就是,该方案还有一定兼容性的问题。而且,有一些框架的节点是往全局document中,如弹窗。这样的ShadowDOM就会失效。

5)应用之间如何通信

直接看解决方案:

  • url传参
  • 建立全局发布订阅模式
  • 通过props传递

6)如何构建全局状态器

其实解决了上边问题"应用之间如何通信", 该问题已经有了解决方案。模拟vuex或者react-redux写一个状态中央管理器,剩下的就是通知到个个子应用的通信问题了。子应用相信框架也有自己的状态管理器。那么此时就已经构成了一个中央管理器。

解答微前端的疑问

1)搭建一个微前端框架主要需要解决什么问题?

借鉴一下来自曾经qianku官方github的一张图例:

image.png

从图例可以看出,一个微前端框架,需要帮忙处理:

  • 1.JS沙箱环境,让子应用不互相影响
  • 2.CSS隔离,让子应用样式不受其他影响。
  • 3.不同应用之间的通信。
  • 4.考虑应用的并行,嵌套。
  • 5.如何跟踪所有项目的生命周期等。
  • 图例其他等等

具体做了啥,还得源码分析,对源码的有兴趣,可参考:


2)微前端的搭建方式有哪些?

微前端的构建方式,大致分类上可分为两种:

  • 1.基座模式

这种模式,基座模式基本承担了微前端的应用基础与核心技术,是由一个主应用,跟一系列子应用构建成的系统。并且由该主应用来管理其他子应用,包括从生命周期到通信机制,一系列都需要经过主应用发起。

现在基本上很多第三方框架,都是基于该模式建立而成。

  • 2.自组织模式

各自拥有小的"基座",系统内部的各个子系统之间自行按照某种规则形成一定的结构或功能。该方案的设计,远比基座更复杂。


3)微前端的对框架的支持?

不同的第三方微前端框架,对常规的框架支持均有所不通。但基本能确定的,vue-cli 3.0与umi等常规框架,都是支持的。此外,普通html与jsp等页面,也均都支持。

类型vite,当前可能需要借助非官方等工具包。此外seo框架笔者还暂为验证,看论坛也是可行的。

4)微前端的弊端是啥?

个人观点:

  • 1.提高技术风险。微服务就一直给人质疑,不正确的隔离,会使带来的问题,大于收益。同理微前端也存在该问题。

  • 2.技术成本变高。一个问题的跟踪,可能需要对应的人员,懂微前端,同时懂主应用与子应用,每一个框架。有时候需要同时深入vue与react与微前端才能解决问题。

  • 3.程序复杂化。开始嵌入时轻松,后续维护缺麻烦。可能背水一战时,随便写了一个框架;后续来了"专家",又退出二次改造框架。然而不想受第二个框架的限制,又推出了第三个框架。使应用的关系越来越复杂。

5)为什么不使用iframe?

上述曾经提到:

如果不考虑体验,iframe是最好的微前端方案。当其他方案不靠谱或者不兼容的时候,使用iframe是成本最低,且风险最小的方案。

但是,如果有更好的解决方案,我们是需要尝试替换的,毕竟,iframe的弊端是十分明显的。

大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。毕竟,这是用户体验是上帝的时代。

几乎每个微前端框架,都会提到,Why Not Iframe?

例如qiankun: www.yuque.com/kuitos/gky7…

下边笔者汇总以下了解到微前端不适合只使用iframe的场景:

  • 1.浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  • 2.DOM 结构不共享。想象一个页面,由不同的iframe组成,此时页面的弹窗要求居中,那只能在对应的iframe,无法针对整个浏览器居中。
  • 3.没有状态管理器的概念。内存变量不共享。需要实现数据的传输,只能通过cookies等,还需要刷新所有的iframe才有最终效果。
  • 4.所有的资源,都需要重新加载。会新增很多请求。
  • 5.iframe嵌入的信息,无法完成SEO的抓取。
  • 6.多个iframe,每个iframe超出时,都会出现各自的滚动条,让人一脸懵逼。
  • 7.等待交互时,无法添加动效,只能白屏等待。
  • 8.iframe 会造成页面阻塞加载,无法异步处理。 其他

结语

1)相关文章推荐

2)实战展望

正在搭建qiankun实战案例,目前刚搭建了简单的交互,还未完善与深入。

后续,子应用将包括vue2.0 , vue3.0 , vite , umi等框架。还包括定制全局状态管理器,全局keep-active控制等。

后续将发布文章分享整个搭建过程,敬请期待。

项目地址:github.com/zhuangweizh…