【微前端】1 原生方案调研

6161 字
31 分钟
【微前端】1 原生方案调研

微前端的本质是降低大型复杂应用的开发、升级、维护和团队协作成本。假设在团队协作的过程中,各个团队使用了不同的技术栈进行应用开发,如果不进行应用拆分解耦,势必需要通过迁移和改造来兼容整个单体应用。

如果能实现应用拆分,就可以使得各个团队的应用保持独立自治,不会互相依赖彼此,也不需要在单体应用中进行技术栈的迁移和改造,极大的减少各个团队的开发、升级和维护成本。那么如何实现应用拆分呢?

微前端的特性 为了使整个单体应用可以根据不同的业务进行解耦,微前端会将单个应用拆分成多个聚合在一起的小型应用,这些应用具备独立开发、独立部署和独立运行的特性。

在聚合的过程中需要一个容器应用(在微前端里称作主应用),主应用通过设计导航将各个拆分的小型应用(在微前端里称作微应用)聚合在一起,可以根据导航进行微应用的切换。具体如下所示:

微前端
微前端

主应用的导航可以是前端框架(React 或 Vue 框架)的路由,也可以是后端服务路由,还可以是前端自己设计的切换逻辑:

如果主应用是 SPA 应用,此时导航是路由跳转,根据前端路由进行微应用切换; 如果主应用是 MPA 应用,此时导航是链接跳转,根据后端路由进行微应用切换; SPA 和 MPA 应用都可以通过其他方式来切换微应用,例如动态切换微应用的 Script 除此之外,复杂的业务场景还可以是上述几种方案的结合体 拆分的微应用需要具备独立开发、独立部署和独立运行的特性:

微应用可以采用不同的技术栈,支持独立开发; 微应用可以单独部署到不同的服务器上,支持独立部署; 微应用的运行可以不依赖其他微应用,支持独立运行。 微前端的方案有哪些? 在了解了微前端能解决哪些业务场景以及特性之后,我们可以根据项目的实际情况出发,选择合适的微前端方案。在实际开发项目的过程中,如果项目本身采用 SPA 模式进行开发,则可以通过以下方案进行微前端改造:

基于 NPM 包的微前端:将微应用打包成独立的 NPM 包,然后在主应用中安装和使用; 基于代码分割的微前端:在主应用中使用懒加载技术,在运行时动态加载不同的微应用; 基于 Web Components 的微前端:将微应用封装成自定义组件,在主应用中注册使用; 基于 Module Federation 的微前端:借助 Webpack 5 的 Module Federation 实现微前端; 基于动态 Script 的微前端:在主应用中动态切换微应用的 Script 脚本来实现微前端; 基于 iframe 的微前端:在主应用中使用 iframe 标签来加载不同的微应用; 基于框架(JavaScript SDK)的微前端:使用 single-spa、qiankun、wujie 等通用框架。 部分方案会要求微应用在主应用的构建时进行聚合,无法使微应用具备运行时的动态化能力(主应用具备线上动态新增、删除和修改微应用的能力),例如基于 NPM 包、代码分割、 传统 Web Components 和构建工具的微前端方案。而基于动态 Script 、iframe 和框架的微前端方案则可以在运行时做到动态化,例如上节课程讲解的安防管理后台系统采用的是 iframe 的方式来实现微应用聚合。

当然,一些方案还可以组合使用,例如在上节课中讲解的低代码管理后台案例,低代码的脚本可能难以在微前端框架中进行集成,因此可以采用基于框架和动态 Script 结合的微前端设计方案。

如果项目是在 MPA 的模式下,则前端应用可以天然做到小型应用的拆分,各自部署在相应的服务下,在主应用中通过服务端的路由来请求和渲染不同的微应用。当然服务端的设计方案非常多,如何实现微应用之间的通信和数据共享是需要考虑的一个重要问题。

温馨提示:MPA 模式下后端的技术栈方案非常多,可以是单个服务框架,可以是多个不同的服务框架,还可以是微服务框架。不同路由的 HTTP 请求将被分发到对应的服务上,这些服务可能是 Niginx 反向代理后的服务、Nginx 部署的静态资源服务、CDN 服务以及对象存储服务等。

