微前端教學手冊

版本:1.0
最後更新:2026 年 1 月
適用對象:資深工程師 / Tech Lead / Architect 最後更新: 2026年1月23日
適用於: 微前端 Created by: Eric Cheng

微前端(Micro-Frontend)教學手冊

版本:1.0
適用對象:資深前端/全端工程師、Tech Lead、架構師
最後更新:2026 年 1 月


目錄

  1. 微前端的核心價值與真正要解決的問題

  2. 微前端主流架構模式比較

  3. Module Federation 深度解析

  4. 路由與狀態管理設計

  5. 隔離與安全性設計

  6. CI/CD 與部署策略

  7. 效能與可維運性

  8. AI 輔助微前端開發實務

  9. 實戰經驗總結

  10. 企業導入微前端常見失敗原因

  11. 檢查清單(Checklist)

  12. 附錄


1. 微前端的核心價值與真正要解決的問題

1.1 什麼是微前端?

微前端是一種將大型前端應用拆解為多個獨立開發、獨立部署、獨立運行的小型前端應用的架構模式。其核心理念源自後端的微服務架構,目標是解決單體前端應用在規模化開發時遇到的瓶頸。

graph TB
    subgraph "微前端架構"
        Shell[基座應用 Shell]
        Shell --> MicroApp1[子應用 A<br/>React]
        Shell --> MicroApp2[子應用 B<br/>Vue]
        Shell --> MicroApp3[子應用 C<br/>Angular]
    end
    
    subgraph "獨立團隊"
        Team1[團隊 A] --> MicroApp1
        Team2[團隊 B] --> MicroApp2
        Team3[團隊 C] --> MicroApp3
    end

1.2 微前端真正要解決的問題

問題類型單體前端的痛點微前端的解決方案
團隊協作多團隊同時修改同一 codebase,衝突頻繁各團隊獨立 repo,獨立部署
技術債務舊系統難以逐步升級,需要大爆炸式重構漸進式遷移,新舊系統並存
部署耦合任何小改動都需要整體部署子應用獨立部署,降低風險
技術棧鎖定被迫統一技術棧,無法採用更適合的工具各子應用可選擇最適合的技術
建置時間專案越大,build 時間越長各子應用獨立 build,平行執行

1.3 什麼情況「不該用微前端」

⚠️ 重要提醒:微前端不是銀彈,錯誤導入會帶來比單體更大的複雜度。

不適合微前端的情境

  1. 團隊規模小於 3 個獨立團隊

    • 微前端的架構成本需要足夠的團隊規模來攤提
    • 小團隊用 Monorepo + 模組化即可
  2. 應用邏輯高度耦合

    • 子應用之間需要頻繁、即時的資料交換
    • 無法清楚劃分業務邊界
  3. 對首屏效能有極致要求

    • 微前端必然帶來額外的 runtime overhead
    • 對效能敏感的 C 端產品需謹慎評估
  4. 缺乏基礎設施支援

    • 沒有成熟的 CI/CD 流程
    • 沒有統一的監控、日誌系統
  5. 為了「新技術」而導入

    • 「別人都在用」不是理由
    • 必須有明確的業務價值驅動
flowchart TD
    A[考慮導入微前端?] --> B{團隊數量 >= 3?}
    B -->|否| C[建議使用 Monorepo]
    B -->|是| D{業務邊界清晰?}
    D -->|否| E[先進行領域建模]
    D -->|是| F{有 CI/CD 基礎設施?}
    F -->|否| G[先建立 DevOps 基礎]
    F -->|是| H{效能要求可接受額外開銷?}
    H -->|否| I[評估其他方案]
    H -->|是| J[✅ 適合導入微前端]

1.4 微前端 vs 單體前端 vs Monorepo

維度單體前端Monorepo微前端
程式碼管理單一 repo單一 repo,多 package多 repo 或 Monorepo
部署單位整體部署可獨立部署(需額外設定)完全獨立部署
技術棧統一可不同但共享工具鏈完全獨立
運行時隔離
團隊自治
架構複雜度
適用規模小型中型大型

選型決策樹

graph TD
    A[專案規模評估] --> B{開發人員 > 30 人?}
    B -->|否| C{需要獨立部署?}
    C -->|否| D[單體前端]
    C -->|是| E[Monorepo + 獨立部署]
    B -->|是| F{團隊完全獨立運作?}
    F -->|否| G[Monorepo]
    F -->|是| H{需要技術棧異構?}
    H -->|否| I[Monorepo + Module Federation]
    H -->|是| J[完整微前端架構]

1.5 本章實務案例

案例:某銀行網路銀行系統重構

背景

  • 原系統為 10 年前的 jQuery + JSP 架構
  • 約 200 個功能模組,50 萬行前端程式碼
  • 5 個開發團隊,共 40 位前端工程師

決策過程

  1. 評估發現無法一次性重寫(風險太高)
  2. 採用微前端實現「絞殺者模式」漸進遷移
  3. 新功能用 Vue 3 開發為子應用
  4. 舊功能逐步重構後替換

結果

  • 18 個月內完成 70% 功能遷移
  • 新功能開發效率提升 40%
  • 部署頻率從每月 1 次提升到每週 2 次

2. 微前端主流架構模式比較

2.1 基座(Shell / Container)模式

基座模式是微前端最常見的架構,由一個「主應用」負責:

  • 應用載入與生命週期管理
  • 全域路由協調
  • 共享資源管理(認證、全域狀態)
  • 子應用間通訊協調
graph TB
    subgraph "基座應用 (Shell)"
        Router[路由管理]
        Auth[認證模組]
        Layout[佈局框架]
        Loader[子應用載入器]
    end
    
    subgraph "子應用層"
        App1[訂單管理<br/>Vue 3]
        App2[客戶管理<br/>React]
        App3[報表中心<br/>Angular]
    end
    
    Router --> Loader
    Loader --> App1
    Loader --> App2
    Loader --> App3
    Auth --> App1
    Auth --> App2
    Auth --> App3

基座的核心職責

// 基座應用的核心介面設計
interface MicroAppShell {
  // 註冊子應用
  registerApp(config: AppConfig): void;
  
  // 啟動子應用
  mountApp(appName: string, container: HTMLElement): Promise<void>;
  
  // 卸載子應用
  unmountApp(appName: string): Promise<void>;
  
  // 子應用間通訊
  dispatch(event: string, payload: any): void;
  
  // 訂閱事件
  subscribe(event: string, handler: Function): Unsubscribe;
}

interface AppConfig {
  name: string;
  entry: string;  // 子應用入口 URL
  container: string;  // DOM 容器選擇器
  activeRule: string | ((location: Location) => boolean);
  props?: Record<string, any>;  // 傳遞給子應用的參數
}

2.2 Runtime Integration vs Build-time Integration

特性Build-time IntegrationRuntime Integration
整合時機打包時運行時
獨立部署❌ 需要重新打包基座✅ 完全獨立
版本控制在 package.json 中在運行時動態載入
效能較好(可 tree-shaking)有額外載入開銷
技術棧靈活度低(需相容)
代表方案npm package、MonorepoModule Federation、qiankun

Build-time Integration 範例

// package.json - 將子應用作為 npm 套件引入
{
  "dependencies": {
    "@myorg/micro-app-order": "^1.2.0",
    "@myorg/micro-app-customer": "^2.1.0"
  }
}
// 基座應用中直接 import
import { OrderModule } from '@myorg/micro-app-order';
import { CustomerModule } from '@myorg/micro-app-customer';

Runtime Integration 範例

// 運行時動態載入子應用
const loadMicroApp = async (name: string, entry: string) => {
  // 1. 獲取子應用 manifest
  const manifest = await fetch(`${entry}/asset-manifest.json`).then(r => r.json());
  
  // 2. 動態載入 JS
  const script = document.createElement('script');
  script.src = `${entry}/${manifest['main.js']}`;
  
  // 3. 等待子應用掛載
  return new Promise((resolve) => {
    script.onload = () => {
      const app = window[`__MICRO_APP_${name}__`];
      resolve(app);
    };
    document.body.appendChild(script);
  });
};

2.3 iframe / Web Components / Module Federation 比較

graph LR
    subgraph "整合技術選型"
        A[iframe] --> A1[最強隔離<br/>最差體驗]
        B[Web Components] --> B1[標準化<br/>生態待成熟]
        C[Module Federation] --> C1[平衡方案<br/>主流選擇]
        D[自研沙箱] --> D1[高度客製<br/>維護成本高]
    end

詳細比較表

維度iframeWeb ComponentsModule Federation
JS 隔離✅ 天然隔離⚠️ 需額外處理⚠️ 需額外處理
CSS 隔離✅ 天然隔離✅ Shadow DOM⚠️ 需命名空間/CSS Module
通訊複雜度高(postMessage)中(事件/屬性)低(直接引用)
SEO❌ 不友好✅ 可支援✅ 可支援
路由同步困難可行容易
共享依賴❌ 無法⚠️ 有限✅ 完整支援
效能開銷
瀏覽器支援✅ 全部⚠️ 需 polyfill✅ 現代瀏覽器

選型建議

// 決策函數範例
function selectIntegrationStrategy(requirements: Requirements): Strategy {
  // 1. 需要最強隔離(如:嵌入第三方內容)
  if (requirements.strictIsolation && requirements.thirdPartyContent) {
    return 'iframe';
  }
  
  // 2. 需要跨框架共用 UI 元件
  if (requirements.crossFrameworkComponents && !requirements.complexStateSharing) {
    return 'web-components';
  }
  
  // 3. 主流場景:同構或異構微前端
  if (requirements.independentDeployment && requirements.sharedDependencies) {
    return 'module-federation';
  }
  
  // 4. 對隔離有特殊需求
  if (requirements.customSandbox) {
    return 'qiankun-like-sandbox';
  }
  
  return 'module-federation';  // 預設推薦
}

