【微前端】在造一个微前端轮子之前,你需要知道这些~

6,056 阅读46分钟

本文正在参加「金石计划」

flag:每月至少产出三篇高质量文章~

欢迎关注我的另外几篇文章:

1、什么是微前端

好的前端开发很难。扩展前端开发以便许多团队可以同时在大型复杂产品上工作更加困难。

什么是微前端? Dan 大神是这么说的:😭😭😭😭😭😭😭😭😭😭

image.png

Micro Frontends一词于 2016 年底首次出现在ThoughtWorks Technology Radar中:

We've seen significant benefit from introducing microservice architectures, which have allowed teams to scale delivery of independently deployed and maintained services. However, teams have often struggled to avoid the creation of front-end monoliths—large and sprawling browser applications that are as difficult to maintain and evolve as the monolithic server-side applications we've abandoned. We're seeing an approach emerge that our teams call micro frontends. In this approach, a web application is broken up by its pages and features, with each feature being owned end-to-end by a single team. Multiple techniques exist to bring the application features—some old and some new—together as a cohesive user experience, but the goal remains to allow each feature to be developed, tested and deployed independently from others. The BFF - backend for frontends approach works well here, with each team developing a BFF to support its set of application features.

我们已经看到引入微服务架构的显着好处,它允许团队扩展独立部署和维护的服务的交付。然而,团队经常努力避免创建前端单体——庞大而庞大的浏览器应用程序,它们与我们放弃的单体服务器端应用程序一样难以维护和发展。我们看到一种方法出现了,我们的团队称之为微前端. 在这种方法中,Web 应用程序被其页面和功能分解,每个功能都由一个团队端到端拥有。存在多种技术来将应用程序功能(一些旧的和一些新的)结合在一起作为一种有凝聚力的用户体验,但目标仍然是允许每个功能独立于其他功能进行开发、测试和部署。BFF - 前端方法的后端在这里很有效,每个团队开发一个 BFF 来支持其应用程序功能集。

—— 2016年11月 from ThoughtWorks Technology Radar

1.1 微前端简介

近年来,微服务大受欢迎,许多组织使用这种架构风格来避免大型单体后端的局限性。虽然关于这种构建服务器端软件的风格已经写了很多,但许多公司仍在与单一的前端代码库作斗争。

现代的前端应用的发展趋势正在变得越来越富功能化,富交互化。复杂的单体前端应用背后则是数量庞大的后端应用组成的微服务集群。在一个团队中维护的前端项目,随着时间推进,会变得越来越庞大,越来越难以维护。所以我们给这种应用起名为巨石单体应用。

也许你想构建一个渐进式或响应式 Web 应用程序,但找不到一个容易的地方开始将这些功能集成到现有代码中。也许你想开始使用新的 JavaScript 语言功能(或可以编译为 JavaScript 的无数语言之一),但你无法将必要的构建工具安装到现有的构建过程中。或者,也许你只是想扩展你的开发,以便多个团队可以同时在一个产品上工作,但现有单体中的耦合和复杂性意味着每个人都在踩着彼此的脚趾。这些都是真正的问题,都会对你有效地为客户提供高质量体验的能力产生负面影响。

最近几年,我们看到越来越多的注意力集中在复杂的现代 Web 开发所必需的整体架构和组织结构上。特别是,我们看到出现了将前端整体分解为更小、更简单的块的模式,这些块可以独立开发、测试和部署,同时仍然作为一个单一的内聚产品出现在客户面前。我们将这种技术称为 微前端,我们将其定义为:

“一种将可独立交付的前端应用程序组合成一个更大整体的架构风格”

它将微服务的概念扩展到前端世界。当前的趋势是构建一个功能丰富且功能强大的浏览器应用程序,也就是位于微服务架构之上的单页应用程序。随着时间的推移,通常由独立团队开发的前端层会增长并变得更难维护。这就是我们所说的Frontend Monolith

image.png

然而,这个想法并不新鲜。它与独立系统概念有很多共同点。在过去,这样的方法被称为垂直化系统的前端集成。但微前端显然是一个更友好、更简洁的术语。

1.1.1 微服务 + 单体前端 

image.png

  • Monolith 有一个团队来创建和维护一个完整的应用程序,共享数据库、后端和前端。
  • Front & Back 将整体式应用程序一分为二,也将工作团队划分为拥有其整体式后端或前端。

借助微服务,后端架构演变为更具可扩展性的架构,因为每个微服务都属于不同的工作团队。虽然后端的划分对用户是透明的,但是当试图将微服务集成到单体前端时就会出现问题,成为应用程序的瓶颈,单体系统的缺点是:

  • 太大太复杂,任何人都无法完全理解并快速正确地进行更改。
  • 前端代码的更改可能会影响整个网站。
  • 前端代码的任何修改都必须重新实现,增加了编译时间。

1.1.2 微前端

image.png

直到出现了垂直组织,通过微前端,使得架构变得不那么复杂,微前端将应用程序分成小的独立功能,每个功能由一个工作团队从后端到前端同时实现。

由于单体前端存在的问题,这种模式变得流行起来。随着前端的迅速发展,继续采用单体架构,维护起来变得更愈发困难。使用微前端,可以保证与后端微服务架构相同的可扩展性、灵活性和适应性。创建的应用程序不那么繁琐并且更加用户友好。而且,每个微前端都可以使用不同的框架进行开发。

1.1.3 微前端的核心思想

微前端是一种前端架构模式,是一种将前端应用程序拆分为更小、更独立的部分,并使用不同的技术栈来构建和部署这些部分的方法。这些独立的部分可以是单个页面、组件、功能模块或应用程序。这种方法的目的是使团队能够更轻松地开发、测试、部署和维护前端应用程序,每个部分都可以使用不同的技术栈和独立的团队进行开发,这样可以更好地满足团队的需求和能力,同时还能够实现更高的可扩展性和灵活性。

image.png

