微前端架构全面解析:什么是微前端及其主流解决方案
👩‍💻

微前端架构全面解析:什么是微前端及其主流解决方案

published_date
最新编辑 2025年02月21日
slug
本文深入解析了微前端架构的概念与原理,详细介绍了微前端如何帮助大型应用进行拆分与管理。文章包括了常见的微前端技术方案如qiankun、single-spa及Web Components的介绍,并重点讨论了这些技术如何实现应用隔离、组件共享和通信。对于前端开发者、架构师及团队管理者,本文提供了全面的微前端架构知识。
tags
Website

微前端是什么?为什么要使用微前端?

微前端就是一个软件架构的设计理念,目的是使大型的应用系统能够更好进行拆分和管理。
传统的前端应用程序往往随着功能的增加而变得庞大臃肿,维护和更新变得困难。
  • 微前端通过将前端应用程序拆分成更小的、独立的部分,每个部分都能够独立开发、部署和维护,从而降低了整体系统的复杂度。
  • 这些独立的部分可以是由不同团队开发的,它们之间可以使用不同的技术栈,也可以独立地进行版本管理和发布。
  • 微前端可以帮助团队更快地开发新功能,降低应用程序的维护成本,并促进团队之间更好的合作。

主流微前端解决方案

qiankun 基于single-spa

single-spa是一个用于构建多个独立前端应用程序并在同一页面上运行它们的js微前端框架,single-spa 提供了一套灵活的 API 和机制,使得各个前端应用程序可以独立开发、部署和维护,同时能够协同工作并共享资源
  • qiankun 则在 single-spa 的基础上进行了封装和扩展,提供了更便捷的微前端开发体验和更强大的功能
  • 提供基座模式,用于注册、承载、启动子应用,子应用可以是单独的项目
  • 提供单实例,多实例,应用通信,应用隔离等

microApp 基于web Components

Web Components 是一种浏览器标准,允许创建可重用的自定义 HTML 元素,这些元素封装了 HTML、CSS 和 JavaScript,并具有良好的封装性和隔离性
microApp 利用了 Web Components 的特性,将前端应用程序拆分成多个独立的微应用,每个微应用都是一个独立的 Web Component
实现步骤
  1. 定义微应用: 将整个前端应用程序拆分成多个独立的微应用。每个微应用可以是一个 Web Component,封装了自己的 HTML、CSS 和 JavaScript 代码,并定义了自己的入口点和功能。
  1. 注册微应用: 在微前端的主应用程序中,注册所有的微应用。这通常涉及指定微应用的入口点、路由信息和其他配置。
  1. 动态加载微应用: 当需要加载某个微应用时,主应用程序可以动态地从远程服务器或本地加载相应的 Web Component,并将其插入到页面中。
  1. 通信与集成: 微应用之间可能需要进行通信和集成,以实现共享数据或共同工作。可以使用诸如 Custom Events、PostMessage 等技术来实现微应用之间的通信。
  1. 独立部署和维护: 每个微应用都可以独立地进行部署和维护,因为它们都是独立的 Web Component。这使得团队可以更灵活地开发和发布新功能,而不会影响其他微应用或整个系统

iframe

single-spa

single-spa原理

基础实现流程

  1. 应用程序注册:在single-spa中,调用`registerApplication`函数实现,注册需要提供子应用的名称、加载函数、路由规则等信息
  1. 路由管理:根据当前的路由状态来加载和激活相应的子应用
  1. 子应用加载:当需要加载某个子应用时,single-spa会调用加载函数来动态加载子应用的代码和资源,加载函数可以是一个异步函数或者返回一个promise,用于异步加载子应用的资源
  1. 生命周期管理
    1. bootstrap(初始化): 在这个阶段,子应用被加载到内存中,但尚未挂载到 DOM 中。这个阶段可以用来初始化子应用,例如加载一些必要的资源或设置一些全局配置。
    2. mount(挂载): 在这个阶段,子应用被挂载到 DOM 中,并开始执行其内部的渲染逻辑。这个阶段是子应用真正开始工作的时候,可以在这里执行一些初始化渲染或绑定事件等操作。
    3. unmount(卸载): 在这个阶段,子应用被从 DOM 中卸载,并停止执行其内部的渲染逻辑。这个阶段可以用来清理子应用的状态或释放资源,以便在下次挂载时重新初始化。
    4. unload(卸载后): 在这个阶段,子应用被完全卸载并从内存中释放。这个阶段是应用程序的生命周期的最后阶段,可以在这里执行一些清理或释放资源的操作。
  1. 共享状态:single-spa 允许不同的子应用之间共享状态。可以使用一些全局状态管理工具(如 Redux、Vuex 等)来管理应用之间共享的状态,或者使用 single-spa 提供的跨应用程序通信机制来实现状态共享。
  1. 错误处理:提供了一些机制来处理应用加载过程中的错误,并提供了一些默认的错误处理行为。也可以自定义错误处理逻辑,以便更好地处理各种错误情况