2.4 主流框架方案比較

框架核心特點適用場景學習曲線
qiankun完整生命週期、沙箱隔離企業級複雜應用
single-spa輕量、框架無關需要高度客製化
Module FederationWebpack 5 原生支援需要依賴共享
micro-appWeb Components 封裝追求簡單易用
wujieiframe 增強方案需要強隔離

2.5 本章實務案例

案例:電商平台技術選型

需求分析

  • 商品中心(React)、訂單系統(Vue)、會員系統(Angular)
  • 需要共享設計系統元件
  • 對 SEO 有要求(商品頁)
  • 需要獨立部署

選型決策

  • 排除 iframe:SEO 需求
  • 排除純 Web Components:狀態共享複雜
  • 選擇 Module Federation:平衡依賴共享與獨立部署

架構設計

Shell (Webpack 5 + MF)
├── shared-design-system (Remote)
├── micro-product (Remote, React)
├── micro-order (Remote, Vue)
└── micro-member (Remote, Angular)

3. Module Federation 深度解析

3.1 基本運作原理(Host / Remote)

Module Federation 是 Webpack 5 引入的原生模組共享機制,核心概念:

  • Host(消費者):載入並使用其他應用暴露的模組
  • Remote(提供者):暴露模組供其他應用使用
  • Shared(共享):多個應用共用的依賴(如 React、Vue)
sequenceDiagram
    participant Host as Host 應用
    participant Remote as Remote 應用
    participant Shared as 共享依賴池
    
    Host->>Remote: 1. 請求 remoteEntry.js
    Remote-->>Host: 2. 返回模組清單
    Host->>Shared: 3. 檢查共享依賴
    Shared-->>Host: 4. 返回已載入的依賴
    Host->>Remote: 5. 請求具體模組
    Remote-->>Host: 6. 返回模組程式碼
    Host->>Host: 7. 執行模組

Host 配置範例

// webpack.config.js (Host 應用)
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      
      // 聲明要使用的遠端應用
      remotes: {
        orderApp: 'orderApp@http://localhost:3001/remoteEntry.js',
        customerApp: 'customerApp@http://localhost:3002/remoteEntry.js',
      },
      
      // 共享依賴配置
      shared: {
        react: { 
          singleton: true,  // 確保只有一個實例
          requiredVersion: '^18.0.0',
        },
        'react-dom': { 
          singleton: true,
          requiredVersion: '^18.0.0',
        },
      },
    }),
  ],
};

Remote 配置範例

// webpack.config.js (Remote 應用)
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'orderApp',
      filename: 'remoteEntry.js',
      
      // 暴露模組
      exposes: {
        './OrderList': './src/components/OrderList',
        './OrderDetail': './src/pages/OrderDetail',
        './orderService': './src/services/orderService',
      },
      
      // 共享依賴
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
        'react-router-dom': { singleton: true },
      },
    }),
  ],
};

使用 Remote 模組

// Host 應用中使用 Remote 模組
// 方式 1:靜態導入(需要 TypeScript 類型聲明)
import OrderList from 'orderApp/OrderList';

// 方式 2:動態導入(推薦,支援錯誤處理)
const OrderList = React.lazy(() => import('orderApp/OrderList'));

// 方式 3:封裝載入邏輯
const loadRemoteModule = async (remoteName: string, moduleName: string) => {
  try {
    const container = window[remoteName];
    if (!container) {
      throw new Error(`Remote ${remoteName} not found`);
    }
    await container.init(__webpack_share_scopes__.default);
    const factory = await container.get(moduleName);
    return factory();
  } catch (error) {
    console.error(`Failed to load ${remoteName}/${moduleName}:`, error);
    throw error;
  }
};

3.2 Shared Library 設計策略

共享依賴的配置是 Module Federation 最複雜也最關鍵的部分。

共享策略選項

shared: {
  // 策略 1:簡單共享(自動推斷版本)
  'lodash': {},
  
  // 策略 2:單例模式(全域只有一個實例)
  'react': {
    singleton: true,
    strictVersion: true,  // 版本不符時報錯
    requiredVersion: '^18.0.0',
  },
  
  // 策略 3:即時載入(不使用懶載入)
  '@company/design-system': {
    eager: true,  // 打包進主 bundle
    singleton: true,
  },
  
  // 策略 4:版本範圍控制
  'axios': {
    singleton: true,
    requiredVersion: '>=0.21.0 <2.0.0',
  },
}

共享依賴決策矩陣

依賴類型singletoneagerstrictVersion說明
React/Vue 核心必須單例,版本嚴格匹配
狀態管理(Redux/Pinia)⚠️單例,版本可適度放寬
UI 元件庫⚠️⚠️看是否需要快速載入
工具庫(lodash)可多版本並存
公司內部共用庫⚠️建議即時載入

3.3 版本衝突與依賴治理

常見版本衝突場景

graph TD
    A[Shell 應用] -->|React 18.2.0| B[共享池]
    C[子應用 A] -->|React 18.0.0| B
    D[子應用 B] -->|React 17.0.2| B
    
    B --> E{版本協商}
    E -->|singleton: true| F[使用最高版本 18.2.0]
    E -->|strictVersion: true| G[版本 17 報錯]

版本衝突解決方案

// 方案 1:版本對齊(推薦)
// 所有應用統一到相同版本
shared: {
  react: {
    singleton: true,
    strictVersion: true,
    requiredVersion: '18.2.0',  // 固定版本
  },
}

// 方案 2:版本範圍容忍
shared: {
  react: {
    singleton: true,
    requiredVersion: '^18.0.0',  // 允許 18.x
  },
}

// 方案 3:Fallback 機制
const loadReactSafely = async () => {
  try {
    return await import('react');
  } catch (e) {
    // 載入本地備份版本
    return await import('./fallback/react');
  }
};

依賴版本治理工具

// scripts/check-shared-versions.ts
// 用於 CI 檢查各子應用依賴版本一致性

import * as fs from 'fs';
import * as path from 'path';

interface DependencyReport {
  package: string;
  versions: Map<string, string[]>;  // version -> apps
}

function checkSharedVersions(apps: string[]): DependencyReport[] {
  const sharedPackages = ['react', 'react-dom', 'react-router-dom'];
  const reports: DependencyReport[] = [];
  
  for (const pkg of sharedPackages) {
    const versions = new Map<string, string[]>();
    
    for (const app of apps) {
      const pkgJson = JSON.parse(
        fs.readFileSync(path.join(app, 'package.json'), 'utf-8')
      );
      const version = pkgJson.dependencies?.[pkg] || pkgJson.devDependencies?.[pkg];
      
      if (version) {
        if (!versions.has(version)) {
          versions.set(version, []);
        }
        versions.get(version)!.push(app);
      }
    }
    
    if (versions.size > 1) {
      reports.push({ package: pkg, versions });
    }
  }
  
  return reports;
}

3.4 Webpack vs Vite Module Federation

特性Webpack 5 MFVite + vite-plugin-federation
成熟度高(原生支援)中(社群插件)
開發體驗HMR 支援更快的 HMR
生產建置穩定需注意相容性
配置複雜度中等較低
Shared 處理完整有限制
生態支援豐富成長中

Vite Federation 配置範例

// vite.config.ts (Host)
import { defineConfig } from 'vite';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    federation({
      name: 'shell',
      remotes: {
        orderApp: 'http://localhost:3001/assets/remoteEntry.js',
      },
      shared: ['vue', 'pinia'],
    }),
  ],
  build: {
    target: 'esnext',
    minify: false,
    cssCodeSplit: false,
  },
});
// vite.config.ts (Remote)
import { defineConfig } from 'vite';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    federation({
      name: 'orderApp',
      filename: 'remoteEntry.js',
      exposes: {
        './OrderList': './src/components/OrderList.vue',
      },
      shared: ['vue', 'pinia'],
    }),
  ],
  build: {
    target: 'esnext',
    minify: false,
  },
});

3.5 本章實務案例

案例:金融交易系統的 MF 實作

挑戰

  • 5 個子應用,3 種框架(React, Vue, Angular)
  • 需要共享設計系統(200+ 元件)
  • 對載入效能敏感

解決方案

// 設計系統作為獨立 Remote
// design-system/webpack.config.js
new ModuleFederationPlugin({
  name: 'designSystem',
  filename: 'remoteEntry.js',
  exposes: {
    './Button': './src/components/Button',
    './Table': './src/components/Table',
    './Form': './src/components/Form',
    // ... 200+ 元件
  },
  shared: {
    react: { singleton: true, eager: true },
    'styled-components': { singleton: true },
  },
})

// 使用預載入優化
// shell/src/bootstrap.ts
const preloadDesignSystem = () => {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'script';
  link.href = 'https://cdn.example.com/design-system/remoteEntry.js';
  document.head.appendChild(link);
};

4. 路由與狀態管理設計

4.1 全域 Router vs 子應用 Router

微前端中的路由是最複雜的議題之一,需要處理「基座路由」與「子應用路由」的協調。