微前端的出现背景主要有以下的几点:

  1. 应用程序复杂性的增加:在过去,前端应用程序通常是一个单一的代码库,由一个开发团队维护。随着应用程序的复杂性增加,开发和维护单一的代码库变得越来越困难。微前端可以帮助团队将应用程序拆分为更小、更易于管理的部分,从而降低应用程序复杂性。

  2. 技术栈多样性:前端技术栈在不断发展和演变,有时会出现多种不同的技术栈。这可能会导致团队在选择技术栈时出现争议。微前端可以允许团队使用不同的技术栈来构建和部署应用程序的不同部分,从而解决这个问题。

  3. 团队规模的增加:随着团队规模的增加,单一的代码库可能会变得不够灵活和可扩展。微前端可以帮助团队将应用程序拆分为更小、更容易管理的部分,并允许不同的团队在不同的部分上工作,从而提高整个团队的效率。

  4. 高可用性和容错性:微前端可以帮助团队将应用程序拆分为更小、更容易管理的部分,从而降低应用程序崩溃的风险。当一个部分出现问题时,其他部分可以继续运行,从而提高应用程序的可用性和容错性。

简单来说:微前端 是一种架构风格,将独立交付的前端应用程序组合成一个更大的整体。保证产品体验的同时提升开发体验。

微前端的核心思想主要有:

  • 技术独立::每个工作团队都可以自由采用其选择的技术堆栈,而无需与其他团队协调。

  • 每个团队的应用程序代码都是隔离的::不同工作团队之间不共享任何运行时,即使他们使用相同的框架。因此,应用程序中没有全局变量或共享状态。

  • 健壮的网页设计::应用程序功能必须可用。“通用渲染”和“渐进增强”方法可用于提高性能。

  • 与技术无关:每个团队都应该能够选择和升级他们的堆栈,而无需与其他团队协调。自定义元素是隐藏实现细节同时为其他人提供中性界面的好方法。

  • 隔离团队代码:不要共享运行时,即使所有团队都使用相同的框架。构建自包含的独立应用程序。不要依赖共享状态或全局变量。

  • 建立团队前缀:就尚无法隔离的命名约定达成一致。命名空间 CSS、事件、本地存储和 Cookie,以避免冲突并明确所有权。

  • 比自定义 API 更喜欢本机浏览器功能:使用浏览器事件进行通信,而不是构建全球 PubSub 系统。如果您真的必须构建跨团队 API,请尽量保持简单。

  • 构建一个弹性站点:你的功能应该很有用,即使 JavaScript 代码执行失败或尚未执行。使用通用渲染和渐进增强来提高感知性能。

1.2 微前端的优势

1.2.1 粒度较小

  • 业务级或更小的粒度,一个业务团队可以独立管理自己的业务代码,更容易维护,同时方便独立建立 CI/CD 流水线,独立部署。
  • 而不是像单体应用是一个庞大的代码库。

image.png

1.2.2 解耦

  • 业务之间低耦合,互不影响,方便升级,更新,重构/重写,尝试不同的技术。
  • 业务内部功能高内聚,业务纵向分离,独立自治。
  • 而不是像单体应用那样各个业务放在一起,大家用统一的技术栈,共用一个开发部署流程。

image.png

1.2.3 灵活、可扩展和可维护

  • 以前两点为前提,微前端架构更容易增加新的业务或扩展已有的业务,方便多个把业务的旧代码慢慢转换成微前端。
  • 灵活性:微前端可以将前端应用程序拆分为更小、更易于管理的部分,使得团队可以更加灵活地开发、测试、部署和维护前端应用程序。
  • 可扩展性:微前端可以帮助团队将前端应用程序拆分为多个部分,每个部分可以独立地扩展,从而使应用程序具有更高的可扩展性。
  • 可维护性:微前端可以将前端应用程序拆分为更小、更易于管理的部分,使得团队可以更加容易地维护前端应用程序。
  • 技术栈多样性:微前端可以允许团队使用不同的技术栈来构建和部署应用程序的不同部分,从而使得团队可以选择最适合自己的技术栈,提高开发效率和可用性。
  • 高可用性和容错性:微前端可以降低应用程序崩溃的风险。当一个部分出现问题时,其他部分可以继续运行,从而提高应用程序的可用性和容错性。

1.3 微前端带来的问题

  1. 每个微前端单独打包会导致一些重复的依赖包,比如多个微前端使用了 React,那么每个包都有重复的 React,而且不方便把他们提取出来,因为每个微前端可能需要不同版本的依赖包。更大的 bundle size 导致用户需要下载的资源变大,对应用的性能和用户体验有不好的影响。

  2. 微服务和微前端都会无法避免的增加复杂度,有更多的代码仓库、工具、流水线和服务器需要管理。所以在选择微前端架构之前要考虑好有没有足够的自动化流程和基础设施支持,有没有扩展他们的能力,有没有保证各微前端质量和一致性方案。

1.4 微前端的使用场景

  1. 大型单页应用程序:在一个单一的代码库中开发和维护一个大型单页应用程序可能会变得非常复杂,使用微前端可以将应用程序拆分为更小、更易于管理的部分,从而提高可维护性和可扩展性。

  2. 复杂的应用程序:当应用程序变得越来越复杂时,使用微前端可以降低应用程序的复杂性,并提高开发效率和可用性。

  3. 多个团队合作开发:当多个团队共同开发一个应用程序时,使用微前端可以将应用程序拆分为多个部分,每个团队可以独立地开发和部署自己的部分,从而提高整个团队的效率。

  4. 多个技术栈的应用程序:当应用程序需要使用不同的技术栈时,使用微前端可以让团队选择最适合自己的技术栈,提高开发效率和可用性。

总的来说,微前端适用于各种规模和类型的前端应用程序,特别是大型、复杂的应用程序和多人合作开发的应用程序。

2、几个重要的概念

集成微前端的难点主要有三个地方:CSS 隔离, JS 隔离,和通信。

2.1 应用隔离

应用隔离主要分两种情况:

  1. 主应用与子应用之间的隔离;
  2. 子应用与子应用之间的隔离;

应用间所隔离的主要是 Javascript 的沙箱隔离 和 CSS 的样式隔离

2.1.1 Javascript 沙箱隔离