采用 MPA 的模式设计微前端可以使前后端采用不同的技术框架来实现,从而解决更加宽泛的团队协作问题。同时这种方式对于整体框架的设计更加灵活多变,可以很好解决不同技术栈之间因为差异大而难以进行迁移和兼容的问题,是微前端架构中采用最多且也是最容易实现的方案。需要注意目前社区常见的微前端框架基本上都是倾向于使用 SPA 模式进行开发,主要解决的是前端应用自身的解耦问题,这个解耦的过程本身可能不涉及服务端的任何更改。


iframe 方案#

学习微前端时,iframe 方案是最“原始”但最“直接”的微前端实现方式——它利用浏览器原生的 iframe 标签,将子应用完整嵌入到主应用中。我们可以从「开发/功能/体验/性能」四个维度,清晰梳理其优劣势:


一、核心优势:原生带来的“零成本入场”#

1. 上手成本为 0,无需额外依赖#

iframe 是浏览器原生 API,无需学习任何微前端框架(如 qiankun、MicroApp)、无需配置构建工具,只需一行 <iframe src="子应用地址" /> 就能完成嵌入。适合:项目工期紧、团队对微前端技术栈不熟悉的场景。

2. 完全的环境隔离,彻底解决“应用冲突”#

这是 iframe 最大的卖点:

  • DOM 隔离:子应用的 DOM 树完全独立于主应用,不会出现“DOM 选择器冲突”;
  • CSS 隔离:子应用的 CSS(包括!important)仅作用于自身 iframe 上下文,不会污染主应用样式;
  • JS 隔离:子应用的全局变量(如windowdocument)、函数、事件循环完全独立,不会出现“变量覆盖”“定时器冲突”;
  • 路由隔离:子应用的路由(hash/history)完全自主管理,无需和主应用做路由适配。

适合:子应用是第三方系统(无法修改代码)技术栈老旧(如 jQuery) 的场景。

3. 技术栈完全无关#

主应用和子应用可以用任意技术栈:Vue3、React18、Angular、JSP、HTML 静态页… 只要能在浏览器中运行,就能嵌入 iframe。

4. 子应用可独立开发/调试/部署#

子应用无需修改任何代码,只要能通过 URL 访问,就能嵌入主应用。开发时直接调试子应用本身,嵌入后无需额外测试。


二、核心不足:原生带来的“体验天花板”#

1. 性能损耗明显#

  • 浏览器会为每个 iframe 创建独立的渲染上下文、JS 引擎实例、网络请求队列,内存/CPU 消耗是同功能单页应用的 2-3 倍;
  • iframe 的onload事件会阻塞主应用的页面加载完成时机(需等待 iframe 内容完全渲染),导致主应用“白屏时间”变长;
  • 多 iframe 场景下,资源加载(JS/CSS/图片)会出现“竞争关系”,进一步拖慢整体性能。

2. 用户体验割裂#

这是 iframe 最被诟病的点:

  • 路由不同步:子应用的路由变化不会反映到主应用的 URL 中,用户点击「浏览器后退/前进」会直接跳转到主应用的上一级路由,而非子应用的历史页面;
  • 双滚动条问题:iframe 默认会显示自身的滚动条(除非显式设置overflow: hidden),导致页面出现“主应用滚动条+iframe 滚动条”的双滚动条,用户体验混乱;
  • 弹窗/模态框问题:子应用的弹窗默认只会在 iframe 内部显示(被 iframe 边界裁剪),无法实现“全局弹窗”效果;
  • 样式一致性问题:虽然 CSS 隔离了,但主应用的主题(如主题色、字体、按钮样式)无法自动同步到子应用,需子应用单独配置,维护成本高。

3. 主/子应用通信繁琐#