graph TB
    subgraph "路由層次"
        A[瀏覽器 URL<br/>/order/detail/123]
        B[基座路由<br/>匹配 /order/*]
        C[子應用路由<br/>匹配 /detail/:id]
    end
    
    A --> B
    B --> C
    
    subgraph "路由職責"
        B1[決定載入哪個子應用]
        C1[子應用內部頁面切換]
    end
    
    B --> B1
    C --> C1

路由架構模式

模式說明優點缺點
基座接管基座統一管理所有路由路由邏輯集中子應用失去自主性
子應用自治基座只做 prefix 匹配子應用完全獨立路由可能衝突
混合模式基座管理一級路由,子應用管理內部路由平衡需要良好的約定

基座路由配置(以 Vue Router 為例)

// shell/src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import { loadMicroApp } from '@/micro/loader';

const routes: RouteRecordRaw[] = [
  // 基座自有頁面
  { path: '/', component: () => import('@/views/Home.vue') },
  { path: '/login', component: () => import('@/views/Login.vue') },
  
  // 子應用路由 - 使用通配符
  {
    path: '/order/:pathMatch(.*)*',
    component: () => import('@/views/MicroAppContainer.vue'),
    meta: { microApp: 'orderApp' },
  },
  {
    path: '/customer/:pathMatch(.*)*',
    component: () => import('@/views/MicroAppContainer.vue'),
    meta: { microApp: 'customerApp' },
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

// 路由守衛:載入對應的子應用
router.beforeEach(async (to, from, next) => {
  const microAppName = to.meta.microApp as string;
  
  if (microAppName) {
    await loadMicroApp(microAppName);
  }
  
  next();
});

export default router;

子應用路由配置

// order-app/src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';

const routes: RouteRecordRaw[] = [
  { path: '/', redirect: '/list' },
  { path: '/list', component: () => import('@/views/OrderList.vue') },
  { path: '/detail/:id', component: () => import('@/views/OrderDetail.vue') },
  { path: '/create', component: () => import('@/views/OrderCreate.vue') },
];

// 關鍵:根據運行環境決定 base path
const getBasePath = () => {
  // 獨立運行
  if (!window.__POWERED_BY_QIANKUN__) {
    return '/';
  }
  // 作為子應用運行
  return '/order';
};

const router = createRouter({
  history: createWebHistory(getBasePath()),
  routes,
});

export default router;

路由同步機制

// 基座與子應用路由同步
class RouterSync {
  private shellRouter: Router;
  private listeners: Map<string, Function> = new Map();
  
  constructor(shellRouter: Router) {
    this.shellRouter = shellRouter;
    this.setupPopstateListener();
  }
  
  // 監聽瀏覽器前進/後退
  private setupPopstateListener() {
    window.addEventListener('popstate', () => {
      this.notifyMicroApps();
    });
  }
  
  // 子應用路由變化時通知基座
  onMicroAppRouteChange(appName: string, path: string) {
    const fullPath = `/${appName}${path}`;
    
    // 避免重複觸發
    if (this.shellRouter.currentRoute.value.fullPath !== fullPath) {
      this.shellRouter.replace(fullPath);
    }
  }
  
  // 通知子應用路由變化
  private notifyMicroApps() {
    this.listeners.forEach((callback) => callback(window.location.pathname));
  }
  
  // 子應用註冊監聽
  registerMicroApp(appName: string, callback: Function) {
    this.listeners.set(appName, callback);
  }
}

4.2 狀態共享邊界

⚠️ 核心原則:子應用之間應該盡可能減少直接的狀態共享,過度共享會導致緊耦合。

狀態共享決策矩陣

狀態類型是否共享共享方式說明
用戶認證資訊✅ 是基座下發Token、用戶資料
全域設定✅ 是基座下發語言、主題、權限
UI 狀態(Modal、Toast)✅ 是事件通訊避免多層彈窗
業務資料⚠️ 謹慎API 或事件避免直接共享
子應用內部狀態❌ 否-完全隔離
快取資料⚠️ 謹慎共享快取層需定義失效策略

正確的狀態共享架構

graph TB
    subgraph "基座 Shell"
        GlobalStore[全域 Store]
        AuthStore[認證狀態]
        ConfigStore[設定狀態]
    end
    
    subgraph "子應用 A"
        LocalStoreA[本地 Store A]
        ComponentsA[元件]
    end
    
    subgraph "子應用 B"
        LocalStoreB[本地 Store B]
        ComponentsB[元件]
    end
    
    GlobalStore -->|只讀訂閱| LocalStoreA
    GlobalStore -->|只讀訂閱| LocalStoreB
    
    LocalStoreA -.->|事件通訊| LocalStoreB

基座全域狀態設計

// shell/src/store/global.ts
import { reactive, readonly } from 'vue';

interface GlobalState {
  user: {
    id: string;
    name: string;
    roles: string[];
    token: string;
  } | null;
  config: {
    language: 'zh-TW' | 'en-US';
    theme: 'light' | 'dark';
    permissions: string[];
  };
  ui: {
    sidebarCollapsed: boolean;
    globalLoading: boolean;
  };
}

const state = reactive<GlobalState>({
  user: null,
  config: {
    language: 'zh-TW',
    theme: 'light',
    permissions: [],
  },
  ui: {
    sidebarCollapsed: false,
    globalLoading: false,
  },
});

// 只暴露只讀版本給子應用
export const globalState = readonly(state);

// 修改方法只在基座內部使用
export const globalActions = {
  setUser(user: GlobalState['user']) {
    state.user = user;
  },
  setLanguage(lang: GlobalState['config']['language']) {
    state.config.language = lang;
  },
  setTheme(theme: GlobalState['config']['theme']) {
    state.config.theme = theme;
  },
  setGlobalLoading(loading: boolean) {
    state.ui.globalLoading = loading;
  },
};

// 暴露給子應用的介面
export const microAppAPI = {
  getState: () => globalState,
  subscribe: (callback: (state: GlobalState) => void) => {
    // 實作狀態訂閱
  },
  requestPermission: async (permission: string) => {
    // 權限請求 API
  },
};

4.3 Event Bus / Global Store 的使用準則

Event Bus 設計

// shared/src/eventBus.ts
type EventHandler = (...args: any[]) => void;

class MicroAppEventBus {
  private events: Map<string, Set<EventHandler>> = new Map();
  private eventHistory: Map<string, any[]> = new Map();  // 用於 replay
  
  // 發送事件
  emit(event: string, ...args: any[]) {
    console.log(`[EventBus] Emit: ${event}`, args);
    
    // 記錄歷史(可選)
    this.eventHistory.set(event, args);
    
    const handlers = this.events.get(event);
    if (handlers) {
      handlers.forEach(handler => {
        try {
          handler(...args);
        } catch (error) {
          console.error(`[EventBus] Handler error for ${event}:`, error);
        }
      });
    }
  }
  
  // 訂閱事件
  on(event: string, handler: EventHandler): () => void {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event)!.add(handler);
    
    // 返回取消訂閱函數
    return () => this.off(event, handler);
  }
  
  // 一次性訂閱
  once(event: string, handler: EventHandler) {
    const wrappedHandler = (...args: any[]) => {
      handler(...args);
      this.off(event, wrappedHandler);
    };
    this.on(event, wrappedHandler);
  }
  
  // 取消訂閱
  off(event: string, handler: EventHandler) {
    const handlers = this.events.get(event);
    if (handlers) {
      handlers.delete(handler);
    }
  }
  
  // 獲取最後一次事件資料(用於子應用延遲載入後獲取)
  getLastEvent(event: string): any[] | undefined {
    return this.eventHistory.get(event);
  }
}

// 單例模式
export const eventBus = new MicroAppEventBus();

// 預定義事件類型(Type-safe)
export const MicroAppEvents = {
  // 用戶相關
  USER_LOGIN: 'user:login',
  USER_LOGOUT: 'user:logout',
  USER_PROFILE_UPDATED: 'user:profile-updated',
  
  // 導航相關
  NAVIGATE_TO: 'navigation:to',
  
  // 業務事件
  ORDER_CREATED: 'order:created',
  ORDER_UPDATED: 'order:updated',
  CUSTOMER_SELECTED: 'customer:selected',
  
  // UI 事件
  SHOW_GLOBAL_LOADING: 'ui:show-loading',
  HIDE_GLOBAL_LOADING: 'ui:hide-loading',
  SHOW_NOTIFICATION: 'ui:notification',
} as const;

使用範例

// 子應用 A:發送事件
import { eventBus, MicroAppEvents } from '@myorg/shared';

const createOrder = async (orderData: OrderData) => {
  const order = await orderService.create(orderData);
  
  // 通知其他子應用
  eventBus.emit(MicroAppEvents.ORDER_CREATED, {
    orderId: order.id,
    customerId: order.customerId,
    totalAmount: order.totalAmount,
  });
  
  return order;
};

// 子應用 B:接收事件
import { eventBus, MicroAppEvents } from '@myorg/shared';
import { onMounted, onUnmounted } from 'vue';

export function useOrderEvents() {
  let unsubscribe: (() => void) | null = null;
  
  onMounted(() => {
    unsubscribe = eventBus.on(MicroAppEvents.ORDER_CREATED, (payload) => {
      console.log('New order created:', payload);
      // 更新相關資料
      refreshCustomerOrders(payload.customerId);
    });
  });
  
  onUnmounted(() => {
    unsubscribe?.();
  });
}

Event Bus 使用守則

做法✅ 正確❌ 錯誤
事件命名order:createddata, update
事件資料只傳 ID 或摘要傳完整業務物件
訂閱管理在 unmount 時取消忘記取消訂閱
事件頻率低頻業務事件高頻 UI 更新
錯誤處理捕獲處理器錯誤讓錯誤冒泡

4.4 本章實務案例

案例:CRM 系統的狀態管理設計

需求

  • 客戶列表(子應用 A)選擇客戶後,訂單列表(子應用 B)需要聯動
  • 需要避免過度耦合

錯誤做法

// ❌ 子應用 A 直接操作子應用 B 的 Store
import { orderStore } from 'orderApp/store';  // 跨應用直接引用
orderStore.setCustomerId(customerId);  // 直接操作

正確做法

// ✅ 通過事件解耦
// 子應用 A
eventBus.emit('customer:selected', { customerId: '123', customerName: 'ABC Corp' });

// 子應用 B
eventBus.on('customer:selected', ({ customerId }) => {
  // 自行決定如何處理
  localStore.setFilter({ customerId });
  fetchOrders({ customerId });
});

5. 隔離與安全性設計

5.1 CSS 隔離策略

CSS 污染是微前端中最常見的問題之一,子應用的樣式可能影響基座或其他子應用。

graph LR
    subgraph "CSS 污染風險"
        A[子應用 A<br/>.btn { color: red }] --> C[全域 CSS]
        B[子應用 B<br/>.btn { color: blue }] --> C
        C --> D[衝突!]
    end

隔離方案比較

方案隔離程度實作難度效能影響相容性
CSS Modules
CSS-in-JS
BEM 命名空間
Shadow DOM
動態樣式前綴

CSS Modules 實作

// 子應用使用 CSS Modules
// OrderList.module.css
.container {
  padding: 16px;
}

.button {
  background: #1890ff;
}

// OrderList.tsx
import styles from './OrderList.module.css';

export const OrderList = () => (
  <div className={styles.container}>
    <button className={styles.button}>Submit</button>
  </div>
);

動態樣式前綴(推薦)

// 使用 PostCSS 自動添加前綴
// postcss.config.js
module.exports = {
  plugins: [
    require('postcss-prefix-selector')({
      prefix: '[data-qiankun="orderApp"]',
      transform: (prefix, selector, prefixedSelector) => {
        if (selector.startsWith(':root')) {
          return selector;  // 不處理 :root
        }
        return prefixedSelector;
      },
    }),
  ],
};

// 輸出結果
// 原始: .btn { color: red; }
// 轉換後: [data-qiankun="orderApp"] .btn { color: red; }

Shadow DOM 隔離(qiankun)

// qiankun 配置啟用 Shadow DOM
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'orderApp',
    entry: '//localhost:3001',
    container: '#subapp-container',
    activeRule: '/order',
    props: { /* ... */ },
  },
]);

