微前端技术培训大纲

版本: v1.0 最后更新: 2026-02-11 适用对象: 前端开发团队(初级 → 高级) 培训目标: 让每个梯队的开发者都能在微前端体系中高效工作,避免"设计优雅、落地艰难"


培训设计原则

  1. 分层递进 — 初级能接入、中级能理解、高级能扩展排障
  2. 每章必有产出 — 不是"听完就忘",每章结尾有 Checklist + 动手练习
  3. 代码即文档 — 所有示例直接取自项目真实代码,不造假例子
  4. 避免过度抽象 — 先讲"怎么做",再讲"为什么",最后讲"还能怎么做"

梯队定义

梯队画像培训后预期
L1 初级刚接触微前端,能写业务页面能独立完成子应用页面开发,知道数据从哪来、事件往哪发
L2 中级1 年+前端经验,理解 Vue 3 生态能独立接入新子应用,能排查跨应用通信问题,理解架构决策
L3 高级架构能力,能做技术决策能扩展基础包能力,能评估架构演进方案,能 Code Review 微前端相关 PR

第一部分:Wujie 框架基础(L1-L3)

1.1 微前端是什么、为什么选 Wujie

目标受众: L1-L3 | 时长: 30min | 形式: 讲解 + 演示

教学内容:

  • 微前端解决的核心问题:巨石应用拆分、技术栈异构、独立部署
  • 主流方案对比(qiankun / micro-app / wujie / Module Federation)
  • 为什么选 wujie
    • iframe 级 JS 沙箱(天然隔离,无变量污染)
    • WebComponent 级 CSS 沙箱(shadowDOM 隔离)
    • 预加载 + 预执行(首屏性能优化)
    • alive 保活模式(切换不销毁,状态保留)
    • 通信机制简单(props 注入 + EventBus)

关键图示:

┌─────────────────────────────────────────────┐
│                  主应用 (main)                │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ 导航菜单  │  │ Tab 栏   │  │ 用户信息  │  │
│  └──────────┘  └──────────┘  └──────────┘  │
│  ┌─────────────────────────────────────────┐│
│  │        wujie 容器 (WebComponent)        ││
│  │  ┌───────────────────────────────────┐  ││
│  │  │  iframe 沙箱 (JS 隔离)            │  ││
│  │  │  ┌─────────────────────────────┐  │  ││
│  │  │  │  子应用 Vue 实例             │  │  ││
│  │  │  │  (独立 Router/Pinia/i18n)   │  │  ││
│  │  │  └─────────────────────────────┘  │  ││
│  │  └───────────────────────────────────┘  ││
│  └─────────────────────────────────────────┘│
└─────────────────────────────────────────────┘

Checklist:

  • 能说出 wujie 的 3 个核心优势
  • 能画出主应用与子应用的容器关系图
  • 理解 iframe 沙箱 vs WebComponent 沙箱的职责分工

1.2 Wujie 核心 API 与运行模式

目标受众: L1-L3 | 时长: 45min | 形式: 讲解 + 代码走读

教学内容:

核心 API(5 个即可上手)
API作用使用场景
setupApp预配置子应用(不加载)主应用初始化时批量注册
startApp启动并渲染子应用路由匹配时加载
preloadApp预加载子应用资源空闲时提前加载
bus.$emit发送事件跨应用通信
bus.$on监听事件跨应用通信
三种运行模式
模式特点适用场景项目使用
alive(保活)切走不销毁,切回恢复状态表单填写中切换 Tab✅ 默认使用
单例每次切换重新渲染无状态页面未使用
重建每次销毁重建需要完全重置未使用

⚠️ alive 模式的关键陷阱:

主应用切换路由 → URL 变化 → 但子应用 Vue Router 不会自动跟随!
原因:alive 模式下子应用实例未销毁,iframe 内的 router 是独立的
解决:主应用通过 bus.$emit('ROUTE_CHANGE_TO_CHILD') 通知子应用 router.push

代码走读(取自项目真实代码):

  • apps/main/src/config/wujie.ts — 主应用 wujie 配置
  • packages/micro-bootstrap/src/index.ts — 子应用启动器
  • apps/main/src/stores/tabs.ts — alive 模式下的路由同步