iframe 的跨上下文通信只能依赖postMessageAPI,需要手动定义通信协议(如消息类型、数据格式),还需处理:

  • 跨域限制(需主/子应用配置Access-Control-Allow-Origin);
  • 消息安全性(需验证消息来源event.origin);
  • 复杂交互的时序问题(如主应用需要等待子应用初始化完成后再执行操作)。

相比其他微前端方案(如 qiankun 的initGlobalState、MicroApp 的props/on),iframe 的通信成本高很多。

4. SEO 不友好#

搜索引擎的爬虫默认不会索引 iframe 内部的内容(会将 iframe 视为“外部资源”而非主页面的一部分),如果主应用需要 SEO 优化,iframe 方案会直接导致子应用内容无法被收录。

5. 调试不便#

在浏览器开发者工具中,iframe 的 DOM、JS、网络请求会被放在独立的“Frames”面板中,调试时需要频繁切换上下文,特别是多 iframe 场景下,调试效率极低。


三、适用场景与替代方案建议#

适合用 iframe 的场景不适合用 iframe 的场景
1. 第三方系统嵌入(无法修改代码);
2. 临时嵌入老旧系统,用于过渡;
3. 对体验/性能要求极低的内部工具。
1. 面向 C 端的用户产品;
2. 主/子应用需要深度交互的场景;
3. 对 SEO 有要求的场景;
4. 多子应用嵌入的复杂场景。

补充:iframe 方案的“优化小技巧”#

如果必须使用 iframe,可以通过以下方式缓解部分问题:

  1. 设置iframe sandbox="allow-same-origin allow-scripts allow-popups"限制子应用权限;
  2. 主应用监听iframe.onload事件,在子应用加载完成后再显示,避免白屏;
  3. 子应用弹窗使用window.topwindow.parent的 DOM 容器,实现“全局弹窗”;
  4. 主应用通过iframe.contentWindow修改子应用的样式变量,实现主题同步。

小结#

在 iframe 微前端方案的示例测试中,我们可以发现:

  • 主子应用同域:可以携带和共享 Cookie,存在同名属性值被微应用覆盖的风险
  • 主子应用跨域同站:默认主子应用无法共享 Cookie,可以通过设置 Domain 使得主子应用进行 Cookie 共享
  • 主子应用跨站:子应用默认无法携带 Cookie(防止 CSRF 攻击),需要使用 HTTPS 协议并设置服务端 Cookie 的 SameSite 和 Secure 设置才行,并且子应用无法和主应用形成 Cookie 共享

了解上述几种 Cookie 的携带情况可以帮助我们更好的进行微前端业务方案的设计,Cookie 是 SSO 免登设计中非常重要的凭证数据。

npm 方案#

npm 包方案是最接近传统前端开发的“微前端实践”——本质是把「业务子模块/完整子应用」打包成 npm 包,主应用通过 npm install 引入后,直接在代码中 import 使用。

注意:它与 iframe、qiankun 等「运行时隔离/动态加载」的方案核心逻辑完全不同——npm 是「代码级静态合并」,而非「运行时独立运行」。

一、核心原理(对比传统 npm 工具包)#

传统 npm 工具包微前端 npm 包方案
封装通用工具(如 lodash)/UI 组件封装完整业务子模块(如用户中心、订单页)/独立子应用
仅暴露 API/组件暴露可直接渲染的入口组件+业务逻辑
依赖主应用的技术栈/环境理论上可独立,但实际强依赖主应用的技术栈兼容

举个例子:

  1. 子应用(如用户中心)打包为 @my-app/user-center npm 包,暴露 UserCenter 组件;
  2. 主应用执行 npm install @my-app/user-center
  3. 主应用在路由中直接引入并渲染:import UserCenter from '@my-app/user-center'

二、优势:零学习成本+极致性能#

1. 开发/集成成本为 0#

  • 完全复用现有 npm 生态和开发习惯,无需任何微前端框架/配置(不用学 qiankun、MicroApp);
  • 构建工具(Webpack/Vite)自动处理依赖安装、Tree Shaking、代码分割,和普通单页应用开发完全一致。