start({
  sandbox: {
    strictStyleIsolation: true,  // 啟用 Shadow DOM
    // 或使用實驗性樣式隔離
    experimentalStyleIsolation: true,
  },
});

5.2 JS 沙箱隔離策略

JS 隔離需要防止子應用污染全域變數、覆寫原生方法等問題。

常見 JS 污染場景

// ❌ 子應用污染全域變數
window.myGlobalVar = 'polluted';
window.addEventListener('resize', handler);  // 未清理

// ❌ 覆寫原生方法
Array.prototype.customMethod = function() { /* ... */ };

快照沙箱(Snapshot Sandbox)

// 簡化版快照沙箱實作
class SnapshotSandbox {
  private windowSnapshot: Map<string, any> = new Map();
  private modifyPropsMap: Map<string, any> = new Map();
  
  activate() {
    // 記錄當前 window 快照
    for (const key in window) {
      this.windowSnapshot.set(key, (window as any)[key]);
    }
    
    // 恢復之前的修改
    this.modifyPropsMap.forEach((value, key) => {
      (window as any)[key] = value;
    });
  }
  
  deactivate() {
    // 記錄變更並恢復原始值
    for (const key in window) {
      if ((window as any)[key] !== this.windowSnapshot.get(key)) {
        this.modifyPropsMap.set(key, (window as any)[key]);
        (window as any)[key] = this.windowSnapshot.get(key);
      }
    }
  }
}

Proxy 沙箱(推薦)

// 使用 Proxy 實現更精細的沙箱
class ProxySandbox {
  private proxy: Window;
  private running = false;
  private fakeWindow: Record<string, any> = {};
  
  constructor() {
    const _this = this;
    
    this.proxy = new Proxy(window, {
      get(target, key: string) {
        // 優先從 fakeWindow 獲取
        if (_this.fakeWindow.hasOwnProperty(key)) {
          return _this.fakeWindow[key];
        }
        
        // 特殊處理
        if (key === 'window' || key === 'self' || key === 'globalThis') {
          return _this.proxy;
        }
        
        const value = (target as any)[key];
        return typeof value === 'function' ? value.bind(target) : value;
      },
      
      set(target, key: string, value) {
        if (_this.running) {
          _this.fakeWindow[key] = value;
        }
        return true;
      },
      
      has(target, key) {
        return key in _this.fakeWindow || key in target;
      },
    });
  }
  
  activate() {
    this.running = true;
  }
  
  deactivate() {
    this.running = false;
  }
  
  getProxy() {
    return this.proxy;
  }
}

// 使用沙箱執行子應用程式碼
const sandbox = new ProxySandbox();
sandbox.activate();

// 在沙箱環境中執行
(function(window) {
  // 子應用程式碼在此執行
  // 所有對 window 的修改都被隔離
}.bind(sandbox.getProxy())(sandbox.getProxy()));

5.3 微前端下的資安風險

風險矩陣

風險類型風險等級說明緩解措施
XSS 跨站腳本子應用注入惡意腳本CSP、輸入驗證
CSRF跨域請求偽造Token 驗證、SameSite Cookie
資料洩漏子應用間敏感資料洩漏最小權限、資料脫敏
依賴劫持惡意 npm 套件依賴鎖定、安全掃描
點擊劫持iframe 點擊劫持X-Frame-Options

Content Security Policy 配置

<!-- 基座應用 CSP 配置 -->
<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' https://cdn.trusted-domain.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  frame-src 'self' https://trusted-iframe.com;
">
// Nginx 配置
server {
  location / {
    add_header Content-Security-Policy "
      default-src 'self';
      script-src 'self' https://cdn.example.com;
      style-src 'self' 'unsafe-inline';
    ";
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
  }
}

5.4 權限、Token、SSO 整合模式

sequenceDiagram
    participant User as 使用者
    participant Shell as 基座應用
    participant SSO as SSO Server
    participant MicroApp as 子應用
    participant API as 後端 API
    
    User->>Shell: 1. 訪問應用
    Shell->>SSO: 2. 重導向至 SSO
    User->>SSO: 3. 輸入帳密
    SSO-->>Shell: 4. 返回 Auth Code
    Shell->>SSO: 5. 交換 Token
    SSO-->>Shell: 6. 返回 Access Token
    Shell->>Shell: 7. 儲存 Token
    Shell->>MicroApp: 8. 載入子應用並傳遞 Token
    MicroApp->>API: 9. 使用 Token 呼叫 API
    API-->>MicroApp: 10. 返回資料

Token 傳遞策略

// 方式 1:通過 props 傳遞(推薦)
registerMicroApps([
  {
    name: 'orderApp',
    entry: '//localhost:3001',
    container: '#container',
    activeRule: '/order',
    props: {
      getToken: () => authStore.getToken(),
      onTokenExpired: () => authStore.refreshToken(),
    },
  },
]);

// 子應用接收
export async function mount(props: MicroAppProps) {
  const { getToken, onTokenExpired } = props;
  
  // 設定 API 攔截器
  axios.interceptors.request.use((config) => {
    config.headers.Authorization = `Bearer ${getToken()}`;
    return config;
  });
  
  axios.interceptors.response.use(
    (response) => response,
    async (error) => {
      if (error.response?.status === 401) {
        await onTokenExpired();
        return axios(error.config);
      }
      throw error;
    }
  );
}
// 方式 2:共享 Cookie(跨域需謹慎)
// 設定 Cookie 為相同主域名
document.cookie = `token=${token}; domain=.example.com; path=/; secure; samesite=strict`;

// 方式 3:LocalStorage + 同域(不推薦跨域)
localStorage.setItem('auth_token', token);

權限控制整合

// shell/src/permission.ts
interface Permission {
  code: string;
  name: string;
  microApp?: string;  // 關聯的子應用
}

class PermissionService {
  private permissions: Set<string> = new Set();
  
  async loadPermissions(userId: string) {
    const perms = await api.getUserPermissions(userId);
    this.permissions = new Set(perms.map(p => p.code));
  }
  
  hasPermission(code: string): boolean {
    return this.permissions.has(code);
  }
  
  // 檢查子應用訪問權限
  canAccessMicroApp(appName: string): boolean {
    const appPermissionMap: Record<string, string> = {
      orderApp: 'ORDER_MODULE_ACCESS',
      customerApp: 'CUSTOMER_MODULE_ACCESS',
      reportApp: 'REPORT_MODULE_ACCESS',
    };
    
    return this.hasPermission(appPermissionMap[appName]);
  }
  
  // 暴露給子應用的 API
  getPermissionsForMicroApp(appName: string): string[] {
    const prefix = appName.toUpperCase();
    return Array.from(this.permissions)
      .filter(p => p.startsWith(prefix));
  }
}

export const permissionService = new PermissionService();

5.5 本章實務案例

案例:金融系統的安全隔離設計

需求

  • 不同業務線的子應用需要完全隔離
  • 敏感操作需要二次驗證
  • 審計日誌完整記錄

實作

// 安全審計裝飾器
function auditLog(action: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function (...args: any[]) {
      const startTime = Date.now();
      
      try {
        const result = await originalMethod.apply(this, args);
        
        // 記錄成功操作
        await logService.log({
          action,
          microApp: getCurrentMicroApp(),
          userId: authStore.getUserId(),
          params: args,
          result: 'success',
          duration: Date.now() - startTime,
        });
        
        return result;
      } catch (error) {
        // 記錄失敗操作
        await logService.log({
          action,
          microApp: getCurrentMicroApp(),
          userId: authStore.getUserId(),
          params: args,
          result: 'failure',
          error: error.message,
          duration: Date.now() - startTime,
        });
        
        throw error;
      }
    };
    
    return descriptor;
  };
}

// 使用範例
class OrderService {
  @auditLog('ORDER_CREATE')
  async createOrder(data: OrderData) {
    // 業務邏輯
  }
  
  @auditLog('ORDER_APPROVE')
  async approveOrder(orderId: string) {
    // 需要二次驗證
    await this.requestSecondaryAuth();
    // 業務邏輯
  }
}

6. CI/CD 與部署策略

6.1 子應用獨立部署架構