在微前端的场景,由于多个独立的应用被组织到了一起,在没有类似 iframe 的原生隔离下,势必会出现冲突,如全局变量冲突、样式冲突,这些冲突可能会导致应用样式异常,甚至功能不可用。通常,子应用在运行期间会有一些污染性的副作用产生,比如全局变量、全局事件、定时器、网络请求、localStorage全局 Style 样式全局 DOM 元素等。

所以想让微前端达到生产可用的程度,保证应用能够稳定的运行且互不影响,需要提供安全的运行环境,能够有效地隔离、收集、清除应用在运行期间所产生的副作用,让每个子应用之间达到一定程度隔离的沙箱机制是必不可少的,这就是沙箱的设计目标。

JS隔离的方式主要有两种,一种是快照拷贝的方式,一个是基于 proxy 的方式。qiankun 优先使用 proxy 沙盒,如果浏览器不支持 proxy,那么再使用快照沙盒。

1. 快照沙箱 - snapshotSandbox

在创建微应用的时候会实例化一个沙盒对象,它有两个方法,active 是在激活微应用的时候执行,而 inactive 是在离开微应用的时候执行。

整体的思路是在激活微应用时将当前的 window对象 拷贝存起来,然后从 modifyPropsMap 中恢复这个微应用上次修改的属性到 window 中。在离开微应用时会与原有的 window对象 做对比,将有修改的属性保存起来,以便再次进入这个微应用时进行数据恢复,然后把有修改的属性值恢复到以前的状态。

2. 代理沙箱 - proxySandbox

微应用中的 script 内容都会加 with(global) 来执行,这里 global 是全局对象,如果是proxy的隔离方式那么他就是下面新创建的proxy对象。

我们知道 with 可以改变里面代码的作用域,也就是我们的微应用全局对象会变成下面的这个 proxy。当设置属性的时候会设置到 proxy对象 里,在读取属性时先从 proxy 里找,没找到再从原始的 window 中找。也就是你在微应用里修改全局对象的属性时不会在 window 中修改,而是在 proxy对象 中修改。因为不会破坏 window对象,这样就会隔离各个应用之间的数据影响。

详细的可看:微前端框架Qiankun 沙箱原理 - Origin of Ray

2.1.2 CSS 样式隔离

由于在微前端场景下,不同技术栈的子应用会被集成到同一个运行池中,所以我们必须在框架层确保各个子主应用之间不会出现样式互相干扰的问题。

1. 常见的解决方案

  • 严格的命名约定,例如 BEM
  • CSS Module
  • 各种 CSS-in-JS 库;
  • shadow DOM

微前端的样式问题也可以通过团队间商量好 CSS 命名规则约定,然后结合使用 CSS 预处理器比如 SASS,其中 selector nesting 功能可以借用来当作 CSS namespace。这样的坏处是难以把已有的大型单体应用中的业务模块慢慢转化成微前端,因为它们比较难以进行大幅度修改。

CSS in JS 是一个比较好的解决方法,因为样式和它对应的组件绑在一起,修改一个组件的样式不会影响其他任何地方,如果删除这个微前端(比如界面上切换了菜单/页面),微前端的 CSS 也会跟着被删除,所以不用担心样式冲突了。

2. qiankun 样式隔离方案

css文件 隔离上,qiankun 提供了两种样式隔离的功能:严格样式隔离和 scoped 样式隔离。

  • 严格样式隔离

    • 严格样式隔离,默认情况下是关闭的。如果需要开启,必须显示配置。
    • 严格样式隔离,是基于 Web Component 的 shadow Dom 实现的。通过 shadow Dom, 我们可以将一个隐藏的、独立的 dom 附加到一个另一个 dom 元素上,保证元素的私有化,不用担心与文档的其他部分发生冲突。
  • scoped 样式隔离

    • scoped 样式隔离,是基于属性选择器实现的,类似 div["data-qiankun=react"]

html entry 解析以后的 html 模板字符串,再添加到 container 指定的节点之前,会先包裹一层 div,并且为这个 div 节点添加 data-qian 属性,属性值为子应用的 name 属性;然后遍历 html 模板字符串中所有的 style 节点,依次为内部样式表中的样式添加 div["data-qiankun=xxx"] 前缀。qiankun 中子应用的 name 属性值是唯一的,这样通过属性选择器的限制,就可实现样式隔离。

严格样式隔离和 scoped 样式隔离不能同时使用,当两者对应的配置项都为 true 时,严格样式隔离的优先级更高。

2.2 应用通信

微前端最常见的问题之一是如何让微应用之间能够相互通信。虽然组成宏应用程序的微应用程序根据定义是松散耦合的,但它们仍然需要能够相互通信。例如,一个导航微应用需要发出一个通知,通知用户刚刚选择的某个其他微应用应该被激活,而要被激活的应用需要接收这样的通知。

根据我们的极简主义思维方式,我们希望避免引入大量消息传递机制。相反,本着 Web 组件的精神,我们将使用 DOM 事件。我们提供了一个简单的广播 API,它预先通知所有存根即将发生的事件,等待任何已请求激活的事件类型被激活,然后针对文档分派事件,任何微应用程序都可以在文档上监听它。鉴于我们所有的iframes 都是同源的,我们可以从页面到达iframe页面,反之亦然,以找到触发事件的元素。

一般而言,我们建议让微应用之间尽可能少地通信,因为这通常会重新引入我们最初试图避免的那种不适当的耦合代码。也就是说,通常我们只需要某种程度的跨应用通信即可。通常使用浏览器自带的 events 比如 clickselectmouseover 实现通信,如果有更多的需求可以使用 Custom event ,这样直接使用浏览器的 API 可以让微前端架构保持解耦。如果不使用浏览器的API,我们也可以写一个 JS 库实现全局的事件驱动式的通信管理功能,但会加入一定程度的耦合。

2.2.1 常见的通信方式

  • 使用自定义事件通信,是降低耦合的一种好方法;
  • 可以考虑 React 或 Vue 应用中常见的全局 state store 机制;
  • 发布-订阅(pub/sub)模式的通信机制;
  • 使用 地址栏作为通信机制;

2.2.2 qiankun - Actions 通信

qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是:

  • setGlobalState:设置 globalState
    • 设置新的值时,内部将执行浅检查,如果检查到 globalState 发生改变则触发通知,通知到所有的观察者函数。
  • onGlobalStateChange:注册观察者函数
    • 响应 globalState 变化,在 globalState 发生改变时触发该观察者函数。
  • offGlobalStateChange:取消观察者函数
    • 该实例不再响应 globalState 变化。

Actions 通信方案是通过全局状态池和观察者函数进行应用间通信,该通信方式适合大部分的场景。

2.3 应用路由

在这个时代,我们都期望 SPA 中的 URL 栏代表应用程序的视图状态,因此我们可以剪切、粘贴、邮件、文本和链接到它以直接跳转到应用程序内的页面。然而,在微前端应用程序中,应用程序状态实际上是状态的组合,每个微应用程序一个。我们如何表示和控制它?

解决方案是将每个微应用程序的状态编码到一个单一的复合 URL 中,并使用一个小型的宏应用程序路由器,它知道如何将复合 URL 放在一起并把它分开。不幸的是,这需要在每个微应用程序中使用特定于 Yumcha 的逻辑:从宏应用程序路由器接收消息并更新微应用程序的状态,相反地通知宏应用程序路由器该状态的变化,以便更新复合 URL。例如,可以想象一个YumchaLocationStrategy用于 Angular 的元素,或者一个<YumchaRouter>用于 React 的元素。

表示宏应用状态的复合 URL。 它的查询字符串解码为两个单独的(双重编码)查询字符串,然后将其传递给其 id 被指定为其键的微应用程序。

3、微前端的实现方式

3.1 服务器端模板组合

我们从一个明显的非创新的前端开发方法开始--在服务器上用多个模板或片段来渲染HTML。我们有一个index.html,它包含了任何常见的页面元素,然后使用服务器端的 includes 来插入来自片段 HTML 文件的特定页面内容。

<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Feed me</title>
  </head>
  <body>
    <h1>🍽 Feed me</h1>
    <!--# include file="$PAGE.html" -->
  </body>
</html>

我们使用 Nginx 为这个文件提供服务,通过与被请求的 URL 匹配,配置 $PAGE 变量。

server {
    listen 8080;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;
    ssi on;

    # Redirect / to /browse
    rewrite ^/$ http://localhost:8080/browse redirect;

    # Decide which HTML fragment to insert based on the URL
    location /browse {
      set $PAGE 'browse';
    }
    location /order {
      set $PAGE 'order';
    }
    location /profile {
      set $PAGE 'profile'
    }

    # All locations should render through index.html
    error_page 404 /index.html;
}

这是相当标准的服务器端组合。我们有理由称其为微前端的原因是,我们将代码分割成这样一种方式,即每一块都代表一个独立的领域概念,可以由一个独立的团队交付。这里没有显示的是这些不同的HTML文件是如何在网络服务器上结束的,但假设它们都有自己的部署管道,这使得我们可以在不影响或不考虑任何其他页面的情况下对一个页面进行部署修改。

为了获得更大的独立性,可以有一个单独的服务器来负责渲染和服务每个微型前端,由一个服务器在前面向其他服务器发出请求。通过对响应的仔细缓存,这可以在不影响延迟的情况下完成。

image.png

这个例子说明了微前端不一定是一种新技术,也不一定很复杂。只要我们注意我们的设计决定如何影响我们的代码库和团队的自主性,无论我们的技术栈如何,我们都可以实现许多相同的好处。

3.2 构建时集成

我们有时会看到的一种方法是将每个微前端作为一个包发布,并让容器应用程序将它们全部作为库依赖项包含在内。以下是容器如何package.json查找我们的示例应用程序:

{
   "name" : "@feed-me/container" ,
   "version" : "1.0.0" ,
   "description" : "A_food_delivery_web_app" ,
   "dependencies" : {
     "@feed-me/browse-restaurants “:“^1.2.3”,
     “@feed-me/order-food”:“^4.5.6”,
     “@feed-me/user-profile”:“^7.8.9”
  }
}

起初这似乎是有道理的。它像往常一样生成一个可部署的 Javascript 包,使我们能够从各种应用程序中删除重复的公共依赖项。然而,这种方法意味着我们必须重新编译和发布每一个微前端,以便发布对产品任何单独部分的更改。就像微服务一样,我们已经看到这种 步调一致的发布过程所带来的痛苦,我们强烈建议不要使用这种微前端方法。

解决了将我们的应用程序划分为可以独立开发和测试的离散代码库的所有麻烦之后,让我们不要在发布阶段重新引入所有这些耦合。我们应该找到一种在运行时而不是在构建时集成我们的微前端的方法。

3.3 运行时集成

3.3.1 基于 iframe 的微前端

在浏览器中组合应用程序的最简单方法之一是简陋的 iframe。就其本质而言,iframe 可以轻松地从独立的子页面构建页面。它们还在样式和全局变量方面提供了良好的隔离度,不会相互干扰。

使用 iframe 可以在父页面中加载子页面,并将子页面作为独立的应用运行。在微前端中,可以将不同的子应用封装成 iframe,并使用 JavaScript 通信机制来实现应用之间的数据共享和交互。

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

    <iframe id="micro-frontend-container"></iframe>

    <script type="text/javascript">
      const microFrontendsByRoute = {
        '/': 'https://browse.example.com/index.html',
        '/order-food': 'https://order.example.com/index.html',
        '/user-profile': 'https://profile.example.com/index.html',
      };

      const iframe = document.getElementById('micro-frontend-container');
      iframe.src = microFrontendsByRoute[window.location.pathname];
    </script>
  </body>
</html>

服务器端包含选项一样 ,使用 iframe 构建页面并不是一项新技术,也许看起来也不那么令人兴奋。但是,如果我们重新审视 前面列出的 微前端的主要优势,iframe 最符合要求,只要我们注意我们如何分割应用程序和构建我们的团队。

我们经常看到很多人不愿意选择 iframe。虽然有些不情愿似乎是出于直觉,认为 iframe 有点“讨厌”,但人们有一些很好的理由避免使用它们。上面提到的简单隔离确实会使它们不如其他选项灵活。在应用程序的不同部分之间构建集成可能很困难,因此它们使路由、历史记录和深层链接更加复杂,并且它们对使您的页面完全响应提出了一些额外的挑战。