Checklist:

  • 能说出 alive 模式下路由不同步的原因和解决方案
  • 能区分 setupApp / startApp / preloadApp 的调用时机
  • 知道 bus.$emit 是同步的,需要 setTimeout 避免 history.state 冲突

1.3 Wujie 沙箱原理(L2-L3 进阶)

目标受众: L2-L3 | 时长: 30min | 形式: 原理讲解 + 源码片段

教学内容:

  • JS 沙箱:iframe 天然隔离
    • 子应用 JS 在 iframe 中执行,window/document 天然隔离
    • 与 qiankun 的 Proxy 沙箱对比:无需 snapshot/restore,无边界 case
  • CSS 沙箱:WebComponent shadowDOM
    • 子应用 DOM 渲染在 shadowRoot 内,样式天然隔离
    • teleported: false 的必要性(弹窗逃逸问题)
  • 通信机制:props 注入 + EventBus
    • props:主应用 → 子应用的冷启动数据(同步,一次性)
    • bus:双向实时通信(异步,持续监听)
  • 降级策略:不支持 WebComponent 时的 fallback

Checklist:

  • 能解释为什么 Element Plus 弹窗需要 teleported: false
  • 能说出 props 和 bus 的区别和各自适用场景
  • 理解 iframe 沙箱为什么比 Proxy 沙箱更可靠

第二部分:CMCLink 基础包设计(L2-L3)

2.1 Monorepo 包架构总览

目标受众: L2-L3 | 时长: 30min | 形式: 架构讲解 + 依赖关系图

教学内容:

cmclink-web-main/
├── apps/                          # 应用层
│   ├── main/                      # 主应用(壳)
│   ├── doc/                       # 单证子应用
│   └── ibs-manage/                # IBS 管理子应用
│
├── packages/                      # 基础包层
│   ├── micro-bridge/              # 🔑 通信 SDK(事件类型 + bus 封装)
│   ├── micro-bootstrap/           # 🔑 子应用启动器(一行代码接入)
│   ├── api/                       # HTTP 请求 SDK(插件式 Axios)
│   ├── hooks/                     # 跨应用共享 Composables
│   ├── types/                     # 共享 TypeScript 类型
│   ├── utils/                     # 工具函数
│   ├── vite-config/               # Vite 配置工厂
│   └── icons/                     # 共享 SVG 图标
包依赖关系
apps/main ──→ micro-bridge(通信)
           ──→ api(HTTP 请求)
           ──→ hooks / utils / types

apps/doc ──→ micro-bootstrap(启动器)──→ micro-bridge(通信)
          ──→ api(HTTP 请求)
          ──→ hooks / utils / types

apps/ibs-manage ──→ (同 doc)
每个包的职责边界(一句话说清)
一句话定位不该放什么
micro-bridge跨应用通信的类型和工具不放业务逻辑
micro-bootstrap子应用一行代码启动不放通信逻辑
apiHTTP 请求 + 公共接口定义不放 UI 组件依赖
hooks≥2 个应用共享的 Composable不放依赖应用实例的 hook
types≥2 个应用共享的纯类型不放运行时代码

Checklist:

  • 能画出包依赖关系图
  • 能说出每个包的一句话定位
  • 知道新代码该放哪个包(决策树)

2.2 @cmclink/micro-bridge — 通信 SDK 详解

目标受众: L2-L3 | 时长: 45min | 形式: 代码走读 + 设计决策讲解

教学内容:

通信统一使用 wujie bus

决策:项目统一使用 wujie bus 作为跨应用通信机制,不再使用 CustomEvent。 原因:wujie bus 是框架原生能力,无需额外封装,且与 props 注入天然配合。