graph TB
    subgraph "獨立部署流程"
        A[Git Push] --> B[CI Pipeline]
        B --> C{Build}
        C --> D[Unit Test]
        D --> E[E2E Test]
        E --> F[Build Artifact]
        F --> G[Deploy to CDN]
        G --> H[更新 Manifest]
    end
    
    subgraph "生產環境"
        I[CDN]
        J[Manifest Server]
        K[基座應用]
        L[子應用 A]
        M[子應用 B]
    end
    
    H --> J
    J --> K
    K --> I
    I --> L
    I --> M

部署架構設計

// 子應用 manifest 配置
interface MicroAppManifest {
  name: string;
  version: string;
  entry: string;  // JS 入口
  styles: string[];  // CSS 檔案
  publicPath: string;
  activeRule: string;
  dependencies: {
    [key: string]: string;  // 共享依賴版本
  };
  deployedAt: string;
  gitCommit: string;
}

// manifest.json 範例
{
  "apps": {
    "orderApp": {
      "name": "orderApp",
      "version": "1.2.3",
      "entry": "https://cdn.example.com/order/1.2.3/remoteEntry.js",
      "styles": ["https://cdn.example.com/order/1.2.3/styles.css"],
      "publicPath": "https://cdn.example.com/order/1.2.3/",
      "activeRule": "/order",
      "dependencies": {
        "react": "18.2.0",
        "react-dom": "18.2.0"
      },
      "deployedAt": "2026-01-23T10:00:00Z",
      "gitCommit": "abc1234"
    }
  }
}

GitHub Actions CI/CD 範例

# .github/workflows/deploy-micro-app.yml
name: Deploy Micro App

on:
  push:
    branches: [main]
    paths:
      - 'apps/order-app/**'

env:
  APP_NAME: orderApp
  CDN_BUCKET: micro-apps-cdn

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
        working-directory: apps/order-app
      
      - name: Run tests
        run: npm test
        working-directory: apps/order-app
      
      - name: Build
        run: npm run build
        working-directory: apps/order-app
        env:
          PUBLIC_PATH: https://cdn.example.com/${{ env.APP_NAME }}/${{ github.sha }}/
      
      - name: Upload to CDN
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
      
      - run: |
          aws s3 sync apps/order-app/dist \
            s3://${{ env.CDN_BUCKET }}/${{ env.APP_NAME }}/${{ github.sha }}/ \
            --cache-control "public, max-age=31536000"
      
      - name: Update Manifest
        run: |
          curl -X POST https://api.example.com/manifest/update \
            -H "Authorization: Bearer ${{ secrets.MANIFEST_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "app": "${{ env.APP_NAME }}",
              "version": "${{ github.sha }}",
              "entry": "https://cdn.example.com/${{ env.APP_NAME }}/${{ github.sha }}/remoteEntry.js"
            }'

6.2 版本相容性控管

語義化版本策略

// shared/version-check.ts
import semver from 'semver';

interface VersionCompatibility {
  shell: string;
  microApps: Record<string, string>;
  sharedLibraries: Record<string, string>;
}

const compatibilityMatrix: VersionCompatibility = {
  shell: '^2.0.0',
  microApps: {
    orderApp: '^1.0.0',
    customerApp: '^1.0.0',
  },
  sharedLibraries: {
    react: '18.x',
    '@company/design-system': '^3.0.0',
  },
};

function checkCompatibility(
  appName: string,
  appVersion: string,
  shellVersion: string
): { compatible: boolean; warnings: string[] } {
  const warnings: string[] = [];
  
  // 檢查 shell 版本相容性
  if (!semver.satisfies(shellVersion, compatibilityMatrix.shell)) {
    return {
      compatible: false,
      warnings: [`Shell version ${shellVersion} is not compatible`],
    };
  }
  
  // 檢查子應用版本
  const requiredVersion = compatibilityMatrix.microApps[appName];
  if (requiredVersion && !semver.satisfies(appVersion, requiredVersion)) {
    warnings.push(`${appName}@${appVersion} may have compatibility issues`);
  }
  
  return { compatible: true, warnings };
}

相容性檢查 Hooks

// shell/src/micro/compatibility.ts
export function createCompatibilityChecker() {
  return {
    beforeLoad: async (app: LoadableApp) => {
      const manifest = await fetchManifest(app.name);
      const shellVersion = process.env.SHELL_VERSION;
      
      const { compatible, warnings } = checkCompatibility(
        app.name,
        manifest.version,
        shellVersion
      );
      
      if (!compatible) {
        throw new Error(`Micro app ${app.name} is not compatible with current shell`);
      }
      
      warnings.forEach(w => console.warn(w));
      
      return true;
    },
  };
}

6.3 灰度發布與回滾策略

graph LR
    subgraph "灰度發布流程"
        A[新版本部署] --> B[1% 流量]
        B --> C{監控指標}
        C -->|正常| D[10% 流量]
        D --> E{監控指標}
        E -->|正常| F[50% 流量]
        F --> G{監控指標}
        G -->|正常| H[100% 全量]
        C -->|異常| I[回滾]
        E -->|異常| I
        G -->|異常| I
    end

灰度策略實作

// manifest-server/src/canary.ts
interface CanaryConfig {
  app: string;
  oldVersion: string;
  newVersion: string;
  percentage: number;  // 0-100
  rules: CanaryRule[];
}

interface CanaryRule {
  type: 'user_id' | 'region' | 'random';
  value: string | number;
}

class CanaryService {
  private configs: Map<string, CanaryConfig> = new Map();
  
  // 判斷使用者應該使用哪個版本
  getVersionForUser(appName: string, context: UserContext): string {
    const config = this.configs.get(appName);
    
    if (!config) {
      return this.getStableVersion(appName);
    }
    
    // 規則判斷
    if (this.matchCanaryRules(config.rules, context)) {
      return config.newVersion;
    }
    
    // 隨機灰度
    const hash = this.hashUserId(context.userId);
    if (hash % 100 < config.percentage) {
      return config.newVersion;
    }
    
    return config.oldVersion;
  }
  
  private hashUserId(userId: string): number {
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      hash = ((hash << 5) - hash) + userId.charCodeAt(i);
      hash = hash & hash;
    }
    return Math.abs(hash);
  }
  
  private matchCanaryRules(rules: CanaryRule[], context: UserContext): boolean {
    return rules.some(rule => {
      switch (rule.type) {
        case 'user_id':
          return context.userId === rule.value;
        case 'region':
          return context.region === rule.value;
        default:
          return false;
      }
    });
  }
}

快速回滾機制

// manifest-server/src/rollback.ts
class RollbackService {
  private versionHistory: Map<string, string[]> = new Map();
  
  // 記錄版本歷史
  recordDeployment(appName: string, version: string) {
    if (!this.versionHistory.has(appName)) {
      this.versionHistory.set(appName, []);
    }
    
    const history = this.versionHistory.get(appName)!;
    history.push(version);
    
    // 只保留最近 10 個版本
    if (history.length > 10) {
      history.shift();
    }
  }
  
  // 回滾到上一個版本
  async rollback(appName: string): Promise<string> {
    const history = this.versionHistory.get(appName);
    
    if (!history || history.length < 2) {
      throw new Error(`No previous version available for ${appName}`);
    }
    
    const previousVersion = history[history.length - 2];
    
    // 更新 manifest
    await this.updateManifest(appName, previousVersion);
    
    // 通知基座刷新
    await this.notifyShell(appName);
    
    // 記錄回滾事件
    await this.logRollback(appName, previousVersion);
    
    return previousVersion;
  }
  
  private async notifyShell(appName: string) {
    // 通過 WebSocket 或 Server-Sent Events 通知
    eventEmitter.emit('app:version-changed', { appName });
  }
}

6.4 本章實務案例

案例:電商大促期間的部署策略

背景

  • 雙 11 期間需要快速部署促銷功能
  • 風險控制要求極高
  • 需要秒級回滾能力

解決方案

// 部署流水線配置
const deploymentPipeline = {
  stages: [
    {
      name: 'canary',
      percentage: 1,
      duration: '30m',
      metrics: {
        errorRate: { threshold: 0.01, action: 'rollback' },
        p99Latency: { threshold: 500, action: 'alert' },
      },
    },
    {
      name: 'early-adopters',
      percentage: 10,
      duration: '2h',
      metrics: {
        errorRate: { threshold: 0.005, action: 'rollback' },
        conversionRate: { threshold: -0.05, action: 'alert' },  // 相對變化
      },
    },
    {
      name: 'full-rollout',
      percentage: 100,
      approval: 'manual',  // 需要人工確認
    },
  ],
  rollback: {
    automatic: true,
    timeToRollback: '30s',
    keepOldVersion: '24h',  // CDN 保留舊版本
  },
};

7. 效能與可維運性

7.1 首屏載入優化

微前端的首屏載入是效能的關鍵挑戰,需要載入基座 + 子應用 + 共享依賴。

graph TB
    subgraph "載入流程"
        A[HTML] --> B[基座 JS]
        B --> C[路由解析]
        C --> D[remoteEntry.js]
        D --> E[子應用 JS]
        E --> F[渲染]
    end
    
    subgraph "耗時分析"
        A1[~50ms]
        A2[~200ms]
        A3[~10ms]
        A4[~100ms]
        A5[~300ms]
        A6[~100ms]
    end
    
    A --- A1
    B --- A2
    C --- A3
    D --- A4
    E --- A5
    F --- A6

優化策略總覽

策略優化效果實作複雜度優先級
預載入 remoteEntry★★★★P0
共享依賴外部化★★★★★P0
路由級程式碼分割★★★★P0
Service Worker 快取★★★P1
子應用預渲染★★★P2
HTTP/2 推送★★P2

預載入實作

// shell/src/preload.ts
class PreloadManager {
  private preloadedApps: Set<string> = new Set();
  private preloadLinks: Map<string, HTMLLinkElement> = new Map();
  