2. 性能天花板最高#

  • 代码级静态合并,没有任何运行时开销(无 iframe 的多渲染上下文、无 qiankun 的沙箱/动态加载);
  • 打包后自动与主应用代码合并优化,体积最小化。

3. 主/子通信“无缝”#

  • 直接通过组件 Props/函数调用通信,和普通前端组件的通信逻辑完全一致;
  • 无需postMessage、全局状态管理等额外层,复杂度极低。

4. 样式/主题统一容易#

  • 子应用的样式可以直接继承主应用的主题变量(如 CSS 变量、Tailwind 配置);
  • 配合 BEM 规范/Scoped CSS 可轻松实现样式隔离,避免 iframe 的“样式割裂”问题。

三、不足:违反微前端核心原则的“硬伤”#

npm 包方案的最大问题是:它并不符合微前端「独立部署、运行时隔离、技术栈无关」的核心定义,本质是「业务模块复用」,而非「微前端架构」。

1. 强耦合:无任何隔离性#

  • 代码耦合:子应用代码直接注入主应用代码中,JS 变量冲突、依赖版本冲突(如子应用用 React 18,主应用用 React 17)极易发生;
  • 样式耦合:若子应用未做样式隔离(如未用 Scoped),会直接污染主应用样式。

2. 无法独立部署:违反微前端核心#

  • 子应用的任何修改都需要重新打包成 npm 包
  • 主应用必须重新安装依赖、重新打包、重新部署——子应用无法单独上线,完全失去了微前端“独立迭代”的优势。

3. 技术栈强绑定:缺乏灵活性#

  • 主应用和子应用必须使用兼容的技术栈(如主应用是 Vue 3,子应用不能是 React,除非用 Web Components 包装,但复杂度陡增);
  • 构建工具(Webpack/Vite)、依赖版本(如 Node.js 版本)也必须统一,否则会出现打包错误。

4. 版本管理“地狱”#

  • 多个子应用的版本依赖会形成复杂的依赖树(如子应用 A v1.0 依赖子应用 B v2.0,主应用依赖子应用 B v3.0),极易出现版本冲突;
  • 若需回滚子应用版本,必须修改主应用的package.json并重新部署。