优点

  • 安全性强:iframe 可以将应用隔离开来,避免不同应用之间的代码和数据相互干扰。
  • 可扩展性强:iframe 可以将应用与服务器进行分离,使应用更容易进行扩展和升级。
  • 兼容性好:iframe 支持所有浏览器,没有兼容性问题。

缺点

  • 跨域通信问题:iframe 中的应用与主页面之间需要通过 JavaScript 通信机制来进行交互,但是这种跨域通信会涉及到一些安全问题,需要特别注意。
  • 性能问题:iframe 中的应用需要单独进行加载和渲染,可能会对应用的性能造成一定的影响。

推荐阅读:

3.3.2 基于模块化加载器的微前端

模块化加载器是一种前端工具,可以帮助管理 JavaScript 模块 的依赖关系和加载顺序。在微前端中,可以使用模块化加载器来加载和运行不同的子应用,并将它们组合成一个整体应用。

可能是最灵活的方法,也是最常采用的方法。每个微前端都使用标签包含在页面上<script>,并在加载时公开一个全局函数作为其入口点。然后容器应用程序确定应该挂载哪个微前端,并调用相关函数来告诉微前端何时何地渲染自己。

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

    <!-- 这些脚本不会立即渲染任何东西,而是将入口函数挂载到window上 -->
    <script src="https://browse.example.com/bundle.js"></script>
    <script src="https://order.example.com/bundle.js"></script>
    <script src="https://profile.example.com/bundle.js"></script>

    <div id="micro-frontend-root"></div>

    <script type="text/javascript">
      // 这些全局函数通过上面的脚本附加到 window
      const microFrontendsByRoute = {
        '/': window.renderBrowseRestaurants,
        '/order-food': window.renderOrderFood,
        '/user-profile': window.renderUserProfile,
      };
      
      const renderFunction = microFrontendsByRoute[window.location.pathname];

      // 确定了入口函数后,我们现在调用它,为其提供应渲染自身的元素的 ID
      renderFunction('micro-frontend-root');
    </script>
  </body>
</html>

以上显然是一个原始的例子,但它展示了基本的技术。与构建时的集成不同,我们可以独立部署每个 bundle.js 文件。而且与 iframes 不同的是,我们可以完全灵活地在我们的微型前端之间建立集成,不管我们怎么想。我们可以以多种方式扩展上述代码,例如,只在需要时下载每个 JavaScript bundle,或者在渲染一个微前端时将数据传入和传出。

这种方法的灵活性,再加上独立的部署性,使它成为我们的默认选择,也是我们最常看到的一种。

优点

  • 灵活性强:模块化加载器可以将不同的子应用进行模块化管理,使得应用更容易进行扩展和维护。
  • 性能较好:模块化加载器可以根据需要进行动态加载和卸载,从而提高了应用的性能和加载速度。

缺点

  • 兼容性问题:模块化加载器需要浏览器支持 ES6 模块化,否则需要使用 polyfill 进行兼容处理。
  • 需要进行配置:模块化加载器需要进行一些配置,可能会增加开发和部署的复杂度。

3.3.3 基于 Web Components 的微前端

前面方法的一个变化是,每个微型前端定义一个HTML自定义元素供容器实例化,而不是定义一个全局函数供容器调用。

Web Components 是一种浏览器提供的标准,可以将页面组件化并进行复用。在微前端中,可以使用 Web Components 来封装页面组件,并在需要的地方动态加载和渲染。

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

    <!-- These scripts don't render anything immediately -->
    <!-- Instead they each define a custom element type -->
    <script src="https://browse.example.com/bundle.js"></script>
    <script src="https://order.example.com/bundle.js"></script>
    <script src="https://profile.example.com/bundle.js"></script>

    <div id="micro-frontend-root"></div>

    <script type="text/javascript">
      // These element types are defined by the above scripts
      const webComponentsByRoute = {
        '/': 'micro-frontend-browse-restaurants',
        '/order-food': 'micro-frontend-order-food',
        '/user-profile': 'micro-frontend-user-profile',
      };
      const webComponentType = webComponentsByRoute[window.location.pathname];

      // Having determined the right web component custom element type,
      // we now create an instance of it and attach it to the document
      const root = document.getElementById('micro-frontend-root');
      const webComponent = document.createElement(webComponentType);
      root.appendChild(webComponent);
    </script>
  </body>
</html>

这里的最终结果与前面的例子很相似,主要区别在于你选择了 "Web Components的方式" 来做事。如果你喜欢网络组件规范,并且喜欢使用浏览器提供的功能的想法,那么这是一个很好的选择。如果你喜欢在容器应用程序和微型前端之间定义你自己的接口,那么你可能更喜欢前面的例子。

优点

  • 独立性强:Web Components 是自包含的,可以将应用组件化并进行复用,从而提高了应用的可维护性和可复用性。
  • 跨框架:Web Components 可以跨越多个前端框架进行使用,因为它们只是一些自定义元素和样式。

缺点

  • 兼容性问题:Web Components 目前还没有得到所有浏览器的支持,需要使用 polyfill 进行兼容处理。
  • 性能问题:Web Components 的初始化和渲染需要消耗较多的时间和内存资源,可能会影响应用的性能。

3.3.4 基于主应用和子应用之间的通信机制的微前端

在微前端中,可以使用 JavaScript 通信机制,如 postMessageCustomEvent 等,来实现主应用和子应用之间的数据共享和交互。可以将不同的子应用封装成 JavaScript 模块,并在主应用中动态加载和运行。

主应用代码:

import { registerApplication, start } from 'single-spa';

registerApplication(
  'sub-app',
  () => import('sub-app'),
  location => location.pathname.startsWith('/sub-app')
);

start();

子应用代码:

import { registerApplication, start } from 'single-spa';

export function bootstrap() {
  // 初始化子应用
}

export function mount() {
  // 渲染子应用
}

export function unmount() {
  // 卸载子应用
}

registerApplication(
  'sub-app',
  () => import('./app.js'),
  () => location.pathname.startsWith('/sub-app')
);