  // 預載入子應用
  preload(appName: string, entry: string) {
    if (this.preloadedApps.has(appName)) return;
    
    // 預載入 remoteEntry.js
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'script';
    link.href = entry;
    link.crossOrigin = 'anonymous';
    document.head.appendChild(link);
    
    this.preloadLinks.set(appName, link);
    this.preloadedApps.add(appName);
  }
  
  // 預取子應用(低優先級)
  prefetch(appName: string, entry: string) {
    if (this.preloadedApps.has(appName)) return;
    
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = entry;
    document.head.appendChild(link);
    
    this.preloadLinks.set(appName, link);
    this.preloadedApps.add(appName);
  }
  
  // 根據路由預測預載入
  predictivePreload(currentRoute: string) {
    const predictions = this.getPredictedRoutes(currentRoute);
    
    predictions.forEach(({ appName, entry, probability }) => {
      if (probability > 0.7) {
        this.preload(appName, entry);
      } else if (probability > 0.3) {
        this.prefetch(appName, entry);
      }
    });
  }
  
  private getPredictedRoutes(currentRoute: string) {
    // 基於歷史資料或規則預測下一個可能訪問的路由
    const routeGraph: Record<string, { appName: string; entry: string; probability: number }[]> = {
      '/': [
        { appName: 'orderApp', entry: '/order/remoteEntry.js', probability: 0.6 },
        { appName: 'customerApp', entry: '/customer/remoteEntry.js', probability: 0.3 },
      ],
      '/order': [
        { appName: 'customerApp', entry: '/customer/remoteEntry.js', probability: 0.8 },
      ],
    };
    
    return routeGraph[currentRoute] || [];
  }
}

export const preloadManager = new PreloadManager();

// 在路由守衛中使用
router.afterEach((to) => {
  preloadManager.predictivePreload(to.path);
});

共享依賴 CDN 外部化

// webpack.config.js
module.exports = {
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
    vue: 'Vue',
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html',
      inject: true,
    }),
  ],
};

// index.html - 使用 CDN
<!DOCTYPE html>
<html>
<head>
  <!-- 預連接 CDN -->
  <link rel="preconnect" href="https://cdn.jsdelivr.net">
  
  <!-- 關鍵依賴使用 preload -->
  <link rel="preload" href="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js" as="script">
  <link rel="preload" href="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js" as="script">
</head>
<body>
  <div id="root"></div>
  
  <!-- 按順序載入 -->
  <script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
</body>
</html>

Service Worker 快取策略

// sw.ts - Service Worker
const CACHE_NAME = 'micro-frontend-v1';
const REMOTE_ENTRY_CACHE = 'remote-entries-v1';

// 快取策略
const cacheStrategies = {
  // 靜態資源:Cache First
  static: async (request: Request) => {
    const cached = await caches.match(request);
    if (cached) return cached;
    
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  },
  
  // remoteEntry:Stale While Revalidate
  remoteEntry: async (request: Request) => {
    const cache = await caches.open(REMOTE_ENTRY_CACHE);
    const cached = await cache.match(request);
    
    const fetchPromise = fetch(request).then(response => {
      cache.put(request, response.clone());
      return response;
    });
    
    return cached || fetchPromise;
  },
  
  // API:Network First
  api: async (request: Request) => {
    try {
      const response = await fetch(request);
      return response;
    } catch (error) {
      const cached = await caches.match(request);
      if (cached) return cached;
      throw error;
    }
  },
};

self.addEventListener('fetch', (event: FetchEvent) => {
  const url = new URL(event.request.url);
  
  if (url.pathname.includes('remoteEntry.js')) {
    event.respondWith(cacheStrategies.remoteEntry(event.request));
  } else if (url.pathname.startsWith('/api/')) {
    event.respondWith(cacheStrategies.api(event.request));
  } else if (url.pathname.match(/\.(js|css|png|jpg|svg)$/)) {
    event.respondWith(cacheStrategies.static(event.request));
  }
});

7.2 過度拆分的反效果

⚠️ 警告:微前端不是拆得越細越好,過度拆分會帶來嚴重問題。

過度拆分的徵兆

徵兆說明影響
子應用 < 3 個頁面單個子應用太小架構開銷 > 收益
頻繁跨應用通訊每次操作都需要事件通訊效能差、難維護
共享依賴爆炸50+ 個共享套件版本衝突、載入慢
部署頻率極低子應用很少獨立部署失去微前端意義
團隊邊界模糊一個團隊維護 5+ 子應用增加溝通成本

合理拆分的指導原則

graph TB
    subgraph "合理拆分"
        A[業務領域獨立] --> E[✅ 適合拆分]
        B[團隊獨立負責] --> E
        C[獨立部署需求] --> E
        D[技術棧差異] --> E
    end
    
    subgraph "不合理拆分"
        F[純粹按頁面拆] --> J[❌ 不建議]
        G[為了用新技術] --> J
        H[缺乏明確邊界] --> J
        I[高度資料耦合] --> J
    end

拆分決策清單

// 用於評估是否需要拆分為獨立子應用
interface SplitDecisionCriteria {
  // 必要條件(需全部滿足)
  required: {
    hasIndependentTeam: boolean;      // 有獨立團隊負責
    hasClearBoundary: boolean;        // 業務邊界清晰
    hasIndependentDeployNeed: boolean; // 需要獨立部署
  };
  
  // 加分項(滿足越多越適合)
  beneficial: {
    differentTechStack: boolean;      // 技術棧不同
    differentReleaseSchedule: boolean; // 發布週期不同
    lowCouplingWithOthers: boolean;   // 與其他模組低耦合
    largeCodebase: boolean;           // 程式碼量大(>50k 行)
    complexDomain: boolean;           // 領域複雜度高
  };
}

function shouldSplit(criteria: SplitDecisionCriteria): {
  decision: 'split' | 'no-split' | 'reconsider';
  reason: string;
} {
  const { required, beneficial } = criteria;
  
  // 必要條件不滿足 → 不拆分
  if (!required.hasIndependentTeam || !required.hasClearBoundary) {
    return {
      decision: 'no-split',
      reason: '缺乏獨立團隊或業務邊界不清晰',
    };
  }
  
  // 計算加分項
  const benefitScore = Object.values(beneficial).filter(Boolean).length;
  
  if (benefitScore >= 3 && required.hasIndependentDeployNeed) {
    return { decision: 'split', reason: '符合拆分條件' };
  }
  
  if (benefitScore >= 2) {
    return { decision: 'reconsider', reason: '可考慮先用 Monorepo 模組化' };
  }
  
  return { decision: 'no-split', reason: '拆分收益不明顯' };
}

7.3 Monitoring / Logging 建議

監控架構

graph TB
    subgraph "前端監控"
        A[效能監控] --> E[監控平台]
        B[錯誤監控] --> E
        C[用戶行為] --> E
        D[業務指標] --> E
    end
    
    subgraph "監控維度"
        E --> F[基座層]
        E --> G[子應用層]
        E --> H[共享層]
    end
    
    subgraph "告警"
        F --> I[告警系統]
        G --> I
        H --> I
    end

統一日誌格式

// shared/src/logger.ts
interface LogEntry {
  timestamp: string;
  level: 'debug' | 'info' | 'warn' | 'error';
  microApp: string;
  traceId: string;
  userId?: string;
  message: string;
  context?: Record<string, any>;
  stack?: string;
}

class MicroAppLogger {
  private appName: string;
  private traceId: string;
  
  constructor(appName: string) {
    this.appName = appName;
    this.traceId = this.getOrCreateTraceId();
  }
  
  private getOrCreateTraceId(): string {
    // 從基座獲取或生成新的 traceId
    return window.__SHELL_TRACE_ID__ || crypto.randomUUID();
  }
  
  private createEntry(
    level: LogEntry['level'],
    message: string,
    context?: Record<string, any>
  ): LogEntry {
    return {
      timestamp: new Date().toISOString(),
      level,
      microApp: this.appName,
      traceId: this.traceId,
      userId: this.getUserId(),
      message,
      context,
    };
  }
  
  info(message: string, context?: Record<string, any>) {
    const entry = this.createEntry('info', message, context);
    this.send(entry);
    console.log(`[${this.appName}]`, message, context);
  }
  
  error(message: string, error?: Error, context?: Record<string, any>) {
    const entry = this.createEntry('error', message, {
      ...context,
      errorMessage: error?.message,
      stack: error?.stack,
    });
    this.send(entry);
    console.error(`[${this.appName}]`, message, error);
  }
  
  private async send(entry: LogEntry) {
    // 批次發送到日誌服務
    await logBuffer.add(entry);
  }
  
  private getUserId(): string | undefined {
    return window.__SHELL_USER_ID__;
  }
}

// 使用範例
const logger = new MicroAppLogger('orderApp');
logger.info('Order created', { orderId: '123', amount: 1000 });
logger.error('Failed to submit order', error, { orderId: '123' });

效能監控

// shared/src/performance.ts
interface PerformanceMetrics {
  microApp: string;
  metrics: {
    // 載入時間
    loadTime: number;
    mountTime: number;
    firstContentfulPaint: number;
    largestContentfulPaint: number;
    
    // 運行時指標
    jsHeapSize: number;
    domNodes: number;
    
    // 自定義指標
    apiLatency: Record<string, number>;
  };
}

class PerformanceMonitor {
  private appName: string;
  private startTime: number = 0;
  
  constructor(appName: string) {
    this.appName = appName;
  }
  
  // 子應用載入開始
  markLoadStart() {
    this.startTime = performance.now();
    performance.mark(`${this.appName}-load-start`);
  }
  
  // 子應用掛載完成
  markMountComplete() {
    performance.mark(`${this.appName}-mount-complete`);
    
    const loadTime = performance.now() - this.startTime;
    
    performance.measure(
      `${this.appName}-load-time`,
      `${this.appName}-load-start`,
      `${this.appName}-mount-complete`
    );
    
    this.reportMetrics({
      loadTime,
      mountTime: loadTime,
    });
  }
  