JS沙箱

沙箱是一种用于隔离 JavaScript 代码的机制,可以在同一个页面中运行多个不同源的 JavaScript 代码,同时确保它们之间不会相互干扰或造成安全漏洞。JavaScript 沙箱通常用于在 Web 应用程序中实现安全的代码执行环境,以防止恶意代码对页面造成破坏或窃取用户信息

功能和特点

  1. 隔离环境: 通过创建一个独立的执行环境来隔离不同的 JavaScript 代码。每个沙箱都拥有自己的全局对象和执行上下文,使得其中的代码无法访问其他沙箱中的变量、函数或对象。
  1. 安全性: 可以帮助确保页面的安全性,防止恶意代码对页面造成破坏或窃取用户信息。通过限制沙箱中的 JavaScript 代码的权限和访问范围,可以有效地防止一些常见的安全漏洞,如 XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)等。
  1. 沙箱配置: 通常允许对沙箱进行配置,以满足不同的安全需求和使用场景。例如,可以配置沙箱的运行时环境、访问控制策略、沙箱间通信机制等。
  1. 资源限制: 可以限制沙箱中 JavaScript 代码的资源使用,包括 CPU、内存、网络等资源。这有助于防止恶意代码占用过多的资源或进行网络攻击。
  1. 错误隔离: 可以帮助隔离不同 JavaScript 代码的错误,使得其中一个沙箱中的错误不会影响到其他沙箱中的代码执行。这有助于提高页面的稳定性和可靠性。

qiankun工作原理

  1. 应用加载:通过动态创建 script 标签的方式加载子应用的入口文件。加载完成后,会执行子应用暴露出的生命周期函数。
  1. 生命周期管理:要求每个子应用都需要暴露出 bootstrap、mount 和 unmount 三个生命周期函数。bootstrap 函数在应用加载时被调用,mount 函数在应用启动时被调用,unmount 函数在应用卸载时被调用。
  1. 沙箱隔离:通过 Proxy 对象创建了一个 JavaScript 沙箱,用于隔离子应用的全局变量,防止子应用之间的全局变量污染。
  1. 样式隔离:通过动态添加和移除样式标签的方式实现了样式隔离。当子应用启动时,会动态添加子应用的样式标签,当子应用卸载时,会移除子应用的样式标签
  1. 通信机制:提供了一个全局的通信机制,允许子应用之间进行通信。

qiankun如何加载子应用

采用动态加载子应用,通过监听主应用的路由变化,根据当前的路由状态来动态加载和卸载子应用,使用import-html-entry
  • 内部采用HTML Entry的方式,通过设置html作为资源入口,加载远程html解析DOM,从而获取js、css等静态资源来实现渲染。
  • 当配置子应用的entry之后,qiankun会通过fetch获取到子应用的html字符串,然后通过正则匹配获取html对应的js、css、注释、入口、脚本entry等等
    • template: HTML 模板
    • script:JS 脚本(内联、外联)
    • styles:css 样式表(内联、外联)
    • entry:子应用入口 js 脚本文件

使用HTML Entry的优点

  1. 无需打包成单个 JS bundle: 在使用 HTML Entry 时,子应用程序不需要被打包成单个 JavaScript bundle。这意味着可以继续使用 CSS 提取、资源并行加载和首屏加载优化等技术,而不必担心打包过程带来的复杂性。
  1. 无需关心子应用的文件名和更新: 使用 HTML Entry,不需要担心子应用程序的 JavaScript 文件名会发生变化,也不需要手动更新打包配置来适应按需加载功能。qiankun 在懒加载 JavaScript 脚本时会自动处理 URL,并且不受文件名变化的影响。
  1. 自动补全 URL: qiankun 在懒加载 JavaScript 脚本时会基于 entry 配置项自动补全 URL。这意味着不需要为按需加载功能特别修改打包配置,qiankun 会处理相关的 URL 自动补全工作

qiankun如何做应用隔离

单实例模式下的沙箱隔离(快照沙箱)

