引言
多个团队各自独立开发、独立部署,再组合成一个完整应用——微前端解决的是"组织分工"而非"技术炫技"。Vite 的原生 ESM 架构让微前端比 webpack 时代更顺滑:不需要运行时打包器,可以直接共享模块。本文从诉求出发,讲清三种主流方案的取舍(Module Federation、qiankun、import maps),给出 Vite + Module Federation 的完整配置,再讲 qiankun 与 Vite 的兼容坑(这是 Vite 微前端最常踩的点),接着讲样式/路由隔离与共享依赖的版本策略,最后给构建与部署的最佳实践。
前置:/vite-config-guide/(配置基础)、/vite-build-optimization/(构建与产物)、/vite-framework-integration/(框架生态)。架构背景见 [[architecture]]、[[frontend]]。
目录
- 1. 微前端解决什么:独立交付与技术异构
- 2. 三条技术路线总览
- 3. Module Federation 核心概念
- 4. Vite + Module Federation 实战配置
- 5. qiankun 与 Vite:兼容与取舍
- 6. import maps 与原生 ESM 微前端
- 7. 样式隔离与路由隔离
- 8. 共享依赖:版本管理与重复加载
- 9. 构建与部署策略
- 10. 速查表与一句话记忆
- 延伸阅读
1. 微前端解决什么:独立交付与技术异构
微前端的核心价值不是"拆代码",而是组织解耦:
- 独立开发:各团队用自己的栈、自己的节奏
- 独立部署:子应用单独上线,不互相阻塞
- 独立故障:一个子应用崩了不影响整站(理想态)
- 技术异构:React/Vue/Svelte 共存(团队自由)
微前端的成本(必须诚实面对):
- 运行时集成复杂(样式/路由/状态隔离)
- 依赖重复加载或版本冲突
- 调试链路变长、性能有损耗
- 需要统一的基建(平台/脚手架/发布规范)
何时该用微前端:团队规模 ≥ 3 个独立交付组、且无法单仓协作时,才值得;小团队强上微前端是过度设计——先问组织问题,再谈技术方案。
记忆:微前端是"组织解耦"的工具,不是"代码拆分"的花活——3 个以上独立交付组才值得,成本在样式/路由/依赖隔离。
2. 三条技术路线总览
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Module Federation | 运行时共享模块(webpack 原生 / Vite 插件) | 依赖可共享去重、技术异构、成熟 | 需要运行时加载、配置复杂 |
| qiankun | 应用沙箱 + 手动/自动加载(单例) | 隔离强、接入老项目容易 | 与 Vite 需兼容处理 |
| import maps + 原生 ESM | 浏览器原生模块映射、无运行时 | 轻量、零构建器依赖 | 共享依赖控制弱 |
选择原则:
- 偏工程化、要共享依赖去重 → Module Federation
- 要强沙箱隔离、接入既有系统 → qiankun
- 最简单、所有子应用同一构建体系 → import maps
Vite 的特殊性:Vite 产物是原生 ESM(<script type="module">),天然贴合 import maps;而 Module Federation 需要插件把 Vite 的 ESM 产物改造成"可联邦"的格式。
记忆:MF 管依赖共享、qiankun 管强隔离、import maps 走原生 ESM——Vite 的原生 ESM 让第三条路尤其顺。
3. Module Federation 核心概念
Module Federation(MF) 的四个角色:
Host(主应用):运行时加载远程模块
Remote(子应用):暴露模块给主应用消费
Shared(共享依赖):双方声明同一个依赖 → 只加载一次
Expose(暴露):Remote 对外提供哪些模块
关键机制:
- Remote 把"expose 的模块"打包成独立产物,运行时通过 manifest 暴露
- Host 通过 import('remoteName/module') 远程加载
- Shared:双方都声明 react → 加载一次、共享实例
为什么共享实例重要:React/Vue 若加载两份实例,hooks 状态、上下文会撕裂——共享依赖不是"省体积",是"保证正确性"。
// 概念示意:Host import 远程模块
const RemoteButton = await import("remote1/Button");
// remote1 在运行时提供 Button,且共享同一份 react
记忆:MF 四角色——Host/Remote/Expose/Shared;Shared 共享依赖是为了"单一实例"的正确性,不只是省体积。
4. Vite + Module Federation 实战配置
Vite 用 @originjs/vite-plugin-federation 实现 MF:
Remote(子应用)暴露模块:
// vite.config.js — remote 侧
import federation from "@originjs/vite-plugin-federation";
export default {
plugins: [
federation({
name: "remote1",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/components/Button.jsx",
"./Store": "./src/store.js",
},
shared: ["react", "react-dom"],
}),
],
build: { target: "esnext" }, // MF 需要 ESM target
};
Host(主应用)加载远程模块:
// vite.config.js — host 侧
export default {
plugins: [
federation({
name: "host",
remotes: {
remote1: "http://localhost:5001/assets/remoteEntry.js",
},
shared: ["react", "react-dom"],
}),
],
};
Host 代码里动态加载:
// 主应用按路由懒加载远程模块
const RemoteButton = React.lazy(() => import("remote1/Button"));
function App() {
return (
<Suspense fallback={<div>加载子应用…</div>}>
<RemoteButton />
</Suspense>
);
}
部署注意:remoteEntry.js 的路径要指向运行时可达的地址(CDN/对象存储),且要正确配置 base。
记忆:Vite MF 靠
@originjs/vite-plugin-federation——Remote 用 exposes 暴露、Host 用 remotes 引入、两边 shared 声明去重、产物 target 必须 esnext。
5. qiankun 与 Vite:兼容与取舍
qiankun 依赖 import-html-entry 去解析子应用的 HTML——webpack 产物(UMD)天然兼容;Vite 的 ESM 产物默认不兼容,要额外处理。
Vite + qiankun 的三个坑与解法:
坑1:Vite 产物是 ESM,qiankun 的沙箱基于 script 标签注入
解法:子应用用 vite-plugin-qiankun 或在 main.tsx 里手动判断
if (window.__POWERED_BY_QIANKUN__) 走微前端模式
坑2:生命周期函数要在全局暴露(bootstrap/mount/unmount)
解法:子应用入口导出 qiankun 要求的生命周期
坑3:publicPath 动态化(子应用资源路径随运行时变化)
解法:__webpack_public_path__ 在 Vite 里用 import.meta.env.BASE_URL 配合
子应用入口适配 qiankun:
// main.tsx(示意)
import { createRoot } from "react-dom/client";
function render(props) {
const root = createRoot(props.container || document.getElementById("root"));
root.render(<App />);
}
if (window.__POWERED_BY_QIANKUN__) {
// 微前端模式:不自动挂载,等 qiankun 调用
} else {
render({});
}
export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount(props) { /* 清理 */ }
取舍:qiankun 沙箱隔离强(JS/样式双沙箱),但 Vite 配合要加插件与生命周期适配;如果团队可控、追求 Vite 原生体验,MF/import maps 更顺。
记忆:qiankun 为 webpack 而生,Vite 接入要处理三件事——
__POWERED_BY_QIANKUN__分支、生命周期导出、动态 publicPath;隔离强但适配有成本。
6. import maps 与原生 ESM 微前端
import maps 是浏览器原生能力:把模块名映射到 URL,运行时解析。
<!-- index.html -->
<script type="importmap">
{
"imports": {
"react": "https://cdn.example.com/react/18.2.0/index.js",
"react-dom": "https://cdn.example.com/react-dom/18.2.0/index.js",
"app1": "https://cdn.example.com/apps/app1/index.js",
"app2": "https://cdn.example.com/apps/app2/index.js"
}
}
</script>
<script type="module">
import * as App1 from "app1";
import * as App2 from "app2";
</script>
Vite + import maps 的组合:
- 主应用:一个 index.html 挂 import map,加载各子应用入口
- 子应用:构建成 ESM 产物(Vite 默认),部署到 CDN
- 共享依赖:统一映射到同一个 URL(react/react-dom 只加载一份)
优缺点:
优点:
- 零运行时、纯浏览器原生、最轻
- 构建解耦彻底(各子应用独立 build)
- 依赖通过 import map 统一版本
缺点:
- 共享依赖管理靠"约定"(没人保证版本一致)
- 子应用间通信要自己定义(window 事件/自定义事件)
- import map 本身要部署方维护(配置即发布)
适合:团队统一、构建体系一致、依赖可控的内部系统——是 Vite 微前端里最省事的路线。
记忆:import maps 是"浏览器的依赖注入"——Vite ESM 产物天然适配;零运行时但共享依赖靠约定、要自己管配置发布。
7. 样式隔离与路由隔离
样式隔离(微前端最大痛之一):
CSS 全局污染:子应用 A 的 .title 会盖到子应用 B
解法阶梯:
1. 命名约定(BEM / 子应用前缀 .app1-)
2. CSS Modules / scoped(构建期隔离,推荐)
3. Shadow DOM(qiankun 可配,彻底但限制全局样式)
// Vite 里用 CSS Modules:默认开箱即用
import styles from "./Card.module.css";
<div className={styles.title}>...</div>;
路由隔离:子应用的路由要挂在主应用的路径前缀下(/app1/*),避免互相覆盖:
主应用路由:
/ → 首页
/app1/** → 子应用 1(React Router basename=/app1)
/app2/** → 子应用 2(Vue Router base=/app2)
// 子应用 1:basename 对齐主应用前缀
<BrowserRouter basename="/app1">
<Routes>...</Routes>
</BrowserRouter>
状态/通信:跨子应用共享状态用 自定义事件 + 事件总线 或统一 store(挂 window 或联邦共享):
// 轻量通信:自定义事件
window.dispatchEvent(new CustomEvent("app:navigate", { detail: "/orders" }));
window.addEventListener("app:navigate", handler);
记忆:样式隔离优先 CSS Modules/BEM 约定、路由用前缀 basename 对齐、跨应用通信走自定义事件——三件套是微前端的日常。
8. 共享依赖:版本管理与重复加载
共享依赖的核心问题:React 加载两份实例 → 状态撕裂;版本冲突 → 行为不确定。
共享策略:
策略1:完全共享(shared 声明)→ 单一实例,但要"版本兼容约定"
策略2:版本锁定(import map / shared 的版本范围)→ 主应用定版本,子应用遵循
策略3:不共享 → 各自加载(体积大、实例撕裂风险高,尽量避免框架级依赖)
MF 的 shared 版本控制:
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
}
// singleton: 强制单一实例;requiredVersion: 版本兼容校验
依赖治理最佳实践:
- 框架级依赖(react/vue)必须 singleton 共享
- 工具库(lodash)可共享可独立(非状态敏感)
- 版本差异大时升级齐平,别"各自锁死"
- 用构建产物分析(bundle analyzer)检查是否重复加载
// vite 分析重复依赖
// @originjs/vite-plugin-federation 的 shared 不生效时 → 检查双方版本
记忆:共享依赖三策——框架级必须 singleton、工具库宽松、版本差异靠约定升级齐平;
requiredVersion校验 + bundle 分析防重复。
9. 构建与部署策略
构建:
- 子应用独立 build:各自 CI/CD、各自产物(remoteEntry.js + chunk)
- MF 子应用要部署"remoteEntry + 全部 chunk"到同一路径
- 主应用只引 remoteEntry.js 的运行时地址
部署拓扑:
方案A:全 CDN(推荐)
每个子应用产物发布到 CDN 独立目录
remoteEntry.js 指向 CDN 绝对 URL
主应用静态部署,运行时动态加载远程
方案B:单一对象存储 + 入口页
各子应用前缀目录(/app1/assets/...)
主应用 index.html 挂载
发布顺序与版本:
- 向后兼容:子应用新版本要兼容老主应用(接口/依赖不破)
- 灰度:先发布子应用灰度,验证后再扩大
- 回滚:remoteEntry.js 指到旧版本目录即可回滚
示例部署(GitHub Actions + CDN):
- name: 构建子应用
run: npm run build
- name: 上传到 CDN 目录
run: aws s3 sync dist/ s3://app-cdn/apps/remote1/${{ github.sha }}/
- name: 更新 remoteEntry 指向
run: echo "s3://app-cdn/apps/remote1/current -> ${{ github.sha }}" # 或用符号/JSON 配置
记忆:微前端部署 = 子应用独立构建发布 + CDN 统一承载 + remoteEntry 指向运行时地址;版本兼容向后、灰度发布、目录级回滚。
10. 速查表与一句话记忆
| 需求 | 方案 |
|---|---|
| 依赖共享去重 | Module Federation(shared singleton) |
| 强沙箱隔离 | qiankun(要适配 Vite) |
| 最简单原生 | import maps + ESM |
| 样式隔离 | CSS Modules / BEM 前缀 |
| 路由隔离 | 子应用 basename 前缀 |
| 跨应用通信 | 自定义事件 / 事件总线 |
| 框架依赖 | singleton 共享、requiredVersion 校验 |
| 部署 | 子应用独立发布 + CDN remoteEntry |
一句话记忆:微前端解决组织解耦而非代码炫技——三条路线按需选:MF 管共享依赖去重、qiankun 管强隔离但 Vite 要适配三件套、import maps 走原生 ESM 最省事;样式用 CSS Modules/前缀、路由用 basename 对齐、通信走自定义事件;框架依赖必须 singleton 共享;构建独立发布 + CDN remoteEntry、版本向后兼容——团队规模不够就别上微前端,上了就按这套治理。
延伸阅读
- /vite-config-guide/ — base 与构建配置
- /vite-build-optimization/ — 产物分析与重复依赖
- /vite-framework-integration/ — 多框架集成
- /vite-monorepo-architecture/ — 与 Monorepo 的搭配
- [[architecture]] — 前端架构演进与模块化
- [[devops]] — 独立部署与 CDN 发布
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。