通信模式全景
┌──────────────────────────────────────────────────────────┐
│                    通信模式全景                            │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  冷启动数据(一次性)         热更新数据(持续监听)         │
│  ┌─────────────────┐        ┌─────────────────────┐     │
│  │  wujie props     │        │  wujie bus           │     │
│  │  主应用 → 子应用  │        │  双向通信             │     │
│  │                  │        │                      │     │
│  │  $shared.auth    │        │  SHARED:AUTH_UPDATED  │     │
│  │  $shared.dict    │        │  SHARED:DICT_UPDATED  │     │
│  │  $shared.locale  │        │  SHARED:LOCALE_CHANGED│     │
│  └─────────────────┘        │  SHARED:TOKEN_REFRESHED│    │
│                              │  SHARED:LOGOUT         │    │
│                              │                        │    │
│                              │  TAB_ADD / TAB_REMOVE   │    │
│                              │  ROUTE_CHANGE_TO_CHILD  │    │
│                              └────────────────────────┘    │
└──────────────────────────────────────────────────────────┘
事件类型定义(编译时安全)
// packages/micro-bridge/src/types.ts
export interface MicroBridgeEventMap {
  'SHARED:AUTH_UPDATED': { token?: string; permissions?: string[]; roles?: UserRoleType[]; ... }
  'SHARED:DICT_UPDATED': { dictMap: Record<string, DictItem[]>; version: number }
  'SHARED:LOCALE_CHANGED': { lang: 'zh-CN' | 'en' }
  'SHARED:TOKEN_REFRESHED': { token: string; refreshToken: string }
  'SHARED:LOGOUT': void
}

// 事件名常量(编译时校验,拼写错误立即报错)
export const SHARED_EVENTS = {
  AUTH_UPDATED: 'SHARED:AUTH_UPDATED',
  ...
} as const satisfies Record<string, keyof MicroBridgeEventMap>
SharedProps 类型(子应用读 props 有提示)
import type { SharedProps } from '@cmclink/micro-bridge'

// 子应用中使用
const shared = (window as any).__WUJIE?.props?.$shared as SharedProps
console.log(shared.auth.token)       // ✅ 有类型提示
console.log(shared.dict.dictMap)     // ✅ 有类型提示
文件职责
文件职责谁用
types.ts事件类型 + SharedProps + SHARED_EVENTS 常量所有包
child-bridge.ts子应用侧通信封装(emit/goLogin/closeTab)子应用
main-bridge.ts主应用侧事件监听(on/off/destroy)主应用
tags-view-proxy.tsTab 操作代理(子应用 → 主应用)子应用
registry.ts子应用注册表(名称/入口/端口)主应用
url.ts子应用 URL 生成(dev/prod 自动切换)主应用

Checklist:

  • 能说出 props 和 bus 各自传递哪些数据
  • 知道 SHARED_EVENTS 常量的作用(编译时拼写校验)
  • 能在子应用中正确读取 SharedProps 并获得类型提示

2.3 @cmclink/micro-bootstrap — 子应用启动器

目标受众: L1-L2 | 时长: 20min | 形式: 代码走读

教学内容:

一行代码接入微前端
// apps/doc/src/main.ts — 子应用入口
import { bootstrapMicroApp } from '@cmclink/micro-bootstrap'
import App from './App.vue'
import router from './router'
import { setupI18n } from '@/plugins/vueI18n'
import { setupElementPlus } from '@/plugins/elementPlus'

bootstrapMicroApp({
  app: App,
  router,
  appId: '#app',
  appName: 'doc',
  plugins: [setupI18n, setupElementPlus],
})
bootstrapMicroApp 内部做了什么
1. createApp(RootComponent)           — 创建 Vue 实例
2. createPinia() + persistedstate     — 初始化状态管理
3. app.use(router) + app.use(pinia)   — 安装核心插件
4. createChildBridge({ appName })     — 创建通信桥接器
5. setupPopupIsolation(app)           — 弹窗隔离(teleported: false)
6. for (plugin of plugins) await plugin(app)  — 安装自定义插件
7. window.mount / window.unmount      — 暴露生命周期给 wujie
8. mount()                            — 挂载应用

Checklist:

  • 能用 bootstrapMicroApp 启动一个新子应用
  • 知道 autoPopupIsolation 的作用
  • 知道 plugins 数组的执行顺序

2.4 @cmclink/api — HTTP 请求 SDK

目标受众: L1-L2 | 时长: 20min | 形式: 代码走读

教学内容:

两层架构
内容谁用
Layer 1createRequest — 插件式 Axios 工厂所有应用必须使用
Layer 2API 模块工厂(createAuthApi 等)按需使用(仅跨应用共享接口)
子应用接入示例
import { createRequest } from '@cmclink/api'

