微前端
参考链接:
什么是微前端?
微前端是一种软件架构,可以将前端应用拆解成一些更小的能够独立开发部署的微型应用,然后在将这些微型应用组合使其成为整体的架构模式。
微前端类似于组件架构,但不同的是,组件不能够独立构建和发布,但是微前端中的应用是可以的。
微前端架构与框架无关,每个微应用都可以使用不同的框架。

| 特性 | 单体前端 | 微前端 |
|---|---|---|
| 代码库 | 单个大型代码库 | 多个独立代码库 |
| 团队结构 | 集中式团队 | 分布式独立团队 |
| 技术栈 | 统一技术栈 | 混合技术栈 |
| 部署 | 整体部署 | 独立部署 |
| 开发速度 | 后期变慢 | 持续快速 |
| 复杂度 | 高度耦合 | 解耦独立 |
微前端的价值
微前端架构具备以下几个核心价值:
- 技术栈无关 主框架不限制接入应用的技术栈,子应用具备完全自主权
- 独立开发、独立部署 子应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
- 独立运行时 每个子应用之间状态隔离,运行时状态不共享
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
应用架构如下:
Stitching layer 作为主框架的核心成员,充当调度者的角色,由它来决定在不同的条件下激活不同的子应用。因此主框架的定位则仅仅是:导航路由 + 资源加载框架
single-spa是什么
好的,我们来详细解释一下 single-spa 是什么。
核心定义
single-spa 是一个用于构建【微前端】架构的 JavaScript 框架。 你可以把它理解为一个顶层的路由器和应用程序加载器。
它的核心思想是:将一个大型的单页前端应用(SPA)拆分成多个小型、独立、可以并行开发交付的“微应用”。然后,single-spa 负责在运行时根据一定的规则(通常是当前的 URL)动态地加载、展示、卸载这些微应用。
一个简单的比喻
想象一下你的浏览器是一个舞台:
- 传统单体应用: 只有一个庞大的剧团表演一整部冗长的戏剧。要更换一个演员(修改一个功能),需要整个剧团停下来重新排练(整个应用重新构建部署)。
- single-spa 微前端: 舞台(浏览器)本身是空的。有一个导演(single-spa)。导演手里有一个节目单(根配置),上面写着什么节目(微应用)在什么时刻(例如,URL 是
/settings时)上台表演。- 当报幕员说“下一个节目是《用户设置》”时(用户访问了
/settings),导演就喊:“《用户设置》剧组,上台!”(加载并挂载 React 微应用)。 - 节目表演完了(用户离开了
/settings),导演就喊:“《用户设置》剧组,下台!”(卸载 React 微应用)。 - 下一个节目是《商品列表》,另一个完全不同的剧组(Vue 微应用)就上台表演。
- 当报幕员说“下一个节目是《用户设置》”时(用户访问了
single-spa 就是这个导演,它自己不表演具体内容,但它负责协调所有剧组(微应用)的上场和下场时机。
为什么需要 single-spa?(解决的问题)
- 技术栈无关: 各个微应用可以使用不同的技术栈(React, Vue, Angular, Svelte 等)。团队可以自由选择或升级其技术栈,而不会影响其他团队。
- 独立开发、独立部署: 每个微应用都由独立的团队开发、测试和部署,大大提升了团队的自治度和发布效率。
- 增量升级: 允许你逐步重写一个老旧的巨型前端应用,可以一部分一部分地用新的框架替换,而不是一次性重写全部。
- 代码隔离: 应用之间实现了良好的代码和样式隔离(虽然需要一些额外规范),避免了全局污染和冲突。
single-spa 的核心工作原理
single-spa 通过定义一套生命周期协议来工作。每个微应用都必须对外暴露三个核心的函数:
bootstrap: 应用首次加载时执行一次,用于初始化。mount: 当激活条件满足时(例如用户访问了该应用的路由),执行此函数。应用需要在这个函数里完成渲染,将组件挂载到 DOM 上。unmount: 当激活条件不再满足时(例如用户离开了该路由),执行此函数。应用需要在这个函数里完成清理工作,将自己从 DOM 上卸载。
single-spa 的“根配置”会注册所有微应用,并为每个应用定义一个 activity function(激活函数)。这个函数通常根据 window.location 进行判断。
javascript
1 | // single-spa 根配置示例 (root-config.js) |
流程如下:
- 用户访问
https://example.com/react。 - single-spa 检查所有注册的应用,发现
my-react-app的activeWhen函数返回true。 - single-spa 动态加载
my-react-app的代码包。 - 依次执行该应用的
bootstrap和mount生命周期函数,应用被渲染到页面上。 - 用户导航到
/vue,my-react-app的activeWhen返回false。 - single-spa 执行它的
unmount函数,将其卸载。
主要优缺点
优点:
- 强大的解耦和能力: 真正实现了前端应用的拆分和团队自治。
- 框架无关: 是整合不同技术栈应用的理想选择。
- 懒加载: 提升首次加载速度,按需加载应用。
缺点:
- 复杂度高: 引入了分布式系统的复杂性,需要解决依赖共享、样式隔离、通信机制等问题。
- 开发体验: 调试和配置比单体应用更复杂。通常需要类似
single-spa官方的create-single-spa工具链来简化流程。 - 依赖管理: 需要谨慎处理共享库(如
react,vue)以避免重复打包。
与 Module Federation 的区别
Webpack 5 的 Module Federation(模块联邦) 是另一种实现微前端的热门技术。它们不是互斥的,甚至可以结合使用:
- single-spa 主要解决的是路由和应用生命周期管理的问题(“什么时候加载哪个应用”)。
- Module Federation 主要解决的是应用之间代码共享和依赖处理的问题(“如何共享一个 React 实例,避免重复加载”)。
一个常见的模式是:使用 single-spa 作为路由器,使用 Module Federation 来加载微应用和共享依赖。
总结
**single-spa 是一个成熟、强大的微前端解决方案,它通过一套生命周期协议,将多个独立开发、独立部署的微应用组合成一个完整的单体体验。它非常适合大型企业级应用、需要整合不同技术栈或进行增量迁移的项目。**虽然它引入了一定的复杂性,但它为解决前端单体膨胀问题提供了非常系统化的思路和工具。
html entry和js entry的概念是什么
好的,这是一个非常核心的微前端概念。**js-entry** 和 **html-entry** 是微前端中两种不同的应用打包和加载方式,它们代表了两种不同的设计哲学和实现技术。
核心概念对比
| 特性 | js-entry (JavaScript 入口) | html-entry (HTML 入口) |
|---|---|---|
| 入口文件 | 一个 JavaScript 文件 (e.g., app.js) |
一个 HTML 文件 (e.g., index.html) |
| 内容 | 主要包含 JavaScript 逻辑和组件 | 包含 HTML、**<script>**、 **<link>**、 **<style>** |
| 加载方式 | 框架(如 single-spa)加载并执行 JS 文件 | 框架通过 fetch 获取 HTML,解析出其中的 JS/CSS 并执行 |
| 样式处理 | 需要手动管理。通常需要约定(如 CSS Modules, CSS-in-JS)或工具来避免冲突。 | 自动处理。HTML 中的 <link>和 <style>标签会被自动插入到 <head>中。 |
| 沙箱隔离 | 通常需要额外的库(如 qiankun的沙箱)来实现 JS 和 CSS 的运行时隔离。 |
天然更易于实现HTML级别的沙箱(例如,创建一个 Shadow DOM 来包裹整个微应用的内容)。 |
| 代表性方案 | 原始的 single-spa 方案 | qiankun(基于 single-spa)、Module Federation(某种程度上) |
深入理解
1. js-entry (JavaScript 入口)
这是 single-spa 早期和官方推荐的方式。它的理念是:一个微应用本质上是一个 JavaScript 模块,这个模块导出了 bootstrap, mount, unmount 等生命周期函数。
如何工作?
- 主应用(容器)根据路由规则,判断需要加载微应用 A。
- 主应用通过
System.import()或import()动态加载微应用 A 的 入口 JS 文件(例如https://a.com/app.js)。 - 这个 JS 文件被执行,并返回一个包含生命周期函数的对象。
- 主应用依次调用微应用的
bootstrap和mount函数。 - 在
mount函数中,微应用用自己的逻辑将组件渲染到主应用提供的 DOM 容器中。
示例代码 (single-spa 配置):
javascript
1 | registerApplication({ |
优点:
- 概念清晰,符合“应用即模块”的理念。
- 打包输出干净,通常是一个或多个 JS 包。
缺点:
- 样式处理麻烦:你需要自己在微应用的 JS 代码里引入 CSS(如
import './app.css'),并小心全局样式冲突。 - 需要额外配置:为了实现资源加载和隔离,需要复杂的 Webpack 配置和额外的库。
2. html-entry (HTML 入口)
这种方式的理念是:一个微应用是一个完整的、可以独立运行的“页面”。主应用只需要加载这个页面的 HTML 文件,剩下的资源(JS, CSS)都由这个 HTML 文件自己声明。
如何工作?
- 主应用根据路由规则,判断需要加载微应用 B。
- 主应用通过 fetch 请求微应用 B 的入口 HTML 文件(例如
https://b.com/index.html)。 - 主应用解析这个 HTML 文件,提取出其中的
**<script>**** 和**<link>****标签。 - 主应用手动创建这些
<script>和<link>元素,并将它们插入到主文档的<head>中,从而加载并执行微应用的 JS 和 CSS。 - 同时,主应用通常会提供一个隔离的沙箱环境(如 Shadow DOM),将微应用的整个 DOM 结构渲染在其中。
示例概念(qiankun 的做法):
javascript
1 | import { registerMicroApps, start } from 'qiankun'; |
优点:
- 对旧项目更友好:很多老项目(如 jQuery、Backbone 项目)本身就是以 HTML 为中心开发的,改造为 html-entry 更容易。
- 资源加载自动化:无需在 JS 中手动引入资源,HTML 中声明的所有资源都会被自动加载。
- 更强大的隔离能力:更容易与 Shadow DOM 结合,实现真正的样式和作用域隔离。
缺点:
- 需要解析 HTML:主应用需要具备解析 HTML 并提取资源的能力,实现起来比直接
import一个 JS 文件更复杂。 - 可能加载多余资源:如果 HTML 中声明了多个入口,需要更精细的解析策略来避免加载未使用的资源。
总结与类比
| 概念 | 类比 |
|---|---|
**js-entry** |
就像 “组装电脑”。主应用(你)买来各个零件(JS 逻辑、手动引入的 CSS),自己动手组装成一台完整的电脑(微应用)。控制力强,但过程繁琐。 |
**html-entry** |
就像 “购买品牌整机”。主应用(你)直接买来一台戴尔电脑(HTML 文件),插上电(插入 DOM)就能用。开箱即用,非常方便,但内部细节被封装了。 |
现代趋势:
- 纯粹的
js-entry由于其配置的复杂性,在直接使用时体验并不友好。 - qiankun 这类框架通过实现
html-entry模式,极大地改善了开发者体验,降低了微前端的上手门槛。 - Webpack Module Federation 提供了另一种更现代化的
js-entry,它通过复杂的依赖共享机制,解决了js-entry的许多痛点(如共享库重复加载),可以看作是js-entry的终极进化形态。
因此,选择哪种方式取决于你的技术栈和框架:
- 使用 原始的 single-spa,你通常需要处理
js-entry。 - 使用 qiankun,你享受的是
html-entry带来的便利。 - 使用 Module Federation,你体验的是高级的、基于依赖共享的
js-entry。
路由分发可以实现微前端架构吗
当然可以。路由分发是实现微前端架构最主要、最常用也是最经典的方式。它被认为是微前端架构的“第一种模式”,并且非常有效。
核心思想
通过路由(URL)的变化,来决定在页面容器中加载、展示和卸载哪个微前端应用。 主应用(通常称为“容器应用”或“根应用”)充当一个路由器和编排者的角色,而各个微应用则是独立开发、部署的功能模块。
它是如何工作的?
- 一个路由规则集:主应用中维护一个配置表,将特定的 URL 路径模式(如
/app1/*,/settings,/dashboard)映射到对应的微应用。 - 监听路由变化:主应用监听
popstate、hashchange等浏览器路由事件。 - 匹配与决策:当路由发生变化时(用户点击链接或手动输入 URL),主应用根据配置表判断当前应该激活哪个微应用。
- 加载与挂载:主应用动态获取该微应用的资源(JS, CSS),并将其挂载到页面中指定的 DOM 容器(如
<div id="micro-app-container">)中。 - 卸载与清理:当用户离开当前路由时,主应用会卸载之前的微应用,并进行必要的清理工作(如清除事件监听器、内存释放等),然后加载新的微应用。
webpack5的Module Federation可以详细说说嘛
好的,我们来深入、详细地探讨 Webpack 5 的 Module Federation(模块联邦)。它不仅仅是微前端的一种实现方式,更是一种革命性的架构思想。
一、核心概念:它是什么?
Module Federation (MF) 的核心目标是:允许一个 JavaScript 应用在运行时动态地从另一个应用加载代码并共享依赖。
你可以把它想象成在浏览器端实现了类似 Node.js 的 require 或 import 机制,但不是在本地文件系统,而是在网络上的不同独立应用之间。
它打破了传统的应用隔离边界,允许应用彼此成为“模块提供者”和“模块消费者”。
二、为什么要用 Module Federation?(解决的核心痛点)
- 彻底解决依赖重复打包
- 传统微前端:如果主应用和微应用都使用了 React、Vue 等相同的库,这些库的代码会被分别打包到各自的 bundle 中。用户浏览器会多次下载和执行相同的库代码,导致体积膨胀和性能下降。
- MF:可以指定共享依赖。React 等库只加载一次,所有应用都使用同一份实例。这解决了最重要的“依赖地狱”问题。
- 更彻底的应用拆分与团队自治
- 它允许将应用拆分成更细粒度的“模块”或“组件”,而不仅仅是“页面”或“应用”。
- 团队可以独立开发、部署一个按钮、一个表单、一个页面,并让其他团队直接消费。
- 运行时动态集成
- 代码集成发生在运行时(Runtime),而非构建时(Build-time)。这意味着:
- 你可以进行 A/B 测试,动态切换不同版本的组件。
- 可以独立部署某个模块,而无需重新部署整个应用。
- 主应用甚至不需要提前知道所有可能被加载的微应用。
- 代码集成发生在运行时(Runtime),而非构建时(Build-time)。这意味着:
三、核心角色与配置
MF 中有两个关键角色:
- Host (宿主模块/消费者)
- 它是一个使用方应用,在运行时从其他地方(
Remote)导入并执行代码。 - 它的配置使用
remotes属性。 - 主应用,消费其他远程模块
- 容器应用,集成各个微前端模块
- 通常是用户直接访问的入口应用
- 它是一个使用方应用,在运行时从其他地方(
- Remote (远程模块/提供者)
- 它是一个提供方应用,将其内部的某些模块暴露给外部使用。
- 它的配置使用
exposes属性。 - 微前端子应用,提供特定功能模块
- 可以独立开发、部署和运行
一个应用可以同时是 Host 和 Remote。
配置详解 (webpack.config.js)
javascript
1 | // Remote 应用的配置 (提供模块的应用,端口3001) |
javascript
1 | // Host 应用的配置 (消费模块的应用,端口3002) |
四、如何使用?
在 app2 (Host) 的代码中,你可以像导入本地模块一样动态导入 app1 (Remote) 暴露的模块:
javascript
1 | // 在 app2 的 React 组件中 |
五、工作流程(魔法是如何发生的?)
- 构建阶段:
Host(app1) 构建时,会生成一个remoteEntry.js文件。这个文件是一个清单(Manifest),记录了exposes了哪些模块以及如何获取它们。Remote(app2) 构建时,会意识到import('app1/Button')是一个远程模块,不会将其打包到自己的 bundle 中。
- 运行时阶段:
- 浏览器加载
app2。 - 当执行到
import('app1/Button')时,Webpack 运行时就会去检查配置。 - 它发现
app1对应http://localhost:3001/remoteEntry.js,于是动态加载这个remoteEntry.js文件。 remoteEntry.js告诉app2如何从app1的服务器上获取Button组件的代码块。- Webpack 加载这个代码块,并与**
**app2**共享的**react**和**react-dom****实例一起执行它。 - 组件渲染成功。
- 浏览器加载
六、与 single-spa 等方案的对比
| 特性 | Module Federation | single-spa (传统方式) |
|---|---|---|
| 核心能力 | 模块共享与依赖管理 | 应用生命周期管理与路由 |
| 集成方式 | 运行时动态导入 | 运行时加载(但依赖需各自处理) |
| 依赖处理 | 卓越。自动共享库,避免重复。 | 困难。需要额外手动配置或工具解决。 |
| 打包输出 | 生成 remoteEntry.js清单文件 |
生成应用的入口 JS 文件 |
| 粒度 | 非常细。可以共享单个组件、函数、工具库。 | 较粗。通常以整个“应用”或“页面”为单元。 |
| 关系 | 互补。可以结合使用:用 single-spa 做路由和生命周期管理,用 MF 来加载组件和共享依赖。 |
总结
Webpack 5 的 Module Federation 是一项颠覆性的技术。它不再是简单地将应用机械地拼凑在一起,而是真正实现了应用间代码的“无缝融合”与“依赖共享”。
它最适合的场景包括:
- 大型平台型产品:需要多个团队独立开发不同功能模块。
- 微前端架构:是目前实现微前端最先进、最优雅的方案之一。
- 跨应用组件/工具库共享:避免维护多个版本的通用组件。
它代表了前端架构从“单体”到“分布式”演进的重要方向,是每一位前端架构师都应该深入理解的技术。
web component
好的,我们来全面、深入地探讨 Web Components。
一、核心概念:它是什么?
Web Components 是一套浏览器原生支持的技术集合,允许你创建可重用的、封装的自定义 HTML 元素(类似于 <div>、<button>,但是是你自己定义的),并在任何现代浏览器中使用它们,无需任何外部框架(如 React、Vue、Angular)。
它的核心目标是为 Web 开发提供真正的组件化模型,并解决代码重用和封装的问题。
二、为什么需要 Web Components?(解决的问题)
- 框架无关性 (Framework-Agnostic)
- 你用 Web Components 编写的组件可以在任何 HTML 页面中使用,无论这个页面用的是 React、Vue、Angular、jQuery 还是纯 JavaScript。它提供了最大程度的可移植性。
- 原生封装 (Native Encapsulation)
- Shadow DOM 提供了强大的样式和行为封装。组件内部的样式不会泄漏到外部,外部的样式也不会渗透到组件内部(除非特意允许)。这彻底解决了 CSS 全局污染的问题。
- 长期稳定性 (Longevity)
- 作为Web 平台标准,它由浏览器厂商直接实现和维护,不像前端框架那样有生命周期(例如,AngularJS 到 Angular 的断代升级)。你写的组件在未来很多年内都能继续工作。
- 生态系统互操作性 (Interoperability)
- 它可以在任何框架中被当作普通的 HTML 元素使用,成为了连接不同技术栈应用的“桥梁”。
三、技术构成(四大核心技术)
Web Components 主要由四项技术标准组成,它们可以单独使用,但组合在一起威力最大。
1. Custom Elements(自定义元素)
一套 JavaScript API,允许你定义自定义元素及其行为。
- 如何定义:通过继承
HTMLElement类来创建一个新的元素类。 - 生命周期回调:
connectedCallback: 当元素首次被插入到 DOM 时调用。disconnectedCallback: 当元素从 DOM 中移除时调用。adoptedCallback: 当元素被移动到新的文档时调用。attributeChangedCallback: 当元素的被观察属性(在observedAttributes中定义)发生变化时调用。
示例:定义一个简单的自定义元素
javascript
1 | class MyButton extends HTMLElement { |
在 HTML 中使用:
html
2. Shadow DOM(影子 DOM)
一套用于将封装的、“影子”的 DOM 树附加到元素的 API。这是实现样式和行为封装的关键。
- Shadow Root:Shadow DOM 的根节点。
- 模式:
open: 可以通过 JavaScript 从外部访问(例如element.shadowRoot)。closed: 外部无法访问,封装性更强。
- 作用域:在 Shadow DOM 内部定义的样式和脚本只在这个范围内有效,与外部隔离。
示例:为自定义元素添加 Shadow DOM
javascript
1 | class MyCard extends HTMLElement { |
在 HTML 中使用:
html
1 | <my-card> |
3. HTML Templates(HTML 模板)
<template> 和 <slot> 元素允许你编写在页面加载时不会立即渲染的标记模板。这些模板可以被后续的 JavaScript 激活和使用。
<template>:内容不会被浏览器解析、渲染或执行,直到被 JavaScript 提取并使用。<slot>:在 Web Component 内部充当占位符,允许用户在使用组件时传入自己的自定义内容(如上例所示)。
4. ES Modules(ES 模块)
现代 JavaScript 的模块标准,为在 Web 上包含和重用 JS 代码提供了官方机制。它使得导入和导出 Web Components 变得简单。
html
1 | <!-- 在 HTML 中导入并使用一个定义好的 Web Component --> |
四、Web Components 的优点与缺点
优点:
- 浏览器原生:无需编译,无需复杂的构建工具。
- 高度封装:Shadow DOM 解决了 CSS 和 DOM 的隔离问题。
- 框架无关:极高的互操作性和可移植性。
- 永久可用:作为 Web 标准,具有极长的生命周期和向后兼容性。
缺点:
- 生态相对年轻:虽然基础功能强大,但缺少像 React/Vue 那样庞大的生态系统(状态管理、路由、测试工具等)。
- 数据流管理较弱:原生不支持类似 React 的 Props 向下传递、事件向上冒泡的父子组件通信范式,需要自己实现(通常用 Custom Events)。
- SSR (服务器端渲染) 支持差:自定义元素可以在服务器上渲染,但其动态行为和客户端激活(Hydration)非常复杂,远不如 Next.js/Nuxt.js 成熟。
- 开发体验 (DX):原始的开发方式比较繁琐,需要写很多样板代码。但社区有诸如 Lit、Stencil 等库来极大地改善体验。
五、Web Components 与微前端和框架
- 在微前端中:Web Components 是实现技术栈无关微前端的理想载体。每个团队可以用自己熟悉的技术(React, Vue)开发组件,但最终发布为一个封装好的 Web Component(例如,将一个 React 组件包装成
<team-product-card>),由主应用统一调度。 - 与框架的关系:
- 不是替代关系,而是互补关系。
- 框架 (React/Vue) 擅长解决: 复杂的 UI 状态管理、数据流、高效的 DOM 差分更新。它们提供了优秀的开发体验和完整的解决方案。
- Web Components 擅长解决: 跨框架重用、底层 UI 组件库、集成遗留应用、长期稳定的基础组件。它们提供了浏览器级别的互操作性。
总结来说,Web Components 是 Web 平台的底层基础,为构建可长期存在、高度封装、框架无关的组件提供了强大的原生能力。 虽然它在开发复杂应用时不如现代框架那么方便,但其独特优势使其在特定场景(如微前端、跨技术栈组件库、大型企业级应用)中是不可替代的选择。
qiankun实现样式和js隔离的原理
好的,我们来深入剖析 qiankun 实现样式和 JavaScript 隔离的原理。这是 qiankun 的核心能力之一,也是它相比原始 single-spa 更易用、更强大的关键。
总体思想
qiankun 的隔离策略可以概括为:“重样式隔离,轻 JS 沙箱”。它通过在应用加载和卸载时动态地操作样式和 JavaScript 执行环境来模拟一个隔离的沙箱环境。
一、样式隔离 (CSS Isolation)
qiankun 提供了三种主要的样式隔离方案,根据场景不同自动或手动启用。
1. 严格样式隔离 (Scoped CSS)
原理: 为每个微应用包裹一个 Shadow DOM。
- 如何工作:
- 当 qiankun 挂载一个微应用时,它会创建一个 Shadow Root,并将其作为该应用的容器。
- 微应用的所有 DOM 结构都被渲染在这个 Shadow DOM 内部。
- Shadow DOM 的特性天然实现了样式的封装:内部的样式不会影响外部,外部的样式也不会影响内部(除非使用
::part或:host等特定语法)。
- 优点: 隔离性最强,是浏览器原生的完美隔离方案。
- 缺点:
- 某些第三方库(特别是弹窗类)可能会因为无法正确定位到 Shadow DOM 外部而出现问题。
- 微应用内的样式完全无法影响主应用,反之亦然,有时这可能不符合设计需求。
- 启用方式: 在
start函数中配置{ strictStyleIsolation: true }。
javascript
1 | import { start } from 'qiankun'; |
2. 实验性样式隔离 (CSS Scoped)
原理: 一种更宽松的隔离方式,使用 运行时动态样式表重写。
- 如何工作:
- qiankun 会劫持微应用运行时动态添加样式标签(
**<style>**,****<link>**)** 的行为。 - 当微应用插入一个新的样式标签时,qiankun 会将其内容抓取过来。
- 使用 CSS 规则重写器(例如
postcss插件)为所有 CSS 选择器添加一个特殊的前缀。这个前缀通常基于微应用的名称或一个特定属性。 - 将重写后的 CSS 内容插入到
document.head中。 - 同时,qiankun 会为微应用的容器元素添加上一步中使用的相同属性。
- qiankun 会劫持微应用运行时动态添加样式标签(
示例:
- 微应用有一个样式规则:`.button { color: red; }`
- qiankun 将其重写为:`[data-qiankun="my-app"] .button { color: red; }`
- 同时,微应用的容器 `<div>` 会获得属性:`<div data-qiankun="my-app">...</div>`
- 这样,样式规则就只会在这个容器内生效。
- 优点: 比 Shadow DOM 兼容性更好,允许微应用样式影响其容器内的任何元素(包括动态 append 到 body 的弹窗)。
- 缺点: 是运行时重写,有一定性能开销,且是实验性功能。
- 启用方式: 在
start函数中配置{ experimentalStyleIsolation: true }。
javascript
1 | start({ |
3. 动态样式表加载/卸载 (最常见的默认行为)
如果不开启上述两种隔离,qiankun 默认采用一种更简单但有效的策略。
- 原理:
- 加载时: qiankun 通过
fetch获取微应用的 HTML 入口,解析出所有的<style>和<link>标签。 - 将这些样式标签直接插入到** **
**document.head**中。 - 卸载时: qiankun 会记录所有由该微应用添加的样式标签,并在卸载微应用时直接将这些标签从 DOM 中移除。
- 加载时: qiankun 通过
- 优点: 实现简单,性能好。
- 缺点: 不是真正的隔离。如果多个微应用有相同选择器的样式规则,后加载的会覆盖先加载的(因为后加载的样式表在更后面,优先级更高)。这依赖于开发者的约定。
- 这是 qiankun 的默认行为,对于很多应用来说已经足够。
二、JavaScript 隔离 (JS Sandbox)
qiankun 的 JS 沙箱的核心目标是:防止微应用在全局环境(**window**)上留下永久的污染,并在应用切换时恢复和清理环境。它主要模拟了三个环境的隔离:
1. 快照沙箱 (SnapshotSandbox) - 用于单实例场景
原理: 在应用加载前后对全局 window 对象进行“拍照”和“diff”,适用于同一时间只能有一个微应用活跃的浏览器环境。
- 工作流程:
- 激活沙箱 (mount): 将当前
window的所有属性拍一个快照(windowSnapshot),存起来。 - 微应用运行: 微应用可以任意修改
window。 - 失活沙箱 (unmount):
- 将当前的
window和之前存的windowSnapshot进行对比,得到修改的差异(modifyPropsMap)。 - 还原现场: 遍历差异,将
window上的属性恢复到拍快照时的状态。 - 记录污染: 将微应用修改的差异保存起来。
- 将当前的
- 再次激活: 将之前保存的差异(
modifyPropsMap)重新应用到****window****上,让微应用感觉自己的修改一直都在。
- 激活沙箱 (mount): 将当前
- 优点: 兼容性极好,支持所有浏览器。
- 缺点: 无法支持多个微应用同时运行(多实例),因为共用一个全局
window。
2. 代理沙箱 (ProxySandbox) - 用于多实例场景(主流)
原理: 使用 ES6 的 Proxy 为每个微应用创建一个假的、隔离的 window 对象。
- 工作流程:
- qiankun 为每个微应用创建一个空的
fakeWindow对象。 - 用
Proxy代理这个fakeWindow。 - 当微应用操作
window时:- 读操作:优先从
fakeWindow里读,如果读不到,则 fallback 到真正的全局window(这样可以共享document,location等全局对象)。 - 写操作:所有对属性的新增和修改都只作用于
fakeWindow上,完全不会污染真正的全局** ****window**。
- 读操作:优先从
- 微应用的所有代码都在这个代理的上下文中执行(通过
with语句或eval改写)。
- qiankun 为每个微应用创建一个空的
- 示例:
javascript
1 | // 微应用代码 |
- 优点:
- 真正的隔离,多个微应用可以同时运行,每个都有自己独立的
window空间。 - 对微应用无感知,无需修改代码。
- 真正的隔离,多个微应用可以同时运行,每个都有自己独立的
- 缺点: 依赖 ES6
Proxy,无法在低版本浏览器(如 IE)中使用。
3. 遗留沙箱 (LegacySandbox) - 已逐渐被代理沙箱取代
原理类似于快照沙箱,但性能优化更好。它只记录微应用修改过的属性,在卸载时还原这些属性。同样不支持多实例。
总结:qiankun 的隔离策略
| 隔离类型 | 技术方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 样式隔离 | Shadow DOM | 原生,隔离最彻底 | 兼容性问题,弹窗等组件可能异常 | 需要最强隔离的新应用 |
| 动态样式表重写 (实验性) | 兼容性好,支持弹窗 | 运行时开销,实验性功能 | 需要兼容弹窗的复杂应用 | |
| 动态加载/卸载 (默认) | 简单,性能好 | 非真正隔离,依赖约定 | 大多数简单应用,样式冲突风险低 | |
| JS 隔离 | 快照沙箱 (SnapshotSandbox) | 兼容所有浏览器 | 不支持多实例 | 单实例模式,需兼容 IE 等老浏览器 |
| 代理沙箱 (ProxySandbox) | 真正隔离,支持多实例,对应用无感知 | 依赖 Proxy,不兼容 IE |
现代浏览器下的主流选择 |
qiankun 会自动选择最佳策略:在支持 Proxy 的浏览器中默认使用代理沙箱 (多实例) 和动态样式加载/卸载。如果不支持 Proxy,则降级到快照沙箱 (单实例)。
通过这种组合策略,qiankun 在绝大多数场景下为微前端应用提供了足够安全、稳定且高性能的隔离环境,极大地简化了微前端的落地复杂度。
qiankun是如何实现父子应用通信的呢
好的,qiankun 的通信机制是其核心功能之一,它设计得非常灵活,提供了多种方式来实现主应用(父应用)和微应用(子应用)之间的通信。
qiankun 并没有强制使用某一种通信方式,而是提供了一个轻量的 API(**initGlobalState**) 作为推荐方案,同时也完全支持任何其他流行的状态管理库(如 Redux, Mobx, Vuex)或浏览器原生 API。
一、官方推荐方案:使用 initGlobalState (Actions 通信)
这是 qiankun 官方提供的通信方式,适用于大多数场景。它的核心思想是一个简单的发布-订阅模式。
1. 在主应用中初始化状态并下发通信方法
主应用是整个状态的管理中心。
javascript
1 | // main-app/src/actions.js |
2. 在微应用中获取并操作状态
微应用需要从生命周期函数中获取到 props,其中就包含了通信方法。
javascript
1 | // micro-app/src/main.js (入口文件) |
3. 在微应用组件中具体使用
javascript
1 | // micro-app/src/App.jsx |
initGlobalState API 说明:
setGlobalState(state: object): 设置新的全局状态,会自动与旧状态进行浅合并。onGlobalStateChange(callback: function): 注册监听器,状态变化时触发。offGlobalStateChange(): 取消监听。getGlobalState(): 获取当前全局状态。
二、其他通信方案
qiankun 是框架无关的,因此你也可以选择任何你熟悉的通信方式。
1. 使用 CustomEvent (浏览器原生事件)
原理: 利用浏览器原生的 window.dispatchEvent 和 window.addEventListener 进行通信。
javascript
1 | // 主应用 - 发送事件 |
优点: 原生支持,非常简单。
缺点: 数据传递能力较弱(只能同步),缺乏状态管理能力,事件需要全局唯一命名以免冲突。
2. 使用 Redux/Mobx/Vuex 等状态库
原理: 主应用和微应用共享同一个状态库实例。
- 步骤:
- 主应用创建一个 Redux Store 或其他状态库实例。
- 将这个 Store 通过
window对象或者微应用的props暴露给微应用。 - 微应用连接到这个全局的 Store,进行状态的读取和分发 Action。
javascript
1 | // 主应用 - 创建并暴露 store |
优点: 功能强大,可以处理复杂的业务逻辑和状态流。
缺点: 紧密耦合,主应用和微应用必须使用同一种状态库,且版本需要兼容。
3. 通过 URL 或 Query Parameters 通信
原理: 通过改变 URL 的查询参数来传递简单信息。
javascript
1 | // 主应用改变URL |
优点: 非常简单,状态可被收藏和分享。
缺点: 只适合传递少量简单数据。
总结与选择建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
官方** ****initGlobalState** |
官方推荐,简单轻量,满足大部分通信需求 | 功能相对简单,不适合极其复杂的场景 | 绝大多数微前端通信场景的首选 |
| CustomEvent | 浏览器原生,非常简单 | 功能弱,只能传简单数据,易产生事件名冲突 | 简单的父子通知、触发动作 |
| Redux/Vuex 等 | 功能强大,适合复杂状态管理 | 耦合度高,主子和微应用必须使用同一种状态库 | 大型复杂应用,且技术栈统一 |
| URL Parameters | 实现简单,状态可分享 | 传递数据量有限,类型受限 | 过滤条件、简单配置等 |
最佳实践建议:
- 优先使用官方的** **
**initGlobalState**,它能覆盖 90% 的微前端通信需求。 - 对于简单的、一次性的动作触发(如“刷新列表”、“显示通知”),可以辅以 CustomEvent。
- 只有在主应用和所有微应用技术栈统一且非常复杂时,才考虑使用共享状态库。
- 通信的设计应遵循最小化原则,尽量减少主应用和微应用之间的耦合,让微应用保持最大的独立性。
- Title: 微前端
- Author: cenweilings@163.com
- Created at : 2024-01-15 00:00:00
- Updated at : 2025-09-27 15:11:56
- Link: https://blog-git-main-cenweilings-projects.vercel.app/2024/01/15/微前端/
- License: This work is licensed under CC BY-NC-SA 4.0.