外观
前端架构设计与微前端系统学习文档
适合人群:已经能独立完成前端项目,希望掌握大型前端应用架构、模块边界、状态与数据流、设计系统、微前端和长期演进治理的学习者。
学习目标:从单应用分层开始,逐步掌握大型应用模块化、API 契约、状态治理、组件库与设计系统、微前端拆分、架构决策和迁移策略。
核对时间:2026-06-30。本文参考 React、Vue、Next.js、single-spa、Module Federation、Storybook、OpenAPI、GraphQL、W3C Design Tokens 等官方资料整理。
目录
- 前端架构设计解决什么问题
- 从单页面应用到大型前端系统
- 应用分层与目录设计
- 模块边界与依赖治理
- 路由架构与权限模型
- 状态管理架构
- 数据请求与 API 契约
- 组件架构与设计系统
- 渲染架构:CSR、SSR、SSG、Edge
- 微前端架构
- 插件化与低代码架构
- 前端 BFF 与 Node.js 中间层
- 架构决策、迁移与技术债治理
- 架构师常见反模式
- 学习路线与练习
- 官方参考资料
1. 前端架构设计解决什么问题
前端架构设计的目标不是把目录分得更漂亮,而是在业务复杂、团队变大、需求长期变化时,让系统仍然能被理解、修改、测试、发布和治理。
它主要解决:
- 代码放在哪里。
- 模块之间如何依赖。
- 页面如何组织。
- 状态放在哪里。
- 数据如何请求、缓存、失效。
- 组件如何复用。
- 业务逻辑如何沉淀。
- 多团队如何协作。
- 旧系统如何迁移。
- 技术债如何可控。
架构设计的好坏,通常不是项目第一周能看出来,而是在一年后需求变化、团队扩张、功能交叉、线上事故、框架升级时显现。
1.1 好架构的特征
好架构通常具备:
- 边界清晰。
- 依赖方向稳定。
- 公共能力可复用。
- 业务模块低耦合。
- 变更影响范围可预期。
- 测试容易写。
- 构建和发布可自动化。
- 性能和安全有治理入口。
- 新人能较快理解。
1.2 差架构的表现
常见表现:
- 页面直接调用底层请求工具。
- 业务逻辑散落在组件里。
- 全局 store 什么都放。
- 公共组件依赖业务接口。
- 任意模块都能互相 import。
- 修改一个功能影响多个页面。
- 组件库版本升级不敢动。
- 没有统一错误处理。
- 没有测试和可观测性。
- 技术债没人记录。
2. 从单页面应用到大型前端系统
2.1 小项目
小项目可以简单:
text
src/
components/
pages/
utils/
api/
App.tsx这类结构适合:
- 页面少。
- 团队小。
- 业务简单。
- 生命周期短。
2.2 中型项目
中型项目开始需要按业务组织:
text
src/
app/
pages/
features/
shared/目标是避免 components、utils、api 变成垃圾桶目录。
2.3 大型项目
大型项目要关注:
- 多业务域。
- 多团队协作。
- 组件库。
- 权限体系。
- 复杂状态。
- API 契约。
- 构建性能。
- 发布节奏。
- 监控与回滚。
可能演进为:
text
apps/
admin/
portal/
packages/
ui/
hooks/
api-client/
config-eslint/
config-ts/
design-tokens/这时架构设计已经和工程化、Monorepo、发布治理紧密相关。
3. 应用分层与目录设计
3.1 分层原则
目录不是重点,依赖方向才是重点。
推荐原则:
- 上层依赖下层。
- 下层不依赖上层。
- 业务模块通过 public API 暴露。
- 公共层不包含业务知识。
- 页面层负责组装,不承担过多业务逻辑。
3.2 示例结构
text
src/
app/
providers/
router/
styles/
main.tsx
pages/
dashboard/
user-list/
features/
user-management/
api/
model/
ui/
index.ts
order-management/
api/
model/
ui/
index.ts
entities/
user/
order/
shared/
api/
config/
lib/
ui/
types/3.3 各层职责
app:
- 应用入口。
- 全局 provider。
- 路由初始化。
- 全局样式。
- 错误边界。
pages:
- 页面级组件。
- 路由参数读取。
- 页面布局。
- 组装 feature。
features:
- 具体业务能力。
- 业务 UI。
- 业务状态。
- 业务 API。
entities:
- 领域实体。
- 实体类型。
- 实体展示组件。
- 实体基础逻辑。
shared:
- 通用 UI。
- 通用 hooks。
- 请求基础设施。
- 工具函数。
- 配置。
3.4 什么时候不需要复杂分层
不要为了“看起来架构化”强行分层。
不需要复杂分层的情况:
- 项目很小。
- 生命周期很短。
- 只有一两个开发者。
- 页面之间几乎没有复用。
- 业务仍在快速试错。
架构的目标是降低复杂度,不是制造仪式感。
4. 模块边界与依赖治理
4.1 为什么模块边界重要
没有边界的项目会逐渐变成:
text
任意页面 -> 任意组件 -> 任意 store -> 任意 api -> 任意 util短期开发很快,长期会导致:
- 代码难删。
- 复用失控。
- 模块不能独立测试。
- 无法拆包。
- 无法迁移。
- 无法并行开发。
4.2 Public API
每个业务模块只通过 index.ts 暴露公共能力:
text
features/user-management/
api/
model/
ui/
index.ts其他模块只能:
ts
import { UserTable } from "@/features/user-management";不允许:
ts
import { UserTable } from "@/features/user-management/ui/UserTable";好处:
- 内部结构可调整。
- 暴露面可控。
- 依赖关系清晰。
4.3 用工具约束边界
人工约定很容易失效。建议使用:
- ESLint
no-restricted-imports。 - 自定义 ESLint 规则。
- Nx module boundaries。
- TypeScript path aliases。
- 包级边界。
- CI 检查。
示例:
js
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
"@/features/*/*",
"!@/features/*/index"
]
}
]
}4.4 依赖方向
推荐:
text
app -> pages -> features -> entities -> shared禁止:
text
shared -> features
entities -> pages
features/order -> features/user/internal如果两个模块互相依赖,通常说明:
- 领域边界不清。
- 有公共逻辑应抽到 shared 或 entities。
- 需要通过事件、回调或 API 解耦。
5. 路由架构与权限模型
5.1 路由不是简单页面列表
大型应用路由还包含:
- 权限。
- 菜单。
- 面包屑。
- 标签页。
- 页面缓存。
- 数据预加载。
- 错误边界。
- 懒加载 chunk。
- 埋点。
5.2 路由配置示例
ts
export const routes = [
{
path: "/users",
title: "用户管理",
requiredPermissions: ["user:read"],
lazy: () => import("@/pages/user-list"),
},
{
path: "/orders",
title: "订单管理",
requiredPermissions: ["order:read"],
lazy: () => import("@/pages/order-list"),
},
];5.3 权限模型
常见模型:
- RBAC:基于角色。
- ABAC:基于属性。
- ACL:访问控制列表。
- Feature Flag:功能开关。
前端权限只能改善体验,不能替代服务端权限校验。
前端负责:
- 控制菜单显示。
- 控制按钮显示。
- 控制路由访问。
- 降低误操作。
服务端负责:
- 真实鉴权。
- 数据权限。
- 操作权限。
- 审计。
5.4 权限治理
建议:
- 权限码集中定义。
- 路由权限和按钮权限统一来源。
- 复杂权限封装成 hook / directive。
- 权限失败统一降级。
- 不在页面到处写散乱字符串。
6. 状态管理架构
6.1 状态分类
状态先分类,再选择工具。
| 状态类型 | 示例 | 推荐位置 |
|---|---|---|
| UI 状态 | 弹窗开关、tab、hover | 组件局部 |
| 表单状态 | 输入值、校验、dirty | 表单库或组件局部 |
| 服务端状态 | 列表、详情、缓存 | TanStack Query / Apollo |
| 全局业务状态 | 用户、租户、主题 | 全局 store |
| URL 状态 | 搜索条件、分页 | query string |
| 派生状态 | 过滤结果、统计值 | computed / selector |
6.2 全局 store 不应承载一切
错误做法:
- 所有接口数据放 Redux / Pinia。
- 所有弹窗状态放全局。
- 表单输入实时同步全局。
- 页面临时状态全局化。
后果:
- 更新范围过大。
- 调试困难。
- 状态生命周期混乱。
- 内存增长。
- 页面之间意外影响。
6.3 服务端状态
服务端状态特点:
- 来源在服务端。
- 可能过期。
- 需要缓存。
- 需要重新拉取。
- 可能多个组件共享。
- 有 loading / error / success 状态。
适合用 TanStack Query、SWR、Apollo Client 等工具。
6.4 状态管理选型
选择时考虑:
- 项目复杂度。
- 团队熟悉度。
- 框架生态。
- 是否需要时间旅行调试。
- 是否需要不可变数据。
- 是否需要服务端状态缓存。
- 是否支持 SSR。
不要因为某个库流行就引入。状态管理工具越多,心智负担越重。
7. 数据请求与 API 契约
7.1 请求层职责
统一请求层应负责:
- baseURL。
- headers。
- token 注入。
- token refresh。
- 错误码处理。
- 超时。
- 请求取消。
- 重试。
- 幂等。
- 日志和 trace id。
- mock。
页面不应该直接散落:
ts
fetch("/api/users").then(...)而应该通过统一客户端:
ts
const users = await userApi.listUsers(params);7.2 API 契约
常见契约方式:
- OpenAPI。
- GraphQL schema。
- tRPC。
- Protobuf。
- 手写 TypeScript 类型。
推荐优先使用可生成类型的契约:
- 后端改字段时能提前发现。
- 前端减少手写类型错误。
- mock 和文档更统一。
7.3 错误处理
错误要分层:
- 网络错误。
- 超时。
- 401 未登录。
- 403 无权限。
- 404 资源不存在。
- 409 冲突。
- 500 服务端错误。
- 业务错误码。
统一策略:
- 全局错误拦截。
- 页面局部可覆盖。
- 用户可理解的错误提示。
- 可观测性记录上下文。
- 防止重复弹窗。
7.4 缓存与失效
缓存策略要明确:
- 哪些数据可缓存。
- 缓存多久。
- 什么时候失效。
- 用户切换租户是否清空。
- 权限变化是否清空。
- 更新后是否乐观更新。
前端缓存一旦设计不清,很容易出现“页面显示旧数据”的线上问题。
8. 组件架构与设计系统
8.1 组件分层
常见组件类型:
- 基础组件:Button、Input、Modal。
- 组合组件:SearchForm、DataTable。
- 业务组件:UserSelector、OrderStatusTag。
- 页面组件:UserListPage。
基础组件不应该依赖业务接口。
业务组件可以依赖业务模型,但要控制范围。
8.2 组件 API 设计
好的组件 API:
- 命名清晰。
- 行为稳定。
- 默认值合理。
- 可组合。
- 可扩展。
- 可访问。
- 类型明确。
示例:
tsx
<DataTable
columns={columns}
dataSource={users}
rowKey="id"
loading={loading}
pagination={pagination}
onChange={handleTableChange}
/>8.3 受控与非受控
受控组件:
tsx
<Input value={value} onChange={setValue} />非受控组件:
tsx
<Input defaultValue="Ada" />组件库要明确支持哪种模式。复杂表单组件通常要支持受控模式,方便表单库集成。
8.4 设计系统
设计系统包含:
- 设计原则。
- Design Tokens。
- 组件规范。
- 交互规范。
- 内容规范。
- 可访问性规范。
- 文档站。
- 版本策略。
组件库只是设计系统的工程实现之一。
8.5 Design Tokens
示例:
json
{
"color": {
"primary": {
"value": "#1677ff"
}
},
"radius": {
"sm": {
"value": "4px"
}
}
}Tokens 可以生成:
- CSS variables。
- Tailwind theme。
- iOS / Android 变量。
- Figma variables。
9. 渲染架构:CSR、SSR、SSG、Edge
9.1 CSR
CSR 适合:
- 后台系统。
- 强交互应用。
- SEO 不重要的内部系统。
- 用户登录后使用的产品。
风险:
- 首屏依赖 JS。
- 低端设备慢。
- SEO 弱。
9.2 SSR
SSR 适合:
- 内容站。
- 电商详情页。
- SEO 关键页面。
- 需要较快首屏的页面。
风险:
- 服务端成本。
- hydration 成本。
- 缓存复杂。
- 部署复杂。
9.3 SSG
SSG 适合:
- 文档。
- 博客。
- 营销页。
- 内容稳定页面。
风险:
- 动态内容不灵活。
- 构建时间可能很长。
9.4 Edge Rendering
Edge 适合:
- 全球用户。
- 个性化程度适中。
- 低延迟要求。
- CDN 边缘可计算场景。
风险:
- 运行时限制。
- 调试复杂。
- 成本和平台绑定。
9.5 选择方法
先问:
- 是否需要 SEO?
- 首屏是否影响业务转化?
- 内容是否频繁变化?
- 用户是否全球分布?
- 页面交互是否很重?
- 团队是否有服务端部署能力?
不要为了追新把后台系统强行 SSR。
10. 微前端架构
10.1 微前端是什么
微前端是把大型前端应用拆成多个可独立开发、独立构建、独立发布的前端子应用,并在运行时或构建时集成。
它的目标是组织协作和系统演进,不是单纯技术炫技。
为什么拆分会带来复杂度
"独立发布"的反面是"集成正确性由谁保证"。单体应用里,模块间靠同一份代码、同一次构建、同一个运行时保证一致性;拆开后,这三件事同时消失:
| 原本免费获得 | 拆分后要显式设计 |
|---|---|
| 同一份依赖 | 依赖共享与版本协商(MF / externals / 多实例代价) |
| 同一次构建 | 契约测试、主/子版本兼容矩阵 |
| 同一个运行时 | JS 沙箱、全局变量污染、定时器与事件清理 |
| 同一套样式表 | 样式隔离与弹窗挂载容器 |
| 同一路由实例 | 路由同步、刷新恢复、多应用激活 |
| 同一个发布流水线 | 灰度、回滚、远程资源不可用降级 |
| 同一次错误上报 | 故障归因到哪个子应用 |
架构师评估微前端时,评估的是这七项显式成本能否被"独立发布带来的组织收益"覆盖。
10.2 适用场景
适合:
- 多团队维护同一个大应用。
- 每个业务域发布节奏不同。
- 遗留系统需要渐进迁移。
- 不同业务需要不同技术栈。
- 主应用需要平台化集成子应用。
不适合:
- 项目很小。
- 团队很小。
- 只是组件复用。
- 没有独立发布诉求。
- 子应用之间强共享状态。
10.3 常见方案
Module Federation
特点:
- Webpack 5 生态提出。
- 支持运行时加载远程模块。
- 可共享依赖。
- 适合模块级集成。
内部机制(理解这些才能解释它的边界):
- 每个参与方构建产出一个容器(container),核心是
remoteEntry.js,暴露get(module)与init(shareScope)两个方法。宿主首次import("remoteApp/Button")时:① 动态插入<script>拉取 remoteEntry;② 调container.init(__webpack_share_scopes__.default)完成共享作用域握手;③ 调container.get("./Button")拿到工厂函数并同步执行。模块"加载(异步)"与"求值(同步)"分离。 - 共享依赖协商发生在运行时:
shared配置声明version、requiredVersion、singleton、eager等,容器初始化时按 semver 在 shareScope 里选版本;版本不满足会告警并各自加载一份。singleton: true只保证"尽量单例",版本冲突时仍可能降级为多实例——React 双实例报错(Invalid hook call)就是这么来的。 - shareScope 在容器 init 时封 seal:后注册的 remote 不能向已初始化的运行时补共享模块,动态 remote 需注意初始化顺序。
- 它不提供应用级生命周期、沙箱与样式隔离——同一页面同一 JS 运行时内,全局变量、CSS 全部共享。所以严格说它是"依赖共享/模块联邦",不是完整微前端容器,常与 single-spa 类路由框架组合。
生态现状:Webpack 5 原生提供 ModuleFederationPlugin;@module-federation/enhanced(联邦 2.0)把它扩展为独立 runtime + manifest(mf-manifest.json)体系,支持 Rspack、Vite(@module-federation/vite)、Next.js,补上了多 shareScope、预取、per-shared runtime 等能力,是目前的活跃方向。
适合:
- 共享组件。
- 共享页面模块。
- 主应用动态加载远程应用。
single-spa
特点:
- 以应用生命周期为核心。
- 支持多个框架共存。
- 路由驱动子应用挂载卸载。
内部机制:
- 子应用只需导出
bootstrap/mount/unmount(可选unload)三个异步生命周期函数,主应用用registerApplication({ name, activeWhen, app })注册;single-spa 监听popstate/hashchange(并对pushState/replaceState打补丁),路由变化时卸载activeWhen不再匹配的已挂载应用,再加载并挂载新匹配的应用。 - 框架内部以应用状态机托管流程,官方
getAppStatus返回的状态包括:NOT_LOADED、LOADING_SOURCE_CODE、NOT_BOOTSTRAPPED、BOOTSTRAPPING、BOOTSTRAPPED、NOT_MOUNTED、MOUNTING、MOUNTED、UNMOUNTING、UNMOUNTED、UNLOADING、LOAD_ERROR、SKIP_BECAUSE_BROKEN;生命周期每步以 Promise 串联,任一环节 reject 会把应用标记为SKIP_BECAUSE_BROKEN而不是拖垮整页——这就是 qiankun 说"继承 single-spa 基本盘"的含义。 - 它只管"何时挂载/卸载哪个应用",不管 JS 隔离和样式隔离;隔离、加载策略等由上层(qiankun 等)补齐。
- 子应用加载失败时框架提供
addErrorHandler等钩子做降级,这是"远程资源不可用兜底"的标准落点。
适合:
- 多子应用集成。
- 技术栈共存。
- 遗留系统迁移。
iframe
特点:
- 隔离性强。
- 集成简单。
- 通信和体验成本高。
适合:
- 第三方系统。
- 安全隔离要求高。
- 低频页面。
Web Components
特点:
- 标准化组件封装。
- 跨框架复用。
- 生态和开发体验需要评估。
适合:
- 跨框架组件。
- 设计系统部分能力。
qiankun
特点(原理层):
- 构建在 single-spa 之上,补上了应用加载与隔离能力:HTML entry 加载、JS 沙箱、样式隔离。
- 子应用以 HTML 为入口:框架 fetch 子应用 HTML,解析出 JS/CSS 并劫持执行,因此子应用需暴露
bootstrap / mount / unmount生命周期。 - JS 沙箱默认开启:
experimentalStyleIsolation用选择器前缀改写作用域(@keyframes、@font-face、@import、@page不被改写,是已知限制);strictStyleIsolation把子应用容器转为 Shadow DOM 做严格隔离,但弹层挂到document.body时会脱离样式作用域,需要调整挂载容器。 - 沙箱实现分两代:快照沙箱(激活时 diff
window、卸载时还原)只支持单实例;Proxy 沙箱(legacySandbox单 window 代理 /proxySandbox独立fakeWindow)支持多实例并行。 - 子应用需配置
publicPath运行时改写(如__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__)和 UMD 输出格式,接入改造成本是所有方案里偏高的一档。
适合:
- 国内团队、Vue/React 混合存量系统的集成平台。
- 需要严格 JS 沙箱与生命周期管控的多子应用门户。
⚠️ 版本现状:生产主流仍是 qiankun 2.x(v2 文档独立维护);3.x 在积极开发中,重写为流式 HTML-entry 加载、Proxy-membrane 沙箱与原生 ESM 沙箱执行,API 与 2.x 不完全兼容。选型时以官方仓库 Roadmap 为准,不要按 2.x 假设长期 API 稳定。
wujie(无界,腾讯)
特点(原理层):
- Web Components + iframe 双沙箱:JS 运行在 iframe 内(天然获得独立的
window/document/history/location,原生隔离),UI 渲染在主应用下的自定义元素wujie-app的 Shadow DOM 内(原生 CSS 隔离)。 - 关键连接点是把 iframe 内
document的查询接口(querySelector、getElementById、head、body等)代理到 Shadow DOM,使子应用框架"以为自己在操作本页 DOM",实际渲染进主应用容器;弹窗document.body.appendChild也因此天然适配。 - iframe 的
src指向主应用域名(同域),因此主、子应用可直接访问window.parent通信;同时提供 props 注入和去中心化 EventBus 三种方式。 - 路由同步通过劫持 iframe 的
history.pushState / replaceState将子应用 URL 同步到主应用 query,浏览器刷新、前进、后退都能作用到子应用。 - 组件式接入(
<WujieVue>/startApp),无需注册表;支持子应用保活(keep-alive)、多应用同时激活、预执行;对 Vite(ESM)子应用支持较好。
适合:
- 对隔离严格性、子应用零改造、保活/多应用激活有强诉求的场景。
注意事项:Shadow DOM 会阻断全局样式(antd 等组件库的全局样式需处理)、pointer-events/层级/无障碍焦点等跨 Shadow 边界的行为需要评估。
micro-app(京东)
特点(原理层):
- 借鉴 WebComponent 思想但自实现:用 CustomElement 结合自定义样式隔离(scopecss,选择器加属性前缀改写)实现"类 WebComponent"渲染,避开原生 Shadow DOM 在部分框架(如 React 全局事件代理)下的兼容问题;原生
shadowDOM属性默认关闭,可按需开启。 - JS 沙箱有两种官方模式,可自由切换:默认 with 沙箱(
with(proxyWindow)包住子应用代码执行,配合缓存优化降低频繁代理开销);遇到 with 沙箱跑不动的场景(如 Vite/ESM 子应用)可加iframe属性切到 iframe 沙箱(隐藏 iframe 提供原生级全局隔离)。另有disable-sandbox可整体关闭沙箱(会连带失去样式/元素/路由隔离,官方不建议)。 - 子应用零改造接入:只需主应用写一个
<micro-app name="xx" url="xx">标签(子应用设置允许跨域即可),路由通过 virtual route / forward 等方式同步。 - 官方同时维护 micro-zoo(子应用侧适配层,处理 react-router 5/6、vue-router 的路由同步)。
适合:
- 希望接入成本最低、类 iframe 使用体验但不接受 iframe 隔离代价的 Vue/React 混合系统。
方案原理对比总表
| 维度 | qiankun 2.x | wujie | micro-app | Module Federation | 原生 iframe |
|---|---|---|---|---|---|
| JS 隔离 | Proxy/快照沙箱(模拟 window) | iframe 原生沙箱 | with + Proxy 沙箱 | 无(同一运行环境) | 原生,最强 |
| CSS 隔离 | 实验性前缀改写 / Shadow DOM 可选 | Shadow DOM(原生) | scopecss 选择器改写(可选开 shadowDOM) | 无,靠约定/CSS Modules | 原生,最强 |
| 集成粒度 | 应用级 | 应用级 | 应用级(组件化标签) | 模块/组件级 | 页面级 |
| 子应用改造成本 | 中(生命周期+publicPath+UMD) | 低 | 极低 | 中(构建配置 shared/exposes) | 无 |
| 路由同步 | single-spa 劫持 history | 劫持 iframe history,天然同步 | virtual route/forward 多种策略 | 自行处理 | 需 postMessage 或 sync |
| 保活/多实例 | 手动 | 内建 | 内建 keep-alive | 不适用 | 天然 |
| Vite/ESM 子应用 | 需插件适配 | 支持 | 切换 iframe 沙箱支持(with 沙箱对 ESM 受限) | Rspack 原生支持(联邦 2.0) | 无要求 |
| 共享依赖 | 手动 externals/window | iframe 同域可 window.parent | window.microApp 等注入 | 一等能力(shareScope 版本协商) | 无法共享 |
💡 选型直觉:按模块复用诉求看选 Module Federation(它本质是依赖共享机制,不是应用容器,可与其他方案叠加);按应用集成诉求看,隔离严格性与保活看 wujie,生命周期管控和生态成熟度看 qiankun,接入成本最低看 micro-app,安全边界压倒一切(不可信第三方页面)才回到 iframe。
10.4 微前端关键问题
路由
需要明确:
- 主应用负责一级路由。
- 子应用负责内部路由。
- 刷新后能否正确恢复。
- 404 如何处理。
样式隔离
方案:
- CSS Modules。
- BEM。
- Shadow DOM。
- CSS-in-JS。
- 命名空间。
不要只依赖团队自觉避免样式冲突。
各方案的隔离强度与代价:
| 方案 | 隔离原理 | 优点 | 代价 / 陷阱 |
|---|---|---|---|
| 命名空间 / BEM | 约定类名前缀 | 零运行时成本 | 靠人守规矩,全局样式(body{}、*{}、element-ui 类)照样泄漏 |
| CSS Modules | 构建期哈希类名 | 局部作用域明确 | 只管编译期,运行时全局选择器不受限;:global 会破窗 |
| 选择器前缀改写(qiankun experimental) | 运行时给子应用样式加 div[data-qiankun-xxx] 前缀 | 不改变 DOM 结构,弹窗可渲染到 body 外 | @keyframes/@font-face/@import/@page 不被改写;动态注入样式需拦截 |
| Shadow DOM(qiankun strict / wujie) | 浏览器原生作用域 | 真正物理隔离,内外都进不来/出不去 | 全局样式(组件库 CSS、主题变量)不再穿透;弹层 portal 到 body 会丢样式;::part/CSS 变量是最常用的逃生通道 |
💡 无论哪种方案都要单独处理两件事:一是弹窗/下拉/Toast 的挂载容器(组件库默认挂 document.body,隔离方案下要么改挂到子应用容器内,要么样式跟不过去),二是主题与 CSS 变量(设计系统 tokens 需要显式注入到每个子应用作用域)。
依赖共享
需要明确:
- React / Vue 是否共享。
- 版本不一致怎么办。
- 公共组件库如何升级。
- 子应用能否独立部署。
通信
通信方式:
- URL。
- CustomEvent。
- 事件总线。
- 全局 store。
- postMessage。
- shared SDK。
建议优先使用低耦合通信。子应用之间直接互调会破坏独立性。
按耦合度和适用性排一个序:
| 方式 | 耦合度 | 适合 | 不适合 / 风险 |
|---|---|---|---|
| URL(path/query) | 最低 | 可分享状态:页签、筛选、分页;刷新可恢复 | 数据量大时 URL 爆长;敏感信息入 URL 会进历史记录与日志 |
| props 注入(主→子单向) | 低 | 登录态、主题、语言、权限等初始化上下文 | 不适合高频变更数据(每次变更要重新传递) |
| 事件总线 / CustomEvent / wujie EventBus | 中 | 瞬时通知型:主题切换、用户信息刷新 | 事件名散落无契约;忘 off 就是内存泄漏;调试链路断 |
| 全局 store(initGlobalState / window.microApp / Pinia 共享) | 中高 | 主应用主导的多子应用共享状态 | 订阅方对数据结构产生隐式依赖,字段变更波及多团队 |
| postMessage | 低(跨域) | iframe、真跨域集成 | 需要自己定义消息协议 + origin 校验,否则是安全面 |
| 子应用直接 import 对方模块 / 互调方法 | 最高 | 基本没有 | 形成隐式依赖网,破坏独立部署;用 MF 共享也要以"公共包"为边界 |
💡 无论选哪种,都建议把"通信契约"版本化:事件名/字段集中定义(如 shared 包导出 MicroAppEvents 常量与 TypeScript 类型),CI 可检查,避免字符串满天飞。
发布与回滚
微前端最容易被低估的是发布治理:
- 子应用版本。
- 主应用兼容。
- 灰度策略。
- 远程资源不可用降级。
- 回滚。
- 监控归因。
10.5 微前端决策清单
在采用前回答:
- 是否真的需要独立发布?
- 是否真的有多个团队?
- 子应用边界是否清晰?
- 共享依赖如何管理?
- 样式隔离怎么做?
- 权限和登录如何统一?
- 线上故障如何定位到子应用?
- 发布失败如何回滚?
- 性能预算如何分摊?
- 本地开发体验如何保障?
11. 插件化与低代码架构
11.1 插件化适用场景
适合:
- 编辑器。
- 可视化搭建平台。
- 低代码平台。
- 数据看板。
- IDE 类产品。
- 工作流平台。
插件化核心是扩展点设计。
11.2 扩展点
常见扩展点:
- 菜单。
- 工具栏。
- 面板。
- 物料组件。
- 数据源。
- 动作。
- 校验器。
- 主题。
- 渲染器。
11.3 插件系统难点
- 插件生命周期。
- 权限控制。
- 沙箱隔离。
- 版本兼容。
- 插件通信。
- 性能。
- 安全。
- 调试。
11.4 Schema 驱动
低代码常用 schema 描述页面:
json
{
"type": "Form",
"props": {
"title": "用户信息"
},
"children": [
{
"type": "Input",
"props": {
"name": "username",
"label": "用户名"
}
}
]
}关键问题:
- schema 版本。
- schema 校验。
- 组件映射。
- 表达式安全。
- 数据绑定。
- 运行时性能。
12. 前端 BFF 与 Node.js 中间层
12.1 BFF 是什么
BFF:Backend For Frontend。它是为特定前端体验服务的后端层。
常见职责:
- 聚合多个后端接口。
- 适配前端数据结构。
- 处理 SSR 数据。
- 管理 session。
- 做权限辅助。
- 降低前端请求瀑布流。
- 对接老系统。
12.2 什么时候需要 BFF
适合:
- 页面需要聚合多个服务。
- 后端 API 不适合前端直接使用。
- SSR 需要服务端拿数据。
- 多端需要不同数据视图。
- 鉴权和 cookie 策略复杂。
不适合:
- 简单 CRUD。
- 团队没有 Node 运维能力。
- 只是为了把请求转发一遍。
12.3 BFF 风险
- 前端团队承担服务端稳定性。
- 权限边界不清。
- 逻辑重复。
- 部署复杂。
- 性能瓶颈。
BFF 要有日志、监控、测试和回滚,不应只是“前端顺手写个 Node 服务”。
13. 架构决策、迁移与技术债治理
13.1 ADR
ADR 示例结构:
md
# ADR-001 使用 pnpm workspace 管理 Monorepo
## 背景
多个前端项目需要共享组件库和工具配置。
## 备选方案
- 多仓库
- pnpm workspace
- Nx
## 决策
采用 pnpm workspace + Turborepo。
## 原因
- 团队学习成本低
- 构建缓存成熟
- 与当前包发布流程兼容
## 风险
- 需要治理依赖边界
- CI 需要调整缓存策略13.2 大型迁移
迁移原则:
- 先评估收益。
- 再做技术验证。
- 再设计双轨方案。
- 再分批落地。
- 每批可回滚。
典型迁移:
- Webpack 到 Vite。
- Vue 2 到 Vue 3。
- Babel 7 到 Babel 8。
- JS 到 TS。
- 多仓库到 Monorepo。
- 自研组件库到设计系统。
13.3 技术债分级
可以分为:
- P0:线上风险,必须立即处理。
- P1:阻塞核心业务演进。
- P2:影响开发效率或维护成本。
- P3:局部代码质量问题。
不是所有技术债都要马上还。架构师要判断债务利息和业务优先级。
14. 架构师常见反模式
14.1 为了架构而架构
表现:
- 小项目上微前端。
- 简单页面上复杂状态机。
- 两个人团队上重型 Monorepo。
- 没有复用需求就建组件平台。
原则:架构要解决真实问题。
14.2 工具替代设计
用了 Nx 不等于有模块边界。
用了 Storybook 不等于有设计系统。
用了 Module Federation 不等于有微前端治理。
工具只提供能力,架构仍需要清晰规则。
14.3 过度抽象
过度抽象会导致:
- 新人难理解。
- 修改链路变长。
- 类型复杂。
- 调试困难。
- 实际复用率低。
抽象应来自重复和变化点,不应来自预感。
14.4 忽略发布和运维
架构如果只考虑开发,不考虑发布、监控、回滚,就是半成品。
大型前端架构必须包含:
- 构建。
- 测试。
- 发布。
- 监控。
- 回滚。
- 事故处理。
15. 学习路线与练习
15.1 基础阶段
目标:理解分层和状态分类。
练习:
- 把一个小项目从
components/pages/utils重构为app/pages/features/shared。 - 画出模块依赖图。
- 区分 UI 状态、服务端状态和全局状态。
- 把请求层统一封装。
15.2 进阶阶段
目标:能设计中型项目架构。
练习:
- 设计一个后台管理系统目录结构。
- 设计路由权限模型。
- 使用 TanStack Query 或同类工具管理服务端状态。
- 用 OpenAPI 或 GraphQL schema 生成类型。
- 用 ESLint 限制跨模块 import。
15.3 高级阶段
目标:能治理多团队项目。
练习:
- 搭建 Monorepo。
- 抽离组件库。
- 建立 design tokens。
- 接入 Storybook 文档。
- 设计组件库版本发布策略。
15.4 架构师阶段
目标:能做架构决策和迁移。
练习:
- 写一份微前端是否适用的决策文档。
- 设计 Module Federation 或 single-spa 方案。
- 设计旧项目到新架构的迁移计划。
- 写 ADR。
- 定义模块边界自动化检查规则。
16. 官方参考资料
- React 官方文档:https://react.dev/
- Vue 官方文档:https://vuejs.org/
- Next.js 官方文档:https://nextjs.org/docs
- TanStack Query:https://tanstack.com/query/latest
- Redux Toolkit:https://redux-toolkit.js.org/
- GraphQL:https://graphql.org/learn/
- OpenAPI:https://www.openapis.org/
- Storybook:https://storybook.js.org/docs
- Module Federation:https://module-federation.io/
- Webpack Module Federation 概念:https://webpack.js.org/concepts/module-federation/
- single-spa:https://single-spa.js.org/docs/getting-started-overview/
- single-spa Applications API(生命周期与状态):https://single-spa.js.org/docs/api
- qiankun:https://qiankun.umijs.org/ (2.x 文档:https://v2.qiankun.umijs.org/ ;仓库:https://github.com/umijs/qiankun)
- wujie(无界):https://wujie-micro.github.io/doc/ (仓库:https://github.com/Tencent/wujie)
- micro-app(京东):https://jd-opensource.github.io/micro-app/ (仓库:https://github.com/jd-opensource/micro-app)
- MDN Web Components:https://developer.mozilla.org/en-US/docs/Web/API/Web_components
- W3C Design Tokens Community Group:https://www.w3.org/community/design-tokens/
- pnpm Workspaces:https://pnpm.io/workspaces
- Nx Module Boundary Concepts:https://nx.dev/features/enforce-module-boundaries