通过记录和保留每个子应用程序加载前的全局状态(比如 window 对象的内容),然后在子应用程序执行期间,拦截和记录对全局状态的任何修改。当需要卸载某个子应用程序时,快照沙箱会恢复原始的全局状态,以确保其他子应用程序或主应用程序不受影响。
实现步骤:
  1. 记录全局状态: 在加载子应用程序之前,记录当前的全局状态,包括 window 对象的内容以及其他全局变量和对象的状态。
  1. 创建沙箱环境: 创建一个代理对象(利用es6的proxy)来代替全局对象,例如创建一个代理 window 对象。这个代理对象将用于拦截对全局对象的访问和修改操作。
  1. 拦截和记录操作: 在代理对象上设置拦截器,用于拦截对全局对象的访问和修改操作。在拦截器中,记录所有的操作,包括属性的读取、写入和删除等。
  1. 执行子应用程序代码: 将子应用程序的 JavaScript 代码加载到沙箱环境中执行。在代码执行期间,所有对全局对象的操作都会被代理对象拦截和记录。
  1. 卸载子应用程序: 当需要卸载子应用程序时,恢复原始的全局状态,包括清理代理对象和拦截器。这样可以确保其他子应用程序或主应用程序不受影响。
利用 ES6 的 Proxy,我们可以做到以下几点:
  1. 拦截属性访问: 我们可以在 Proxy 对象上设置 get 拦截器,用于拦截对全局对象属性的读取操作。这样,当子应用尝试访问全局对象的属性时,Proxy 就会拦截并记录下这个操作。
  1. 拦截属性设置: 我们可以在 Proxy 对象上设置 set 拦截器,用于拦截对全局对象属性的修改操作。这样,当子应用尝试修改全局对象的属性时,Proxy 就会拦截并记录下这个操作。
  1. 拦截属性删除: 我们可以在 Proxy 对象上设置 deleteProperty 拦截器,用于拦截对全局对象属性的删除操作。这样,当子应用尝试删除全局对象的属性时,Proxy 就会拦截并记录下这个操作。

多实例下的沙箱隔离(proxy)

  1. 代理拦截: qiankun 使用了 ES6 的 Proxy 对象来拦截对全局对象的操作,如 window 对象。这意味着子应用程序的代码不会直接操作真正的 window 对象,而是通过代理对象进行操作。
  1. 环境隔离: qiankun 在每个子应用程序中创建了一个独立的代理对象和一个副本的 window 对象(称为 fakeWindow)。这样,每个子应用程序都有自己的沙箱环境,与其他子应用程序和主应用程序的环境完全隔离开来。
  1. 状态管理: 由于沙箱隔离,激活和卸载子应用程序时不需要操作状态池来更新或还原主应用程序和其他子应用程序的环境状态。因为子应用程序的环境状态都是在各自的沙箱环境中维护的,互不干扰。
  1. 统一 URL 下多个子应用支持: qiankun 的沙箱机制也支持在统一 URL 下加载多个子应用程序的场景。每个子应用程序都有自己的沙箱环境,因此不会相互影响,可以安全地共存于同一页面中。

qiankun如何做样式隔离

样式隔离主要通过 HTML 结构和动态样式表实现:
  1. 样式表解析和包裹: 当子应用挂载时,qiankun 会解析所有样式表(包括内联样式和外联样式),并将它们全部包裹在 style 标签中,插入到 HTML 模板中。这样可以确保子应用的样式不会直接影响其他子应用或主应用的样式。
  1. 样式表的动态切换: 当从一个子应用切换到另一个子应用时,qiankun 会执行子应用的 mounted 生命周期,在此期间,先执行卸载逻辑,将当前子应用的样式表全部删除。然后,在新子应用开始挂载时,再将新子应用的样式表挂载到页面上。这样就可以避免不同子应用的样式同时存在于同一个项目中,实现了样式的基本隔离。

qiankun中如何实现父子应用间的通信

  1. 自定义事件机制: 可以通过自定义事件来进行通信。父项目可以通过 window.dispatchEvent() 方法触发自定义事件,而子项目则可以通过 window.addEventListener() 方法监听相应的事件。这样就可以在父子项目之间传递消息和数据。
  1. 全局状态管理: 可以共享一个全局状态管理器,例如 Redux、Vuex 等。通过在全局状态管理器中存储共享数据,父子项目可以实现数据的共享和同步,从而实现通信。
  1. 发布/订阅模式: 可以使用发布/订阅模式来进行通信。父项目可以充当发布者,子项目可以充当订阅者。父项目通过发布消息,而子项目则通过订阅消息来接收数据。
  1. 全局事件总线: 可以在父项目中创建一个全局事件总线,用于中转消息和数据。父项目和子项目都可以通过全局事件总线来发送和接收消息,从而实现通信。
  1. window 对象属性: 父项目和子项目可以通过 window 对象的属性来进行通信。例如,父项目可以将数据存储在 window 对象的属性中,而子项目则可以通过访问相应的属性来获取数据。