  // 收集 Web Vitals
  collectWebVitals() {
    // 使用 web-vitals 庫
    import('web-vitals').then(({ onCLS, onFID, onLCP, onFCP, onTTFB }) => {
      onCLS((metric) => this.reportMetric('CLS', metric.value));
      onFID((metric) => this.reportMetric('FID', metric.value));
      onLCP((metric) => this.reportMetric('LCP', metric.value));
      onFCP((metric) => this.reportMetric('FCP', metric.value));
      onTTFB((metric) => this.reportMetric('TTFB', metric.value));
    });
  }
  
  private reportMetrics(metrics: Partial<PerformanceMetrics['metrics']>) {
    // 上報到監控平台
    navigator.sendBeacon('/api/metrics', JSON.stringify({
      microApp: this.appName,
      timestamp: Date.now(),
      metrics,
    }));
  }
  
  private reportMetric(name: string, value: number) {
    this.reportMetrics({ [name]: value } as any);
  }
}

錯誤邊界與上報

// React 錯誤邊界
class MicroAppErrorBoundary extends React.Component<
  { appName: string; fallback: React.ReactNode; children: React.ReactNode },
  { hasError: boolean; error?: Error }
> {
  state = { hasError: false, error: undefined };
  
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // 上報錯誤
    errorReporter.report({
      microApp: this.props.appName,
      error: {
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
      },
      context: {
        url: window.location.href,
        userAgent: navigator.userAgent,
      },
    });
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}

// 全域錯誤捕獲
window.addEventListener('error', (event) => {
  errorReporter.report({
    microApp: getCurrentMicroApp() || 'shell',
    error: {
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
    },
  });
});

window.addEventListener('unhandledrejection', (event) => {
  errorReporter.report({
    microApp: getCurrentMicroApp() || 'shell',
    error: {
      message: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
    },
  });
});

7.4 本章實務案例

案例:電商平台效能優化實戰

問題

  • 首屏載入時間 > 5 秒
  • 切換子應用卡頓明顯
  • 用戶流失率高

優化措施與效果

優化項實作效果
預載入在首頁預載入高頻子應用-800ms
CDN 外部化React/Vue 走 CDN-400ms
Service Worker快取 remoteEntry-300ms(二次訪問)
程式碼分割路由級懶載入-500ms
壓縮優化Brotli + Tree Shaking-200ms

最終效果:首屏時間從 5.2s 降至 2.1s


8. AI 輔助微前端開發實務

8.1 用 AI 產生子應用模板

Prompt 範例:生成子應用骨架

## 角色
你是一位微前端架構專家,熟悉 Module Federation 和 qiankun。

## 任務
請為我生成一個 Vue 3 子應用的完整專案模板,需符合以下要求:

1. **技術棧**
   - Vue 3 + TypeScript + Vite
   - Pinia 狀態管理
   - Vue Router 4
   - Module Federation 配置

2. **微前端整合**
   - 可獨立運行,也可作為子應用載入
   - 與基座共享 Vue、Pinia
   - 生命週期函數(bootstrap, mount, unmount)
   - 接收基座傳遞的 props(token, eventBus)

3. **專案結構**
   請生成完整的目錄結構和關鍵檔案內容

4. **最佳實務**
   - CSS 使用 scoped 或 CSS Modules
   - 錯誤邊界處理
   - 效能監控 hook

AI 輔助生成的範例程式碼

// src/main.ts - 子應用入口
import { createApp, App as VueApp } from 'vue';
import { createPinia } from 'pinia';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './router';
import { setupGlobalProps } from './micro/props';

let app: VueApp | null = null;
let router: ReturnType<typeof createRouter> | null = null;
let pinia: ReturnType<typeof createPinia> | null = null;

// 獨立運行
if (!window.__POWERED_BY_QIANKUN__) {
  bootstrap().then(mount);
}

export async function bootstrap() {
  console.log('[orderApp] bootstrap');
  // 初始化工作
}

export async function mount(props: MicroAppProps = {}) {
  console.log('[orderApp] mount', props);
  
  const { container, getToken, eventBus } = props;
  
  // 創建路由
  router = createRouter({
    history: createWebHistory(
      window.__POWERED_BY_QIANKUN__ ? '/order' : '/'
    ),
    routes,
  });
  
  // 創建狀態管理
  pinia = createPinia();
  
  // 創建 Vue 應用
  app = createApp(App);
  app.use(router);
  app.use(pinia);
  
  // 設置全域屬性
  setupGlobalProps(app, { getToken, eventBus });
  
  // 掛載
  const mountElement = container
    ? container.querySelector('#app')
    : document.getElementById('app');
    
  app.mount(mountElement!);
}

export async function unmount() {
  console.log('[orderApp] unmount');
  
  app?.unmount();
  app = null;
  router = null;
  pinia = null;
}

export async function update(props: MicroAppProps) {
  console.log('[orderApp] update', props);
  // 處理 props 更新
}

8.2 用 AI Review 微前端架構設計

Prompt 範例:架構審查

## 角色
你是一位資深微前端架構師,請審查以下架構設計。

## 當前架構

Shell (React 17) ├── shared-components (npm package) ├── micro-order (React 18) ├── micro-customer (Vue 3) ├── micro-report (Angular 15) └── micro-settings (React 17)


## 需要你分析
1. 這個架構有什麼潛在問題?
2. React 17 和 18 並存會有什麼風險?
3. 共享組件用 npm package 是否合適?
4. 請給出改進建議

## 請用以下格式回覆
- 🔴 嚴重問題:...
- 🟡 潛在風險:...
- 🟢 優點:...
- 💡 改進建議:...

AI 審查回覆範例

## 架構審查報告

### 🔴 嚴重問題
1. **React 版本衝突**
   - Shell (React 17) 與 micro-order (React 18) 版本不一致
   - Module Federation 的 singleton 配置可能導致運行時錯誤
   - 建議:統一升級到 React 18,或配置 strictVersion: false

2. **shared-components 用 npm package**
   - 更新組件需要所有子應用重新發布
   - 喪失 Module Federation 的獨立部署優勢
   - 建議:改為 Remote 模組,運行時載入

### 🟡 潛在風險
1. CSS 衝突未提及隔離方案
2. 缺乏統一的錯誤處理機制
3. Angular 15 即將 EOL,需規劃升級

### 🟢 優點
1. 技術棧多樣化符合微前端理念
2. 業務領域拆分看起來合理

### 💡 改進建議
1. 將 shared-components 改為 Module Federation Remote
2. 統一 React 版本到 18.x
3. 加入 CSS 隔離方案(建議 CSS Modules)
4. 建立跨應用錯誤邊界

8.3 用 AI 協助 Dependency / 風險分析

自動化依賴分析腳本

// scripts/analyze-deps.ts
import * as fs from 'fs';
import * as path from 'path';

interface DependencyAnalysis {
  app: string;
  dependencies: Record<string, string>;
  devDependencies: Record<string, string>;
}

interface ConflictReport {
  package: string;
  versions: { app: string; version: string }[];
  severity: 'critical' | 'warning' | 'info';
  suggestion: string;
}

async function analyzeDependencies(appsDir: string): Promise<ConflictReport[]> {
  const apps = fs.readdirSync(appsDir);
  const allDeps: DependencyAnalysis[] = [];
  
  // 收集所有依賴
  for (const app of apps) {
    const pkgPath = path.join(appsDir, app, 'package.json');
    if (fs.existsSync(pkgPath)) {
      const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
      allDeps.push({
        app,
        dependencies: pkg.dependencies || {},
        devDependencies: pkg.devDependencies || {},
      });
    }
  }
  
  // 分析衝突
  const reports: ConflictReport[] = [];
  const criticalPackages = ['react', 'react-dom', 'vue', '@angular/core'];
  
  const packageVersions = new Map<string, { app: string; version: string }[]>();
  
  for (const { app, dependencies } of allDeps) {
    for (const [pkg, version] of Object.entries(dependencies)) {
      if (!packageVersions.has(pkg)) {
        packageVersions.set(pkg, []);
      }
      packageVersions.get(pkg)!.push({ app, version });
    }
  }
  
  // 生成報告
  for (const [pkg, versions] of packageVersions) {
    const uniqueVersions = [...new Set(versions.map(v => v.version))];
    
    if (uniqueVersions.length > 1) {
      const isCritical = criticalPackages.includes(pkg);
      
      reports.push({
        package: pkg,
        versions,
        severity: isCritical ? 'critical' : 'warning',
        suggestion: isCritical
          ? `統一 ${pkg} 版本,建議使用 ${uniqueVersions.sort().pop()}`
          : `建議統一 ${pkg} 版本以減少 bundle 大小`,
      });
    }
  }
  
  return reports;
}

// 生成 AI 可讀的報告
async function generateAIPrompt(reports: ConflictReport[]): Promise<string> {
  const critical = reports.filter(r => r.severity === 'critical');
  const warnings = reports.filter(r => r.severity === 'warning');
  
  return `
## 依賴衝突分析報告

### 嚴重衝突(需立即處理)
${critical.map(r => `
- **${r.package}**
  - 衝突版本:${r.versions.map(v => `${v.app}: ${v.version}`).join(', ')}
  - 建議:${r.suggestion}
`).join('')}

### 警告(建議處理)
${warnings.slice(0, 10).map(r => `
- **${r.package}**:${r.versions.length} 個不同版本
`).join('')}

請分析這些衝突可能導致的運行時問題,並提供解決方案。
  `;
}

8.4 本章實務案例

案例:用 AI 加速微前端導入

背景

  • 傳統單體應用需要遷移到微前端
  • 團隊缺乏微前端經驗
  • 時程緊迫(3 個月)

AI 輔助流程

階段AI 輔助內容節省時間
架構設計生成架構方案並審查2 週 → 3 天
模板生成自動生成子應用骨架1 週 → 1 天
依賴分析自動化衝突檢測3 天 → 2 小時
Code ReviewAI 輔助審查 MF 配置持續節省
文件撰寫自動生成架構文件1 週 → 2 天