const request = createRequest({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 30000,
  plugins: {
    auth: {
      getAccessToken: () => shared.auth.token,  // 从 wujie props 读取
      getRefreshToken: () => '',                 // 子应用不负责刷新
      setToken: () => {},
      removeToken: () => {}
    },
    ui: { showMessage, showConfirm, showNotification },
    logout: {
      onUnauthorized: () => bridge.goLogin()     // 通知主应用跳转登录
    }
  }
})
接口放哪里?
接口是否 ≥2 个应用调用?
  ├── 是 → @cmclink/api/modules/(如 auth、dict、file)
  └── 否 → 当前应用 src/api/(如订单管理、航线查询)

Checklist:

  • 能用 createRequest 创建子应用的请求实例
  • 知道 Token 从 wujie props 读取,不从 localStorage 读
  • 知道新接口该放 @cmclink/api 还是应用内 src/api/

第三部分:主应用设计(L2-L3)

3.1 主应用职责与架构

目标受众: L2-L3 | 时长: 30min | 形式: 架构讲解

教学内容:

主应用是"壳",不是"巨石"

主应用只负责 4 件事:

职责实现文件
认证登录/登出/Token 管理stores/user.ts, utils/auth.ts
路由菜单渲染 + 子应用容器切换router/, App.vue
共享状态广播watch store → bus.$emitstores/shared-provider.ts
Tab 管理统一 Tab 栏 + 路由同步stores/tabs.ts
shared-provider.ts — 共享状态广播核心
主应用 Pinia Store 变化
  → watch 检测
    → bus.$emit(SHARED_EVENTS.XXX, payload)  // 热更新
    → 同时更新 wujie props.$shared           // 冷启动

设计决策:

  • 系统级数据(auth/dict/locale)由主应用独占维护
  • 子应用只读消费,不需要额外抽象层
  • 删除了 shared-stores 包(过度设计)

Checklist:

  • 能说出主应用的 4 个核心职责
  • 理解 shared-provider.ts 的 watch → emit 模式
  • 知道为什么删除了 shared-stores 包

3.2 Tab 管理与路由同步(L2-L3 重点)

目标受众: L2-L3 | 时长: 30min | 形式: 代码走读 + 场景演练

教学内容:

这是微前端最复杂的部分
用户点击主应用 Tab
  → 主应用 router.push('/doc/document/blManage')
  → wujie URL 变化,但 alive 模式下子应用 router 不跟随
  → 主应用 setTimeout → bus.$emit('ROUTE_CHANGE_TO_CHILD', { path })
  → 子应用监听 → router.push(childPath)

为什么需要 setTimeout?

  • wujie bus.$emit 是同步的
  • 如果不延迟,主应用和子应用的 router 会同时操作 history.state
  • 导致 history.state 冲突,路由行为异常
TagsView 代理模式
子应用打开新页面
  → tagsViewStore.addView(route)
  → createTagsViewProxy 拦截
  → bus.$emit('TAB_ADD', { appName, title, path, childPath })
  → 主应用 tabs.ts 监听 → 添加 Tab 到统一 Tab 栏

Checklist:

  • 能解释 alive 模式下路由不同步的原因
  • 知道 setTimeout 延迟的必要性
  • 能说出子应用 Tab 操作如何代理到主应用

第四部分:子应用接入实战(L1-L2)

4.1 新子应用接入 Checklist

目标受众: L1-L2 | 时长: 60min | 形式: 动手实操

教学内容:

接入三步走

Step 1: 注册子应用

// packages/micro-bridge/src/registry.ts
export const microAppRegistry: MicroAppConfig[] = [
  // ... 已有子应用
  {
    name: 'my-app',           // 子应用名称
    entry: '/my-app/',        // 入口路径
    activeRule: '/my-app',    // 路由匹配前缀
    port: 3008,               // 开发端口
  },
]

Step 2: 创建子应用

apps/my-app/
├── package.json              # 依赖 @cmclink/micro-bootstrap + @cmclink/api
├── vite.config.ts            # 使用 createChildAppConfig
├── .env.dev                  # VITE_BASE_URL=https://dev-api.example.com
├── src/
│   ├── main.ts               # bootstrapMicroApp(...)
│   ├── App.vue
│   ├── router/index.ts
│   ├── api/                   # 业务特有接口
│   ├── config/axios/index.ts  # createRequest + 插件注入
│   └── views/