qiankun中如何实现组件在不同项目间的共享

  1. 父子项目间的组件共享: 主项目加载时,将需要共享的组件挂载到全局对象(如 window)上,在子项目中直接注册和使用该组件。子项目可以通过全局对象来获取共享组件,并在需要的地方进行使用。
  1. 子项目间的组件共享(弱依赖): 子项目可以通过主项目提供的全局变量来获取共享组件。如果子项目中的共享组件是异步加载的,可以在加载组件之前先检查全局对象中是否存在该组件,如果存在则直接复用,否则再加载该组件。
  1. 子项目间的组件共享(强依赖): 在主项目中通过 loadMicroApp 函数手动加载提供组件的子项目,并将需要共享的组件挂载到全局对象上。然后将 loadMicroApp 函数传递给子项目,在子项目中需要使用共享组件的地方,手动加载提供组件的子项目,并等待加载完成后即可获取共享组件。

qiankun中应用之间如何复用依赖

  1. npm包
  1. CDN 引入: 如果依赖库被托管在 CDN 上,可以直接在 HTML 文件中通过 <script> 标签引入。不同的子应用程序可以在自己的入口 HTML 文件中引入相同的 CDN 资源,从而实现依赖的复用。
  1. 公共资源路径: 将依赖库放置在主项目或者公共 CDN 上,子应用程序可以通过访问主项目或者公共 CDN 上的资源路径来加载依赖库。这样可以确保所有子应用程序都共享相同的依赖库,实现依赖的复用。
  1. 外部脚本加载: 主项目可以通过动态创建 <script> 标签,加载需要的依赖库。子应用程序可以在加载主项目时,同时加载主项目引入的依赖库。这样可以实现依赖的动态加载和复用。
  1. 自定义模块加载器: 可以实现一个自定义的模块加载器,用于加载和管理依赖库。主项目可以通过模块加载器来加载依赖库,并将加载好的依赖库暴露给子应用程序使用。这样可以实现依赖的统一管理和复用。

面试题

如何处理 js 沙箱不能解决的 js 污染问题?

  1. 代码审查: 审查所有引入的第三方代码和库,确保它们是可信的,并且不包含任何恶意代码。尽量避免引入未经审查的代码,以降低安全风险。
  1. 沙箱加固: 加强 JavaScript 沙箱的安全性,采用更加严格的沙箱规则和策略。例如,限制沙箱中的 JavaScript 代码的访问权限,禁止访问敏感的全局变量和对象。
  1. 沙箱监控: 监控沙箱中的 JavaScript 代码的执行情况,及时发现和处理可能的安全问题。可以使用监控工具和技术来监控沙箱中的代码执行情况,并及时采取相应的措施来应对潜在的安全威胁。
  1. 子应用避免直接操作全局对象:如 window 和 document。如果必须要操作,我们应该在子应用卸载时,清理掉这些全局事件和全局变量,以防止对其他子应用或主应用造成影响。

比较一下qiankun 和 iframe?

qiankun:
  • 优势:
    • 轻量级:能够实现多个子应用程序的动态加载和管理,而不需要每个子应用程序都使用一个独立的 iframe。
    • 沙箱隔离:提供了沙箱隔离机制,能够有效地隔离不同子应用程序的 JavaScript 代码,确保它们之间的安全性和稳定性。
    • 路由协调:能够协调不同子应用程序之间的路由,实现统一的路由管理和跳转。
    • 状态管理:支持状态共享和管理,可以实现不同子应用程序之间的状态共享和通信。
  • 劣势:
    • 兼容性:在一些老旧的浏览器和环境下可能存在兼容性问题,需要额外的兼容性处理和调试。
    • 学习曲线:对于初学者来说,学习和理解 qiankun 的概念和使用方式可能需要一定的时间和精力。
iframe:
  • 优势:
    • 兼容性: iframe 是 HTML 标准的一部分,具有很好的浏览器兼容性,适用于各种环境和场景。
    • 隔离性: 每个子应用都在自己的 iframe 中运行,与其他子应用程序和主应用程序的代码完全隔离,可以确保各应用间的独立性。
    • 安全性: 由于代码完全隔离,iframe 在安全性方面具有一定的优势,可以防止一些常见的安全漏洞和攻击。
  • 劣势:
    • 性能: 使用 iframe 会增加页面的加载和渲染时间,可能会影响整体性能,特别是在加载大量子应用程序时。
    • 通信复杂性: iframe 之间的通信相对复杂,需要通过一些特殊的技术和机制来实现跨域通信和数据共享。
    • 维护成本: 每个子应用程序都需要一个独立的 iframe,可能会增加维护成本和复杂度。
 