總結:AI 輔助使專案提前 3 週完成


9. 實戰經驗總結

9.1 成功導入的關鍵因素

mindmap
  root((微前端成功導入))
    組織面
      清晰的團隊邊界
      足夠的團隊規模
      管理層支持
    技術面
      成熟的 CI/CD
      完善的監控
      標準化規範
    業務面
      清晰的領域邊界
      合理的拆分粒度
      漸進式遷移

9.2 架構演進路線建議

階段目標關鍵動作
Phase 1
基礎建設
建立微前端基礎框架選型、POC、基座開發
Phase 2
試點導入
驗證可行性1-2 個子應用上線
Phase 3
規模化
標準化複製子應用模板、規範文件
Phase 4
優化治理
效能與穩定性監控完善、自動化測試

9.3 技術選型決策表

場景推薦方案次選方案不推薦
新建大型系統Module Federationqiankuniframe
舊系統漸進遷移qiankunsingle-spa大爆炸重寫
需要強隔離wujie(iframe 增強)qiankun shadow無隔離方案
React 同構系統Module Federation自研方案qiankun
跨框架系統qiankunWeb Components強制統一框架

9.4 關鍵經驗教訓

✅ 應該做的

  1. 先做好領域建模

    • 微前端是技術手段,不是目的
    • 業務邊界決定拆分方式
  2. 統一基礎設施

    • 統一日誌格式
    • 統一監控埋點
    • 統一錯誤處理
  3. 建立規範文件

    • 子應用開發規範
    • 部署流程規範
    • Code Review 檢查清單
  4. 漸進式遷移

    • 從低風險模組開始
    • 逐步擴大範圍
    • 保持回滾能力

❌ 不應該做的

  1. 追求過度解耦

    • 不是所有模組都要獨立部署
    • 過度拆分會增加複雜度
  2. 忽視效能問題

    • 首屏載入是關鍵指標
    • 需要從架構層面考慮
  3. 缺乏 E2E 測試

    • 子應用間整合測試很重要
    • 不能只依賴單元測試
  4. 沒有灰度發布機制

    • 微前端更需要灰度能力
    • 快速回滾是必備功能

10. 企業導入微前端常見失敗原因

10.1 十大失敗原因清單

排名失敗原因發生頻率影響程度預防措施
1業務邊界不清晰就開始拆分★★★★★致命先做領域建模
2團隊規模不足硬要導入★★★★☆嚴重評估團隊規模
3缺乏統一的基礎設施★★★★☆嚴重先建 CI/CD、監控
4共享依賴版本管理混亂★★★★☆嚴重統一版本策略
5子應用間過度耦合★★★☆☆中等限制通訊方式
6忽視首屏效能優化★★★☆☆中等效能預算機制
7沒有完善的測試策略★★★☆☆中等E2E + 契約測試
8文件與規範缺失★★☆☆☆輕微建立規範文件
9缺乏回滾與灰度機制★★☆☆☆中等部署流水線完善
10為了技術而技術★★★★☆嚴重明確業務價值

10.2 失敗案例分析

案例 1:過早拆分的代價

背景

  • 新創公司,10 人開發團隊
  • 為了「架構先進」導入微前端
  • 拆分成 8 個子應用

問題

  • 每次功能開發需要跨 3-4 個 repo
  • 部署流程複雜,經常出錯
  • 團隊花大量時間處理「架構問題」

教訓

  • 團隊規模不足時,微前端是負擔
  • 應該先從 Monorepo 模組化開始

案例 2:版本地獄

背景

  • 大型企業,5 個團隊各自維護子應用
  • 沒有統一的依賴版本管理
  • React 版本從 16 到 18 都有

問題

  • 某次更新後全站白屏
  • 花了 3 天才找到是 React 版本衝突
  • 緊急回滾造成業務損失

教訓

  • 核心依賴必須有嚴格的版本策略
  • 需要有相容性測試機制

案例 3:效能災難

背景

  • 電商 C 端網站
  • 為了技術棧自由,導入微前端
  • 首頁載入了 3 個子應用的 remoteEntry

問題

  • 首屏時間從 2 秒惡化到 7 秒
  • 轉換率下降 30%
  • 最終被迫回退到單體架構

教訓

  • C 端應用對效能敏感,微前端需謹慎
  • 需要有效能預算和監控

10.3 失敗預警信號

當出現以下信號時,應該重新評估微前端策略:

const warningSignals = {
  // 🔴 高風險信號
  critical: [
    '超過 50% 的開發時間花在「解決架構問題」',
    '部署失敗率超過 10%',
    '首屏載入時間超過 5 秒',
    '頻繁出現跨子應用的 Bug',
  ],
  
  // 🟡 中風險信號  
  warning: [
    '新人上手時間超過 2 週',
    '每次需求都需要改 3 個以上子應用',
    '共享依賴版本超過 3 種',
    'E2E 測試經常失敗',
  ],
  
  // 🟢 需關注信號
  attention: [
    '子應用間通訊事件超過 20 種',
    '基座程式碼持續膨脹',
    '文件跟不上架構變化',
  ],
};

11. 檢查清單(Checklist)

11.1 導入前評估清單

## 📋 微前端導入前評估檢查清單

### 組織與團隊
- [ ] 團隊規模 >= 3 個獨立團隊
- [ ] 每個團隊有明確的業務領域負責
- [ ] 管理層理解並支持微前端的長期投入
- [ ] 有專人負責基礎設施維護

### 技術基礎
- [ ] 有成熟的 CI/CD 流程
- [ ] 有完善的監控和日誌系統
- [ ] 有自動化測試體系
- [ ] 團隊具備前端工程化經驗

### 業務需求
- [ ] 業務領域邊界清晰可拆分
- [ ] 有獨立部署的實際需求
- [ ] 對首屏效能的要求已評估
- [ ] 漸進式遷移路徑已規劃

### 風險評估
- [ ] 已評估過度拆分的風險
- [ ] 已評估版本衝突的風險
- [ ] 已評估效能下降的風險
- [ ] 有回滾和應急方案

11.2 架構設計檢查清單

## 📋 微前端架構設計檢查清單

### 基座設計
- [ ] 路由管理方案確定
- [ ] 認證/授權整合方案確定
- [ ] 全域狀態管理方案確定
- [ ] 錯誤邊界處理方案確定
- [ ] 子應用載入器設計完成

### 子應用規範
- [ ] 生命週期函數規範(bootstrap/mount/unmount)
- [ ] 路由 base path 規範
- [ ] CSS 隔離方案(Module/BEM/Shadow DOM)
- [ ] JS 沙箱需求評估
- [ ] 打包輸出格式規範

### 共享機制
- [ ] 共享依賴清單確定
- [ ] 版本管理策略確定
- [ ] 共享組件載入方式確定
- [ ] 事件通訊規範確定

### 部署架構
- [ ] CDN 部署方案確定
- [ ] Manifest 管理方案確定
- [ ] 灰度發布策略確定
- [ ] 回滾機制確定

11.3 子應用開發檢查清單

## 📋 子應用開發檢查清單

### 專案初始化
- [ ] 使用團隊標準模板建立專案
- [ ] 配置正確的 Module Federation 設定
- [ ] 實現標準生命週期函數
- [ ] 配置獨立運行模式

### 開發規範
- [ ] 使用 TypeScript 強型別
- [ ] CSS 使用 scoped 或 CSS Modules
- [ ] 遵循統一的程式碼風格
- [ ] 添加必要的 JSDoc 註解

### 整合測試
- [ ] 單元測試覆蓋率 >= 80%
- [ ] 與基座整合測試通過
- [ ] 與其他子應用共存測試通過
- [ ] 效能指標符合預算

### 部署前檢查
- [ ] 共享依賴版本與基座一致
- [ ] 打包產物大小合理(< 500KB gzip)
- [ ] 無 console.log 等除錯程式碼
- [ ] 錯誤監控已接入

11.4 上線前檢查清單

## 📋 微前端上線前最終檢查清單

### 功能驗證
- [ ] 所有子應用功能正常
- [ ] 子應用切換流暢
- [ ] 路由同步正確
- [ ] 認證狀態正確傳遞
- [ ] 跨應用通訊正常

### 效能驗證
- [ ] 首屏載入時間 < 3 秒
- [ ] LCP < 2.5 秒
- [ ] FID < 100 毫秒
- [ ] CLS < 0.1
- [ ] 記憶體無明顯洩漏

### 穩定性驗證
- [ ] 錯誤監控已生效
- [ ] 日誌收集正常
- [ ] 灰度發布機制就緒
- [ ] 回滾流程已演練
- [ ] 應急聯絡人已確認

### 文件與培訓
- [ ] 架構文件已更新
- [ ] 操作手冊已完成
- [ ] 團隊培訓已完成
- [ ] FAQ 文件已準備

附錄

A. 推薦工具與資源

類別工具說明
框架qiankun螞蟻金服出品,功能完整
框架Module FederationWebpack 5 原生支援
框架single-spa輕量、框架無關
監控Sentry錯誤監控
效能Web VitalsGoogle 效能指標
測試CypressE2E 測試
測試Pact契約測試

B. 參考文獻

  1. Micro Frontends - Martin Fowler
  2. Module Federation 官方文件
  3. qiankun 官方文件
  4. single-spa 官方文件

C. 術語表

術語說明
Shell基座應用,負責載入和管理子應用
RemoteModule Federation 中暴露模組的應用
HostModule Federation 中消費模組的應用
Sandbox沙箱,用於隔離 JS 執行環境
remoteEntry.jsModule Federation 的入口檔案

文件維護資訊

  • 作者:微前端架構團隊
  • 版本:1.0
  • 最後更新:2026-01-23
  • 下次審閱:2026-07-01