start();

在主应用中,我们使用 registerApplication 函数注册子应用,指定子应用的名称、加载子应用的函数和一个谓词函数,用于确定何时加载子应用。然后,我们调用 start 函数来启动应用程序。

在子应用中,我们首先导出了 bootstrapmountunmount 函数,这些函数分别用于初始化、渲染和卸载子应用。然后,我们使用 registerApplication 函数来注册子应用,指定子应用的名称、加载子应用的函数和一个谓词函数,用于确定何时加载子应用。最后,我们调用 start 函数来启动子应用。

在子应用渲染后,子应用可以通过 single-spa 库提供的全局 window.System.import 函数来加载其他子应用,并通过自定义事件、postMessage 或者共享状态等方式进行通信。

优点

  • 灵活性强:主应用和子应用之间的通信可以非常灵活,可以根据需要进行定制化。
  • 可扩展性强:主应用和子应用之间的通信可以使应用更容易进行扩展和升级。
  • 性能较好:通信机制可以使应用之间的数据传输

缺点

  • 开发成本较高:需要开发者对通信机制有一定的了解,并且需要在主应用和子应用之间进行协调,从而增加了开发成本。
  • 可维护性较差:通信机制的复杂性可能会导致代码难以维护,特别是在应用数量较多的情况下。
  • 安全性问题:通信机制需要进行跨域通信,可能会存在一些安全风险。

3.3.5 基于 Webpack 5 模块联邦实现微前端

Webpack 5 模块联邦是一种新的技术,它允许多个独立的 Webpack 构建之间共享代码。这意味着您可以将应用程序分解为多个微前端,每个微前端都可以独立地开发和部署,并且它们可以在运行时动态加载和共享代码。下面是一个示例微前端应用程序,其中每个微前端都有其自己的 Webpack 构建,并使用模块联邦来共享核心代码。

首先,我们需要使用 Webpack 5ModuleFederationPlugin 插件。这个插件可以让我们将不同的 Webpack 构建之间的模块共享。

我们可以创建一个微前端核心代码的仓库,然后在这个仓库中定义需要共享的模块。

// webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/index.js',
  output: {
    publicPath: 'http://localhost:3001/',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'core',
      filename: 'remoteEntry.js',
      exposes: {
        './Header': './src/components/Header',
        './Footer': './src/components/Footer',
      },
    }),
  ],
};

在这个示例中,我们将模块名设置为“core”,并将组件 HeaderFooter 暴露出来,这样其他微前端应用就可以使用这些组件。

现在,我们可以在其他微前端应用中引入这些组件。

// webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/index.js',
  output: {
    publicPath: 'http://localhost:3002/',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      remotes: {
        core: 'core@http://localhost:3001/remoteEntry.js',
      },
    }),
  ],
};

在这个示例中,我们将远程模块“core”定义为一个远程仓库,并指定了它的 URL。然后我们就可以在应用程序代码中使用这些组件。

// app.js

import React from 'react';
import ReactDOM from 'react-dom';
import Header from 'core/Header';
import Footer from 'core/Footer';

const App = () => (
  <div>
    <Header />
    <h1>Hello, World!</h1>
    <Footer />
  </div>
);

ReactDOM.render(<App />, document.getElementById('root'));

通过这种方式,我们可以使用 Webpack 5 模块联邦技术,将不同的微前端应用程序连接在一起,并共享代码。这可以使我们更容易地构建和维护微前端应用程序,而无需处理复杂的代码复制和管理。

优点

  • 集成化程度高:基于 Webpack 5 模块联邦,可以实现应用的代码共享和复用,避免了代码的冗余,同时也能集成化地管理应用之间的依赖关系和版本控制。
  • 高性能:Webpack 5 模块联邦支持动态加载和卸载,从而提高了应用的性能和加载速度。同时也能够做到在运行时动态加载和更新依赖,降低了应用的耦合度。
  • 可扩展性强:Webpack 5 模块联邦能够实现多个应用之间的共同开发和部署,避免了应用之间的依赖冲突,使得应用更容易进行扩展和升级。
  • 集成多种方案:Webpack 5 模块联邦不仅支持基于 Web Componentsiframe模块化加载器主应用和子应用之间的通信机制等多种微前端实现方案,还可以与其他微服务框架进行集成。

缺点

  • 学习成本较高:使用 Webpack 5 模块联邦实现微前端,需要开发者具备 Webpack 相关的知识和技能,增加了开发的门槛。
  • 对项目架构和部署有一定要求:需要针对项目的架构和部署进行一定的调整和优化,才能更好地使用 Webpack 5 模块联邦。

无论哪种实现路径,微前端都需要考虑一些关键问题,如应用之间的路由、状态管理、数据共享等。

4、微前端框架

4.1 Single-spa

single-spa 是最早的微前端框架,兼容多种前端技术栈。Single SPA 将自己定义为 “前端微服务的 Javascript 框架”。简而言之,它将生命周期应用于每个应用程序。每个应用程序都可以响应 url 路由事件,并且必须知道如何从 DOM 引导、装载和卸载自身。传统 SPA 和单 SPA 应用程序之间的主要区别在于它们必须能够与其他应用程序共存,并且它们没有各自的 HTML 页面。

single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。

single-spa 宣称自己是一个用于前端微服务的 JavaScript 路由器。single-spa 的路由最好的一点是,你不需要使用一个特定的JavaScript库或框架。相反,你可以用它来在同一个页面上运行 Angular、React、Vue 和其他框架进行同步开发,最后使用一个公用的路由去实现完美的切换。

这个工具是你在使用Micro frontand开发时,在每个应用生命周期中需要应用的。

image.png

每个应用程序都必须能够响应URL路由事件,并且必须能够从DOM启动、挂载和卸载。

优点:

  • 敏捷性 - 独立开发、独立部署,微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
  • 技术栈无关,主框架不限制接入应用的技术栈,微应用具备完全自主权;
  • 增量升级,在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 更快交付客户价值,有助于持续集成、持续部署以及持续交付;
  • 维护和 bugfix 非常简单,每个团队都熟悉所维护特定的区域;