在选择使用 iframe 还是 qiankun 时,可以根据具体的需求和场景来决定
  • 如果需要实现多个子应用程序的动态加载和管理,并且需要沙箱隔离、路由协调和状态管理等功能,则可以选择使用 qiankun。
  • 如果主要考虑兼容性、隔离性和安全性,并且子应用程序数量较少且不需要复杂的路由管理和状态共享,则可以考虑使用 iframe。

老项目改成微前端,里面的角色权限怎么处理

  1. 基于路由的权限控制: 在微前端架构中,可以通过路由来控制用户的访问权限。根据用户的角色信息,在路由配置中设置不同的权限要求,只有具有相应权限的用户才能访问对应的页面或功能。
  1. 统一的权限管理中心: 可以建立一个统一的权限管理中心,负责管理整个微前端系统的角色和权限信息。在用户登录时,通过权限管理中心获取用户的角色信息,并根据角色信息进行权限控制。
  1. 动态加载权限配置: 可以将角色权限的配置信息存储在后端数据库或配置文件中,并在系统启动时动态加载。这样可以实现灵活的权限配置,随时根据需求调整用户的权限。
  1. 前端路由守卫: 在前端代码中使用路由守卫机制,对用户的访问进行拦截和控制。根据用户的角色信息,决定是否允许用户访问特定的页面或功能。
  1. 与现有系统对接: 如果老项目已经有了一套完善的权限管理机制,可以考虑与现有系统对接,共享用户的角色和权限信息。这样可以避免重复开发和维护权限管理功能。

如何解决子项目之间和主项目之间的全局变量冲突的问题

  1. 命名空间: 主项目和子项目可以使用不同的命名空间来定义全局变量,以确保它们不会冲突。例如,在定义全局变量时加上项目特定的前缀,以区分不同项目的全局变量。
  1. 沙箱隔离: 使用沙箱隔离技术,如 qiankun 中的沙箱机制,可以将主项目和每个子项目隔离开来,防止它们之间的全局变量冲突。每个项目都有自己的沙箱环境,可以在其中定义和使用全局变量,互不干扰。
  1. 模块化加载: 使用模块化加载器,如 webpack,将主项目和子项目的代码分割成模块,并使用模块系统来管理和加载依赖关系。这样可以避免全局变量的冲突,每个模块都有自己的作用域。
  1. 避免全局污染: 尽量避免在全局作用域中定义和使用全局变量,而是使用局部作用域或模块作用域来定义变量。这样可以减少全局变量冲突的可能性,提高代码的可维护性和健壮性。
  1. 规范约定: 制定规范约定,定义全局变量的命名规则和使用规范,确保所有项目都遵循相同的规范。这样可以减少全局变量冲突的发生,提高代码的一致性和可维护性。

微前端与单体应用、单页应用的区别

单体应用(Monolithic Application):
  • 单体应用是指整个前端应用程序都打包在一起,所有的功能模块、页面和组件都在同一个代码库中管理和部署。
  • 单体应用的开发、测试和部署都集中在同一个团队和流程中进行。
  • 单体应用通常适用于小型或中型的项目,因为随着项目规模的增大,代码的复杂性和维护成本会增加。
单页应用(Single Page Application,SPA):
  • 单页应用是指整个应用程序只有一个 HTML 页面,页面内容通过 JavaScript 动态加载和更新,从而实现页面的切换和内容的更新。
  • SPA 通常使用前端框架(如React、Angular、Vue.js等)来管理路由和状态,以提供更好的用户体验和性能。
  • 单页应用的优点包括快速加载速度、流畅的用户体验和简化的后端开发,但也存在首次加载时间长、SEO 不友好等缺点。
微前端(Micro Frontends):
  • 微前端是一种前端架构模式,它将一个大型的前端应用程序拆分成多个小的、独立的部分,每个部分可以单独开发、测试和部署。
  • 微前端允许每个部分有自己的技术栈和开发团队,同时可以共享公共资源和组件,从而提高开发效率和降低维护成本。
  • 微前端的优点包括更灵活的团队组织、独立部署和扩展、更好的代码复用和解耦,但也需要考虑到部署和通信等方面的挑战。