四、适用场景(非严格微前端场景#

npm 包方案仅适合“模块复用”,而非严格的微前端架构,典型场景:

  1. 跨应用的通用业务模块复用:如多个应用都需要的「用户中心」「支付组件」「实名认证模块」;
  2. 技术栈完全统一的团队:如全团队强制使用 Vue 3 + Vite,且有严格的代码规范(BEM/Scoped CSS)避免冲突;
  3. 对性能要求极致的 C 端场景:如核心商品页、支付页,无法接受任何运行时开销。

五、与其他微前端方案的对比(帮你选择)#

微前端方案核心类型独立部署技术栈无关隔离性性能学习成本
iframe运行时隔离0
NPM 包代码级复用0
qiankunRuntime 沙箱
Module Federation构建时动态加载
MicroApp基于 Shadow DOM

总结#

NPM 包方案不是“严格意义上的微前端”——它更像是「业务组件库」的延伸,适合模块复用场景,但如果需要独立部署、技术栈无关、运行时隔离的真正微前端,建议选择:

  • 轻量隔离:MicroApp(京东开源,基于 Shadow DOM);
  • 成熟生态:qiankun(蚂蚁开源,基于 single-spa);
  • 构建层面:Module Federation(Webpack 5 原生,适合技术栈统一场景)。

Web Components 方案:浏览器原生的「微前端隔离技术底层」#

Web Components 不是专门为微前端设计的框架,而是浏览器原生提供的「组件封装标准」——但它的**「DOM/样式天然隔离、技术栈无关、可独立运行」三大特性,刚好完美契合微前端的核心需求,因此成为了现代微前端方案的主流隔离技术底层**(比如京东开源的 MicroApp、字节的 Garfish 都用它做样式/DOM 隔离)。


一、先搞懂:Web Components 核心是什么?#

oero 它由 3 个浏览器原生 API 组成,合起来实现「封装一个可复用、隔离的自定义 HTML 元素」:

API核心功能
Custom Elements允许开发者定义自己的 HTML 标签(比如 <user-center></user-center>),并绑定生命周期(connectedCallback/attributeChangedCallback 等)
Shadow DOM为自定义元素创建「影子 DOM 树」——完全隔离于主应用的 DOM 树,样式、DOM 选择器、事件默认不会穿透
HTML Templates<template> 标签预定义可复用的 DOM 结构,只有被激活时才会渲染,避免初始 DOM 冗余

极简落地示例(以 React 子应用打包成 Web Components 为例)#

  1. 子应用端:用工具把 React 组件转成 Web Components

    Terminal window
    # 安装转换工具
    npm install @r2wc/react-to-web-component
    // 子应用:用户中心组件
    import React from 'react'
    import r2wc from '@r2wc/react-to-web-component'
    const UserCenter = () => <div className="user-center">用户中心</div>
    // 转成 Web Components 自定义元素
    customElements.define('user-center', r2wc(UserCenter))
    // 打包成独立 CDN 资源(比如 user-center.wc.js)
  2. 主应用端:直接引入并使用,无需关心子应用技术栈

    <!-- 主应用可以是 Vue/React/Angular/JSP/纯 HTML -->
    <body>
    <h1>主应用</h1>
    <!-- 直接使用自定义元素 -->
    <user-center></user-center>
    <!-- 引入子应用的 Web Components 资源 -->
    <script src="https://cdn.example.com/user-center.wc.js"></script>
    </body>

二、微前端视角:Web Components 的核心优势#

对比 iframe、npm 包等方案,它刚好弥补了前两者的「痛点」:

1. 原生隔离:比 iframe 轻,比 npm 包安全#

  • DOM 隔离:Shadow DOM 不会被主应用的 document.querySelector 选中,避免 DOM 冲突;
  • 样式隔离:子应用的 CSS 仅作用于 Shadow DOM 内部,!important 也不会污染主应用;
  • 事件隔离:子应用的事件默认不会冒泡到主应用(需显式设置 event.composed = true 才会穿透);
  • 性能:仅创建一个 Shadow DOM 上下文,比 iframe 的「多渲染上下文」轻量 80%+。

2. 技术栈无关:真正的「拿来就能用」#

主应用和子应用可以是任何技术栈(Vue/React/Angular/JSP/PHP 生成的静态页都支持),只要子应用能打包成 Web Components 即可——完全解决了 npm 包「技术栈强绑定」的问题。

3. 独立部署:符合微前端「独立迭代」的核心#

子应用打包成 Web Components 后,可以独立部署到 CDN,主应用通过 <script> 或 ESM 动态引入:

  • 子应用更新后,主应用无需重新打包、重新部署,直接加载最新 CDN 资源即可;
  • 支持灰度发布(比如给 10% 用户加载 v1.1,90% 加载 v1.0)。

4. 无依赖:浏览器原生支持,无需额外框架#

无需引入任何微前端框架(qiankun/MicroApp)或构建工具插件,现代浏览器(Chrome 64+/Safari 10+/Firefox 63+)直接支持——学习成本仅为「了解 3 个核心 API」。

5. 样式可定制:解决 iframe「样式割裂」的痛点#

Shadow DOM 虽然隔离了样式,但支持通过两种方式「穿透」修改:

  • CSS 自定义属性(CSS Variables):子应用内部使用 var(--primary-color),主应用通过 user-center { --primary-color: #1890ff; } 统一主题;
  • ::part() 伪元素:子应用给需要定制的元素添加 part 属性(比如 <button part="submit-btn">提交</button>),主应用通过 user-center::part(submit-btn) { background: red; } 修改样式。

三、微前端视角:Web Components 的核心不足#

作为原生 API,它的「底层性」决定了它的「不完美」:

1. 开发体验差:缺乏现代框架的「糖」#

  • 原生 Custom Elements 的生命周期(connectedCallback/disconnectedCallback)远不如 Vue/React 的 onMounted/onUnmounted 直观;
  • Shadow DOM 中的事件冒泡需要手动设置 event.composed = true,否则主应用无法监听;
  • 缺乏响应式、状态管理、路由等现代前端开发的核心能力,需要子应用自己实现。

2. 路由与状态同步复杂#

  • Web Components 本身没有路由能力,子应用的路由(hash/history)需要自己管理,且无法直接与主应用的路由同步(需通过 postMessage 或属性传递路由参数);
  • 跨子应用的全局状态管理需要额外设计(比如用 broadcastchannel 或主应用注入全局状态),比 qiankun 的 initGlobalState 复杂。

3. 兼容性限制#

  • IE 11 完全不支持,需要引入 @webcomponents/webcomponentsjs 等 polyfill,但 polyfill 会带来 100KB+ 的体积开销和性能损耗;
  • 部分浏览器对 Shadow DOM 的 ::part()、CSS 自定义属性穿透等特性支持不完善(比如 Safari 13 之前的版本)。

4. 调试不便#

  • Shadow DOM 的内容在 Chrome DevTools 中默认折叠显示,需要手动点击「Show Shadow DOM」才能查看;
  • 样式调试时,无法直接在 DevTools 中修改 Shadow DOM 内的样式,需通过主应用的 ::part() 或子应用代码修改后重新打包。

5. 构建复杂度高#

  • Vue/React 等框架需要额外配置才能打包成 Web Components(比如 Vue 的 build.lib.target: 'wc'、React 的 @r2wc/react-to-web-component);
  • 子应用的静态资源(图片、字体)需要处理为「绝对路径」或「base64 编码」,否则会因为 Shadow DOM 的路径隔离导致资源加载失败。

四、适用场景:「隔离优先、跨栈需求、轻量模块」#

Web Components 不是「万能微前端方案」,但在以下场景中是最优解

  1. 传统项目的微前端改造:主应用是 JSP/PHP/ASP 等老旧技术栈,无法引入现代前端框架,只能用原生 JS 实现隔离;
  2. 跨技术栈的通用组件:公司内部有 Vue/React/Angular 多个项目,需要一个统一的「支付弹窗」「实名认证」「用户中心」等组件;
  3. 第三方组件嵌入:第三方开发者提供的插件(比如广告组件、评论组件),需要嵌入到主应用中,避免污染主应用的样式和 DOM;
  4. 轻量微前端模块:子应用功能简单(比如一个按钮、一个表单),无需复杂的路由和状态管理。

五、与其他微前端方案的对比#

方案类型隔离性跨技术栈独立部署性能开发体验
iframe运行时隔离0
NPM 包代码级复用0
Web Components原生组件
qiankunRuntime 沙箱
MicroAppWeb Components 封装

关键结论:

  • 如果追求「原生、轻量、无依赖」,选原生 Web Components
  • 如果追求「现代开发体验、路由/状态同步」,选基于 Web Components 的微前端框架(比如 MicroApp,它在 Web Components 之上封装了路由同步、状态管理、样式穿透等功能)。

六、最佳实践建议#

  1. 用框架封装 Web Components:不要直接用原生 API 开发复杂子应用,推荐用 Vue 3 的 build.lib.target: 'wc' 或 React 的 @r2wc/react-to-web-component 打包;
  2. 样式穿透用 CSS 自定义属性:优先使用 var(--theme-color) 实现主题统一,比 ::part() 更兼容;
  3. 事件冒泡显式设置 composed: true:确保主应用能监听子应用的事件;
  4. 复杂场景用 MicroApp:如果需要路由同步、状态管理、性能监控等高级功能,直接用封装了 Web Components 的 MicroApp,避免重复造轮子;
  5. 兼容性处理:若需支持 IE 11,引入 @webcomponents/webcomponentsjs 并禁用 Shadow DOM(使用 mode: 'open'light DOM)。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

【微前端】1 原生方案调研
https://blog.fridolph.top/posts/2025-03-01_tinyfe/
作者
Fridolph
发布于
2025-03-01
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录