Step 3: 主应用配置

// apps/main/vite.config.ts — 添加环境变量
// .env.dev
VITE_MY_APP_APP_URL=http://localhost:3008
子应用 main.ts 模板
import { bootstrapMicroApp } from '@cmclink/micro-bootstrap'
import App from './App.vue'
import router from './router'
import { setupI18n } from '@/plugins/vueI18n'
import { setupElementPlus } from '@/plugins/elementPlus'

bootstrapMicroApp({
  app: App,
  router,
  appId: '#app',
  appName: 'my-app',
  plugins: [setupI18n, setupElementPlus],
})
子应用 vite.config.ts 模板
import { createChildAppConfig } from '@cmclink/vite-config/child'

export default createChildAppConfig({
  appName: 'my-app',
  port: 3008,
})
子应用读取共享数据
import type { SharedProps } from '@cmclink/micro-bridge'

// 冷启动:从 wujie props 读取
const shared = (window as any).__WUJIE?.props?.$shared as SharedProps | undefined
const token = shared?.auth?.token || ''
const dictMap = shared?.dict?.dictMap || {}

// 热更新:监听 bus 事件
const bus = (window as any).__WUJIE?.bus
bus?.$on('SHARED:AUTH_UPDATED', (data) => {
  // 更新本地 token
})
bus?.$on('SHARED:DICT_UPDATED', (data) => {
  // 更新本地字典缓存
})

动手练习:

  • 创建一个 hello-world 子应用并在主应用中渲染
  • 在子应用中读取主应用传递的用户信息
  • 在子应用中发送一个 Tab 操作到主应用

4.2 子应用常见问题排查(L1-L3)

目标受众: L1-L3 | 时长: 30min | 形式: 问题清单 + 解决方案

#问题现象原因解决方案
1子应用弹窗样式错乱弹窗 teleport 到 document.body,逃逸出 shadowDOM确认 autoPopupIsolation: true(默认开启)
2子应用切换后状态丢失未使用 alive 模式检查 wujie 配置是否设置 alive: true
3子应用路由不跟随主应用alive 模式下 router 独立确认监听了 ROUTE_CHANGE_TO_CHILD 事件
4子应用 API 请求 401Token 未正确传递props.$shared.auth.token 读取,不从 localStorage
5子应用静态资源 404base path 配置错误检查 vite.config.ts 的 base 和 .envVITE_BASE_PATH
6子应用字典数据为空冷启动时字典未加载完先读 $shared.dict.version,为 0 则等待 SHARED:DICT_UPDATED
7两个子应用样式互相影响CSS 变量泄漏检查是否有全局 :root 样式,改为 scoped
8bus 事件监听不到事件名拼写错误使用 SHARED_EVENTS 常量,编译时校验
9子应用独立运行正常,嵌入后白屏生命周期未正确暴露确认使用了 bootstrapMicroApp(自动处理)
10主应用 Tab 栏不显示子应用页面未集成 TagsView 代理使用 createTagsViewProxy 拦截 Tab 操作

Checklist:

  • 遇到问题时能按此表快速定位
  • 知道 DevTools 中如何查看 wujie bus 事件(console 打印)

第五部分:后续迭代指导(L2-L3)

5.1 新增共享事件的标准流程

目标受众: L2-L3 | 时长: 20min | 形式: 流程讲解

1. 在 micro-bridge/types.ts 的 MicroBridgeEventMap 中添加事件类型
2. 在 SHARED_EVENTS 常量中添加对应 key(编译时校验)
3. 在主应用 shared-provider.ts 中添加 watch → bus.$emit
4. 在主应用 getSharedProps() 中添加对应 props 字段
5. 更新 SharedProps 类型定义
6. 子应用通过 bus.$on 监听 + props 冷启动读取

示例:新增"主题切换"事件

// Step 1: types.ts
'SHARED:THEME_CHANGED': { theme: 'light' | 'dark' }