缺点:

  • 无通信机制
  • 不支持 Javascript 沙箱
  • 样式冲突
  • 无法预加载

推荐阅读:

4.2 Qiankun

Qiankun 建立在 single-spa 的基础上,提供更多的功能和开箱即用的API,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。它可以和任何 JavaScript 框架一起使用,而且和 single-spa 一样,可以让你在同一个页面上混合使用各种框架。

1_ll1DJAlY7zW23sQN6jKy0Q.gif

当你有一个具有不同技能的开发团队,并希望保持项目简单时,选择这个选项是有意义的。

风格隔离有助于防止多个JS框架之间的冲突。然而,由于混合 JavaScript 框架有可能导致冲突,乾坤给你一个JS沙盒,让你在向用户发布之前测试子应用。

优点:

  • 基于 single-spa 封装,提供了更加开箱即用的 API。
  • 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
  • 样式隔离,确保微应用之间样式互相不干扰。
  • JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
  • 社区较为活跃,维护者也较多,有问题会及时得到响应;

缺点:

  • 可能对一些 jQuery 老项目支持性不是特别好;
  • 安全和性能可能会有影响,具体取决于项目;
  • eval 的争议,eval函数的安全和性能是有一些争议的

4.3 Micro-App

京东推出的 Micro App 是基于 Web Components 原生组件进行渲染的微前端框架。

micro-app之前,业内已经有一些开源的微前端框架,比较流行的有2个:single-spaqiankun

single-spa是通过监听 url change 事件,在路由变化时匹配到渲染的子应用并进行渲染,这个思路也是目前实现微前端的主流方式。同时single-spa要求子应用修改渲染逻辑并暴露出三个方法:bootstrapmountunmount,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。因为qiankun是基于single-spa进行封装,所以这些特点也被qiankun继承下来,并且需要对webpack配置进行一些修改。

micro-app并没有沿袭single-spa的思路,而是借鉴了 WebComponents 的思想,通过 CustomElement 结合自定义的 ShadowDom,将微前端封装成一个类 WebComponents 组件,从而实现微前端的组件化渲染。并且由于自定义 ShadowDom 的隔离特性,micro-app不需要像single-spaqiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改 webpack 配置,是目前市面上接入微前端成本最低的方案。

概念图

image

micro-app的优势

1、使用简单

我们将所有功能都封装到一个类 WebComponent 组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。

同时micro-app还提供了js沙箱样式隔离元素隔离预加载数据通信静态资源补全等一系列完善的功能。

2、零依赖

micro-app没有任何依赖,这赋予它小巧的体积和更高的扩展性。

3、兼容所有框架

为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何技术框架中都可以正常运行。

主要特点:

  • 简单上手
  • 无关技术栈:任何框架皆可使用;
  • 静态资源补全;
  • JS沙箱;
  • 样式隔离;
  • Qiankun 微前端框架的优势他都有;

4.4 无界

腾讯推出的无界,也是一款基于 Web Components + iframe 的微前端框架,具备成本低、速度快、原生隔离、功能强等一系列优点。

无界的优势:

  • 多应用同时激活在线:框架具备同时激活多应用,并保持这些应用路由同步的能力

  • 组件式的使用方式:无需注册,更无需路由适配,在组件内使用,跟随组件装载、卸载

  • 应用级别的 keep-alive:子应用开启保活模式后,应用发生切换时整个子应用的状态可以保存下来不丢失,结合预执行模式可以获得类似ssr的打开体验

  • 纯净无污染

    • 无界利用iframewebcomponent来搭建天然的js隔离沙箱和css隔离沙箱
    • 利用iframehistory和主应用的history在同一个top-level browsing context来搭建天然的路由同步机制
    • 副作用局限在沙箱内部,子应用切换无需任何清理工作,没有额外的切换成本
  • 性能和体积兼具

    • 子应用执行性能和原生一致,子应用实例instance运行在iframewindow上下文中,避免with(proxyWindow){code}这样指定代码执行上下文导致的性能下降,但是多了实例化iframe的一次性的开销,可以通过 preload 提前实例化
    • 体积比较轻量,借助iframewebcomponent来实现沙箱,有效的减小了代码量
  • 开箱即用:不管是样式的兼容、路由的处理、弹窗的处理、热更新的加载,子应用完成接入即可开箱即用无需额外处理,应用接入成本也极低

4.5 Webpack Module Federation

Module FederationZack Jackson 发明的一种 JavaScript 架构,后来他提议为它创建一个 Webpack 插件。Webpack 团队帮助将该插件引入了已经发布的 Webpack 5

模块联邦专注于微型前端,但并不局限于这些框架,从而给开发者带来很大的灵活性。这是处理代码依赖性问题的最有效工具之一,它允许通过依赖性共享来扩大捆绑规模。

例如,你可以使用模块联盟通过分块加载操作来加载远程模块。这个过程让所有的东西都从一次往返服务器的过程中并行加载。

简而言之,Module Federation 允许 JavaScript 应用程序在运行时从另一个应用程序动态导入代码。该模块将构建一个独特的 JavaScript 入口文件,其他应用程序可以通过设置 Webpack 配置来下载该文件。

它还通过启用依赖共享解决了代码依赖和增加包大小的问题。例如,如果您正在下载 React 组件,您的应用程序将不会导入 React 代码两次。该模块将巧妙地使用您已有的 React 源代码,并且只导入组件代码。最后,您可以使用 React.lazyReact.suspense 在导入代码由于某种原因失败时提供回退,确保用户体验不会因为构建失败而中断

其他使模块联盟有用的关键概念包括:

  • 与环境无关的功能,可以在 WebNode.js 上发挥作用。
  • 防止同级别的容器覆盖其模块的控制。
  • 支持 webpack 使用的所有模块类型。

可以通过投票来影响模块联盟团队优先考虑的功能,使其成为一个面向社区的项目,朝着大多数专家认为有益的方向发展。

推荐阅读:

4.6 Medusa

medusa 是一款基于各种框架之上的微前端框架,具有极高的框架兼容性,能够运行在目前主流的几种微前端框架之上。使得乾坤飞冰京东微前端以及next.js能够同时运行在一个环境下。并全部采用proxy的沙箱方案,摒弃老旧浏览器的兼容性问题,完全隔离主应用与微应用。