// Step 2: SHARED_EVENTS
THEME_CHANGED: 'SHARED:THEME_CHANGED',

// Step 3: shared-provider.ts
watch(() => themeStore.theme, (theme) => {
  bus.$emit(SHARED_EVENTS.THEME_CHANGED, { theme })
})

// Step 4-5: SharedProps
locale: { ... }
theme: { theme: 'light' | 'dark' }  // 新增

// Step 6: 子应用
bus.$on('SHARED:THEME_CHANGED', ({ theme }) => { ... })

5.2 新增子应用的标准流程

目标受众: L2 | 时长: 20min | 形式: Checklist

步骤操作文件
1在 registry.ts 注册micro-bridge/src/registry.ts
2创建应用目录apps/[name]/
3配置 package.json依赖 @cmclink/micro-bootstrap + @cmclink/api
4配置 vite.config.ts使用 createChildAppConfig
5编写 main.ts使用 bootstrapMicroApp
6配置 .env 文件VITE_BASE_URL + VITE_BASE_PATH
7主应用添加环境变量VITE_[NAME]_APP_URL
8集成 TagsView 代理使用 createTagsViewProxy
9集成共享数据消费读 props.$shared + 监听 bus 事件
10验证独立运行 + 嵌入主应用 + Tab 切换 + 路由同步

5.3 架构演进路线图

目标受众: L3 | 时长: 20min | 形式: 讨论

阶段内容触发条件
当前wujie alive + bus 通信 + monorepo
中期子应用注册表配置化(JSON/YAML)子应用 > 10 个
中期共享组件库 @cmclink/components跨应用 UI 组件 > 5 个
长期考虑 Module Federation 混合方案需要运行时共享依赖
长期Changesets 版本管理包开源发布

原则:不提前抽象,等痛点出现再演进。


5.4 Code Review 检查项(L2-L3)

微前端相关 PR 的 Review 要点:

通信相关:

  • 事件名是否使用 SHARED_EVENTS 常量(不允许硬编码字符串)
  • 新事件是否在 MicroBridgeEventMap 中定义了类型
  • bus.$emit 后是否需要同步更新 props(冷启动兼容)

子应用相关:

  • 是否使用 bootstrapMicroApp 启动(不允许手动 createApp)
  • Token 是否从 props.$shared 读取(不允许从 localStorage 读)
  • 弹窗组件是否在 shadowDOM 内(teleported: false)
  • 新接口放对位置了吗(@cmclink/api vs src/api/)

主应用相关:

  • 新的 watch 是否注册了 stop 函数到 stopWatchers
  • Tab 操作是否考虑了 alive 模式的 setTimeout 延迟

附录

A. 培训课程安排建议

课次内容时长受众
第 1 课1.1 + 1.2 + 2.32hL1-L3
第 2 课4.1 动手实操2hL1-L2
第 3 课2.1 + 2.2 + 2.42hL2-L3
第 4 课3.1 + 3.22hL2-L3
第 5 课1.3 + 5.1-5.42hL2-L3
第 6 课4.2 排障 + 答疑1.5hL1-L3

总计: 约 11.5 小时,建议分 6 次完成,每周 1-2 次

B. 关键文件速查表

文件作用修改频率
micro-bridge/src/types.ts事件类型 + SharedProps + 常量新增事件时
micro-bridge/src/registry.ts子应用注册表新增子应用时
micro-bootstrap/src/index.ts子应用启动器极少修改
main/stores/shared-provider.ts共享状态广播新增共享数据时
main/stores/tabs.tsTab 管理 + 路由同步极少修改
main/config/wujie.tswujie 配置新增子应用时
vite-config/src/child-app.ts子应用 Vite 配置工厂极少修改

C. 术语表

术语含义
alive 模式wujie 保活模式,子应用切走不销毁
buswujie 内置 EventBus(window.__WUJIE.bus
props主应用通过 wujie 注入给子应用的数据
shadowDOMWebComponent 的 DOM 隔离机制
冷启动子应用首次加载,从 props 读取数据
热更新子应用运行中,通过 bus 事件接收数据变更
TagsView 代理子应用 Tab 操作通过 bus 代理到主应用统一管理
SHARED_EVENTS共享状态事件名常量,编译时校验拼写