特性:

  1. 不限制使用的前端框架
  2. 兼容 qiankun、飞冰、京东等微前端框架,可以直接加载,不需要任何改动
  3. 支持服务端渲染模式的直接使用
  4. 支持加载比较流行的服务端渲染框架 next.js
  5. 支持作为独立微前端框架来使用
  6. 以React组件的生命周期作为微应用的生命周期

美杜莎的使用场景十分广泛,大部分的前端框架都能无缝接入:

  • next.js 的应用接入到微前端体系框架中:目前 next.js 服务端渲染框架的应用场景十分广泛。同时也没有一款微前端框架能够把 next.js 框架集成进来的。
  • 框架无关(我们还支持乾坤、飞冰、京东微前端框架):很多时候我们会遇到这样一个问题,公司的部门很多,由于各种原因,不同部门应用了不同的微前端框架。后面想做整合,却发现改造成本较大。美杜莎就解决了这样的一个问题,我们可以保持原先应用保持不变,只替换基座的框架,就能使这几种微前端应用运行在同一个页面上
  • 多层级应用嵌套:理论上只要浏览器撑得住,就可以无限嵌套下去

4.7 Bit

Bit 是一个可用于构建微前端的产品解决方案,在国外比较知名。除了创建的能力,Bit 还提供了通过独立组件管理前台的可能性。

当涉及到确保每个前端得到它自己的中立和快速的构建过程时,Bit 提供了一个特殊的 CI/CD 过程,是完全组件驱动的。这意味着,不同的团队可以自由地纳入变化,没有任何延迟,以创建一个统一的项目。整个工作流程通过简单的解耦代码库、小型定义的 API、独立的发布管道和独立的团队得到加强。

尽管微前端通常被认为是在运行时发生的组合,但Bit 允许开发人员在构建时有效地组合前端以享受两全其美:“传统单体”的安全性和健壮性以及微前端的简单性可扩展性.

推荐阅读:

4.8 SystemJS

SystemJS 实际上并不算是微前端框架,但它确实提供了独立模块的跨浏览器管理解决方案,这是实现 MF 的关键(实际上也被 single-spa 使用)。

SystemJS 可以 转译生产工作流代码,以接近原生的速度运行。运行时测试显示,未缓存的本地模块加载时间为 1668ms,而 SystemJS2334ms

缓存的本地模块加载时间为 49ms,而 SystemJS 的加载时间为 81ms。在这样的速度下,人类用户不会发现其中的差别。

尽管速度快,但 SystemJS 为你提供了大量的功能来执行任务,如重新加载工作流、加载全局脚本和导入地图。

SystemJS 让我们可以使用不同的JS模块相关功能,如导入地图和动态导入,而不需要本地浏览器的支持。

image.png

SystemJS 可以被认为是 JS 模块的编排器。它允许我们使用不同的 JS 模块相关功能,例如动态导入、导入映射等,而不依赖于本机浏览器支持——以及所有这些,具有接近本机的性能。一些值得注意的功能包括适用于旧版浏览器的 Polyfill、使用名称的模块导入(通过将名称映射到路径)以及对多个 JS 模块的单个网络请求(通过使用其 API 将多个模块设置为单个文件)。

SystemJS 的一些突出功能包括:通过将名称映射到路径来促进模块的导入,为老式浏览器提供 Polyfill 的能力,以及利用其API为多个JS模块执行单一的网络查询,在一个文件格式中设置多个模块的能力。

4.9 Piral

Piral 对于那些期待使用微型前端来构建门户应用的人来说是一个伟大的工具。解耦模块被称为 Pilets,它允许你创建一个模块化的前端应用程序。

我们应该知道,一个 Pilets 是单独开发的,并带有微型前端开发所需的属性。

通过将开发生命周期分成两半,Piral能够处理整个过程。

image.png

你通常从应用程序的外壳开始,从模板开始,通过调试、构建和发布进行。然后,在调试、创建和发布之前,在各个模块(Pilets)上工作,照顾到基础设施。

像许多顶级的微型前端框架一样,Piral是开源的,并且是社区驱动的。但与很多开源工具不同的是,你可以为Piral 的支持付费,这将改善你的Web开发工作流程。

你可以得到一个强大的框架,但你也可以在需要时得到专业帮助。

4.10 Luigi

Luigi 是一个模块化的顶级微前端框架,它可以帮助你分解你的单体网络应用,为精简重建做准备。它使一个Web应用能够直接与由其组成的微前端进行通信。

为了使这种通信无痛地运行并避免任何故障或缺陷,可以在路由、导航、用户体验元素和授权方面进行设置配置。

这个JavaScript微前端框架允许你创建一个管理用户界面驱动的本地和分布式视图。

image.png

企业级的框架被小型企业和跨国公司使用。它的不可知技术意味着你永远不会被锁定在一个特定的服务器、供应商或编码语言。

4.11 Open Components

Open Components 项目宣布其目标是“前端世界中的无服务器”。更具体地说,OC 的目标是成为微前端的框架,在一个地方提供你需要的一切,使其成为一个丰富而复杂的系统,包括从组件处理到注册表到模板甚至 CLI 工具的任何工具。OpenComponents 有两个部分:

  • components是主要由 html、javascript、css 组成的同构代码的小单元。它们可以选择包含一些逻辑,允许服务器端 node.js 应用程序组成用于呈现视图的模型。呈现后,它们是纯 html 片段,可以注入任何 html 页面。
  • consumers 是网站或微型网站(小型可独立部署的网站,全部通过前门服务或任何路由机制连接),需要组件来在其网页中呈现部分内容。看看这里:

4.12 FrintJS

FrintJS 是“一个用于构建可扩展和反应式应用程序的模块化 JavaScript 框架”。它允许您加载来自单独的捆绑器的应用程序,为您的应用程序提供一个结构,并处理路由、依赖关系等问题的真实性。该项目还通过附加包支持 RNVue,但它主要针对 React 进行了记录和测试。

在此处 可以试用或访问 GitHub 上的项目以了解更多信息。

5、参考 & 推荐阅读