微前端架构设计模式完全指南

HTMLPAGE 团队
20 分钟阅读

深入理解微前端架构的设计理念和实现方案,从技术选型到落地实践,掌握构建可扩展、独立部署的大型前端应用的方法论。

#微前端 #架构设计 #Module Federation #qiankun #前端工程化

微前端架构设计模式完全指南

什么是微前端

微前端是一种将前端应用分解为更小、更简单的部分的架构模式。每个部分可以由不同的团队独立开发、测试和部署。

传统单体前端 vs 微前端架构:

传统单体前端:
┌─────────────────────────────────────────────────────────────┐
│                    大型单体应用                              │
│  ┌─────────────────────────────────────────────────────────┐│
│  │  功能A + 功能B + 功能C + 功能D + 功能E + ...            ││
│  │  共享状态、共享依赖、共享构建                            ││
│  └─────────────────────────────────────────────────────────┘│
│                                                             │
│  问题:                                                     │
│  • 代码耦合严重                                             │
│  • 构建时间长                                               │
│  • 部署风险高                                               │
│  • 团队协作困难                                             │
└─────────────────────────────────────────────────────────────┘

微前端架构:
┌─────────────────────────────────────────────────────────────┐
│                      容器/主应用                             │
├─────────────┬─────────────┬─────────────┬───────────────────┤
│   子应用 A   │   子应用 B   │   子应用 C   │    子应用 D      │
│  (团队 A)   │  (团队 B)   │  (团队 C)   │   (团队 D)       │
│   Vue 3     │   React     │   Angular   │   Nuxt           │
│  独立部署   │  独立部署   │  独立部署   │   独立部署        │
└─────────────┴─────────────┴─────────────┴───────────────────┘
                                                             
  优势:                                                     
  • 技术栈无关                                               
  • 独立开发部署                                             
  • 增量升级                                                 
  • 团队自治                                                 

何时需要微前端

适用场景评估:

✅ 适合微前端的场景:
• 大型企业级应用(100+ 页面)
• 多团队并行开发
• 需要集成遗留系统
• 不同业务线技术栈不统一
• 需要独立部署和发布

❌ 不适合微前端的场景:
• 小型项目(<50 页面)
• 单人或小团队开发
• 对性能要求极高
• 技术栈统一且稳定
• 业务耦合度高

微前端实现方案对比

主流方案对比

方案对比表:

┌──────────────────┬────────────────┬────────────────┬─────────────────┐
│      方案        │   Module       │    qiankun     │   single-spa    │
│                  │   Federation   │                │                 │
├──────────────────┼────────────────┼────────────────┼─────────────────┤
│ 实现原理         │ Webpack 5 原生 │ JS 沙箱隔离    │ 路由劫持        │
│ 学习成本         │ 中             │ 低             │ 高              │
│ 隔离性           │ 弱             │ 强             │ 中              │
│ 性能             │ 优             │ 良             │ 良              │
│ 技术栈限制       │ Webpack 5+     │ 无限制         │ 无限制          │
│ 通信机制         │ 共享模块       │ 全局状态       │ 自定义事件      │
│ 适用场景         │ 新项目         │ 企业级应用     │ 需要高度自定义  │
└──────────────────┴────────────────┴────────────────┴─────────────────┘

Module Federation 方案

基本概念

Module Federation(模块联邦)是 Webpack 5 的内置功能,允许多个独立的构建组成一个应用。

// 核心概念
const concepts = {
  // Host:消费远程模块的应用
  host: '主应用,加载和使用其他应用暴露的模块',
  
  // Remote:提供模块的应用
  remote: '子应用,将自己的模块暴露给其他应用使用',
  
  // Shared:共享依赖
  shared: '多个应用共享的依赖,避免重复加载',
  
  // Exposes:暴露的模块
  exposes: '子应用向外暴露的模块',
  
  // Remotes:远程模块配置
  remotes: '主应用中配置的远程子应用'
};

主应用配置

// host-app/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      
      // 远程应用配置
      remotes: {
        // 格式:别名: '应用名@远程入口URL'
        productApp: 'productApp@http://localhost:3001/remoteEntry.js',
        userApp: 'userApp@http://localhost:3002/remoteEntry.js',
        orderApp: 'orderApp@http://localhost:3003/remoteEntry.js'
      },
      
      // 共享依赖
      shared: {
        react: {
          singleton: true,       // 单例模式
          requiredVersion: '^18.0.0',
          eager: true            // 立即加载
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^18.0.0',
          eager: true
        },
        // 共享状态管理
        zustand: {
          singleton: true,
          requiredVersion: '^4.0.0'
        }
      }
    })
  ]
};

子应用配置

// product-app/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'productApp',
      
      // 远程入口文件名
      filename: 'remoteEntry.js',
      
      // 暴露的模块
      exposes: {
        './ProductList': './src/components/ProductList',
        './ProductDetail': './src/components/ProductDetail',
        './ProductStore': './src/stores/productStore'
      },
      
      // 共享依赖
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
        zustand: { singleton: true, requiredVersion: '^4.0.0' }
      }
    })
  ]
};

动态加载远程模块

// host-app/src/utils/loadRemote.ts

// 动态加载远程模块
async function loadRemoteModule<T>(
  remoteName: string,
  modulePath: string
): Promise<T> {
  // 等待远程容器初始化
  await __webpack_init_sharing__('default');
  
  const container = (window as any)[remoteName];
  
  // 初始化远程容器
  await container.init(__webpack_share_scopes__.default);
  
  // 获取模块工厂
  const factory = await container.get(modulePath);
  
  // 返回模块
  return factory();
}

// 使用示例
const ProductList = React.lazy(() => 
  loadRemoteModule('productApp', './ProductList')
);

// 在组件中使用
function App() {
  return (
    <Suspense fallback={<Loading />}>
      <ProductList />
    </Suspense>
  );
}

运行时动态配置

// host-app/src/bootstrap.ts

// 运行时获取远程应用配置
interface RemoteConfig {
  name: string;
  url: string;
  scope: string;
}

async function loadRemoteConfig(): Promise<RemoteConfig[]> {
  const response = await fetch('/api/micro-apps/config');
  return response.json();
}

async function loadRemoteContainer(remote: RemoteConfig) {
  // 动态创建 script 标签
  const script = document.createElement('script');
  script.src = remote.url;
  script.type = 'text/javascript';
  script.async = true;
  
  return new Promise<void>((resolve, reject) => {
    script.onload = () => {
      resolve();
    };
    script.onerror = () => {
      reject(new Error(`Failed to load remote: ${remote.name}`));
    };
    document.head.appendChild(script);
  });
}

// 初始化所有远程应用
async function initRemotes() {
  const remotes = await loadRemoteConfig();
  
  await Promise.all(
    remotes.map(remote => loadRemoteContainer(remote))
  );
  
  console.log('All remotes loaded');
}

qiankun 方案

为什么选择 qiankun

qiankun 是蚂蚁金服开源的微前端框架,基于 single-spa 封装,提供了开箱即用的功能:

  • JS 沙箱隔离
  • CSS 样式隔离
  • 预加载
  • 全局状态管理

主应用配置

// main-app/src/main.ts
import { registerMicroApps, start, initGlobalState } from 'qiankun';

// 注册子应用
registerMicroApps([
  {
    name: 'product-app',
    entry: '//localhost:3001',
    container: '#subapp-container',
    activeRule: '/products',
    props: {
      // 传递给子应用的数据
      globalStore: store,
      utils: { request, message }
    }
  },
  {
    name: 'user-app',
    entry: '//localhost:3002',
    container: '#subapp-container',
    activeRule: '/user',
    loader: (loading) => {
      // 加载状态回调
      console.log('Loading:', loading);
    }
  },
  {
    name: 'order-app',
    entry: '//localhost:3003',
    container: '#subapp-container',
    activeRule: '/orders'
  }
], {
  // 生命周期钩子
  beforeLoad: [
    async (app) => {
      console.log('Before load:', app.name);
    }
  ],
  beforeMount: [
    async (app) => {
      console.log('Before mount:', app.name);
    }
  ],
  afterUnmount: [
    async (app) => {
      console.log('After unmount:', app.name);
    }
  ]
});

// 全局状态管理
const initialState = {
  user: null,
  token: null,
  theme: 'light'
};

const actions = initGlobalState(initialState);

// 监听全局状态变化
actions.onGlobalStateChange((state, prevState) => {
  console.log('Global state changed:', state, prevState);
});

// 修改全局状态
actions.setGlobalState({ theme: 'dark' });

// 启动 qiankun
start({
  prefetch: 'all',           // 预加载策略
  sandbox: {
    strictStyleIsolation: true,  // 严格样式隔离(Shadow DOM)
    experimentalStyleIsolation: true  // 实验性样式隔离(作用域 CSS)
  }
});

子应用改造

// product-app/src/main.ts (Vue 3 子应用)
import { createApp, App as VueApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './routes';

let app: VueApp | null = null;
let router = null;

// 渲染函数
function render(props: any = {}) {
  const { container } = props;
  
  router = createRouter({
    history: createWebHistory(
      (window as any).__POWERED_BY_QIANKUN__ ? '/products' : '/'
    ),
    routes
  });
  
  app = createApp(App);
  app.use(router);
  
  // 挂载到指定容器或 #app
  const mountEl = container 
    ? container.querySelector('#app') 
    : document.getElementById('app');
  
  app.mount(mountEl);
}

// 独立运行时直接渲染
if (!(window as any).__POWERED_BY_QIANKUN__) {
  render();
}

// qiankun 生命周期钩子
export async function bootstrap() {
  console.log('Product app bootstrapped');
}

export async function mount(props: any) {
  console.log('Product app mounting with props:', props);
  
  // 接收主应用传递的数据
  if (props.globalStore) {
    // 使用全局 store
  }
  
  // 监听全局状态
  props.onGlobalStateChange?.((state: any) => {
    console.log('Global state in product app:', state);
  });
  
  render(props);
}

export async function unmount() {
  console.log('Product app unmounting');
  
  if (app) {
    app.unmount();
    app = null;
  }
  
  if (router) {
    router = null;
  }
}

Vite 子应用配置

// product-app/vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';

export default defineConfig({
  plugins: [
    vue(),
    qiankun('product-app', {
      useDevMode: true
    })
  ],
  
  server: {
    port: 3001,
    cors: true,
    origin: 'http://localhost:3001'
  },
  
  build: {
    target: 'esnext',
    minify: 'terser'
  }
});

应用间通信

全局状态通信

// shared/globalState.ts

interface GlobalState {
  user: User | null;
  token: string | null;
  permissions: string[];
  theme: 'light' | 'dark';
}

// 使用发布订阅模式
class GlobalStateManager {
  private state: GlobalState;
  private listeners: Set<(state: GlobalState) => void>;
  
  constructor(initialState: GlobalState) {
    this.state = initialState;
    this.listeners = new Set();
  }
  
  getState(): GlobalState {
    return { ...this.state };
  }
  
  setState(partial: Partial<GlobalState>) {
    this.state = { ...this.state, ...partial };
    this.notify();
  }
  
  subscribe(listener: (state: GlobalState) => void) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
  
  private notify() {
    this.listeners.forEach(listener => listener(this.getState()));
  }
}

// 单例
export const globalState = new GlobalStateManager({
  user: null,
  token: null,
  permissions: [],
  theme: 'light'
});

事件总线通信

// shared/eventBus.ts

type EventCallback = (...args: any[]) => void;

class EventBus {
  private events: Map<string, Set<EventCallback>>;
  
  constructor() {
    this.events = new Map();
  }
  
  on(event: string, callback: EventCallback) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event)!.add(callback);
    
    // 返回取消订阅函数
    return () => this.off(event, callback);
  }
  
  off(event: string, callback: EventCallback) {
    this.events.get(event)?.delete(callback);
  }
  
  emit(event: string, ...args: any[]) {
    this.events.get(event)?.forEach(callback => {
      try {
        callback(...args);
      } catch (error) {
        console.error(`Error in event handler for ${event}:`, error);
      }
    });
  }
  
  once(event: string, callback: EventCallback) {
    const wrappedCallback = (...args: any[]) => {
      callback(...args);
      this.off(event, wrappedCallback);
    };
    this.on(event, wrappedCallback);
  }
}

// 全局事件总线
export const eventBus = new EventBus();

// 使用示例
// 子应用 A 发送事件
eventBus.emit('user:login', { userId: 123, name: 'John' });

// 子应用 B 监听事件
eventBus.on('user:login', (user) => {
  console.log('User logged in:', user);
});

基于 Props 的通信

// 主应用传递共享上下文
registerMicroApps([
  {
    name: 'sub-app',
    entry: '//localhost:3001',
    container: '#container',
    activeRule: '/sub',
    props: {
      // 共享服务
      services: {
        http: axiosInstance,
        auth: authService,
        notification: notificationService
      },
      
      // 共享状态
      getGlobalState: () => globalState.getState(),
      setGlobalState: (state) => globalState.setState(state),
      
      // 事件通信
      eventBus,
      
      // 导航方法
      navigateTo: (path: string) => {
        router.push(path);
      }
    }
  }
]);

// 子应用使用
export async function mount(props) {
  const { services, eventBus, navigateTo } = props;
  
  // 使用共享的 HTTP 服务
  const data = await services.http.get('/api/data');
  
  // 发送事件
  eventBus.emit('data:loaded', data);
  
  // 跨应用导航
  navigateTo('/other-app/page');
}

样式隔离方案

Shadow DOM 隔离

// qiankun 配置严格样式隔离
start({
  sandbox: {
    strictStyleIsolation: true // 使用 Shadow DOM
  }
});

// 注意:Shadow DOM 会导致一些问题
// 1. 弹窗组件可能无法正常工作
// 2. 全局样式无法穿透
// 3. 第三方组件库可能不兼容

CSS Scoped 隔离

// 实验性样式隔离(推荐)
start({
  sandbox: {
    experimentalStyleIsolation: true
  }
});

// 原理:为子应用的所有样式添加特殊选择器前缀
// .container { color: red; }
// 变为:
// div[data-qiankun="sub-app"] .container { color: red; }

命名空间约定

// 子应用样式使用命名空间
// product-app/styles/main.scss

$namespace: 'product-app';

.#{$namespace} {
  // 所有样式都在命名空间下
  
  &-container {
    padding: 20px;
  }
  
  &-header {
    font-size: 24px;
  }
  
  // 组件样式
  .button {
    // 自动被命名空间限定
  }
}

// 或使用 CSS Modules
// ProductList.module.css
.container { ... }
.item { ... }

路由管理

主应用路由配置

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

const routes = [
  {
    path: '/',
    component: MainLayout,
    children: [
      {
        path: '',
        component: HomePage
      },
      // 子应用路由占位
      {
        path: '/products/:pathMatch(.*)*',
        component: MicroAppContainer,
        meta: { microApp: 'product-app' }
      },
      {
        path: '/user/:pathMatch(.*)*',
        component: MicroAppContainer,
        meta: { microApp: 'user-app' }
      },
      {
        path: '/orders/:pathMatch(.*)*',
        component: MicroAppContainer,
        meta: { microApp: 'order-app' }
      }
    ]
  },
  {
    path: '/login',
    component: LoginPage
  }
];

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

// 路由守卫
router.beforeEach((to, from, next) => {
  if (to.meta.microApp) {
    // 微应用路由处理
    console.log('Navigating to micro app:', to.meta.microApp);
  }
  next();
});

export default router;

子应用路由配置

// product-app/src/router/index.ts

// 判断是否在 qiankun 环境中
const isQiankun = (window as any).__POWERED_BY_QIANKUN__;

// 动态设置基础路径
const baseUrl = isQiankun ? '/products' : '/';

const routes = [
  {
    path: '/',
    component: ProductLayout,
    children: [
      { path: '', component: ProductList },
      { path: 'detail/:id', component: ProductDetail },
      { path: 'category/:category', component: ProductCategory }
    ]
  }
];

export function createAppRouter() {
  return createRouter({
    history: createWebHistory(baseUrl),
    routes
  });
}

共享依赖管理

依赖外部化

// 使用 externals 共享依赖
// webpack.config.js
module.exports = {
  externals: {
    vue: 'Vue',
    'vue-router': 'VueRouter',
    pinia: 'Pinia'
  }
};

// 在 HTML 中加载共享依赖
// index.html
<script src="https://cdn.example.com/vue@3.3.0/dist/vue.global.prod.js"></script>
<script src="https://cdn.example.com/vue-router@4.2.0/dist/vue-router.global.prod.js"></script>

共享依赖版本管理

// shared-deps/package.json
{
  "name": "@myorg/shared-deps",
  "version": "1.0.0",
  "dependencies": {
    "vue": "^3.3.0",
    "vue-router": "^4.2.0",
    "pinia": "^2.1.0",
    "axios": "^1.4.0"
  },
  "peerDependencies": {
    "vue": "^3.3.0"
  }
}

// 子应用使用共享依赖包
// product-app/package.json
{
  "dependencies": {
    "@myorg/shared-deps": "^1.0.0"
  }
}

部署策略

独立部署架构

微前端独立部署架构:

                    ┌─────────────────┐
                    │    CDN / 负载    │
                    │     均衡器       │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ↓                    ↓                    ↓
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│   主应用      │   │   产品子应用   │   │   用户子应用   │
│  端口: 80     │   │  端口: 3001   │   │  端口: 3002   │
│  路径: /      │   │  路径: /prod  │   │  路径: /user  │
└───────────────┘   └───────────────┘   └───────────────┘
        │                    │                    │
        └────────────────────┼────────────────────┘
                             │
                    ┌────────┴────────┐
                    │   容器编排      │
                    │   (K8s/Docker)  │
                    └─────────────────┘

Nginx 配置

# nginx.conf
http {
    upstream main-app {
        server localhost:3000;
    }
    
    upstream product-app {
        server localhost:3001;
    }
    
    upstream user-app {
        server localhost:3002;
    }
    
    server {
        listen 80;
        server_name example.com;
        
        # 主应用
        location / {
            proxy_pass http://main-app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
        
        # 产品子应用静态资源
        location /product-app/ {
            alias /var/www/product-app/;
            try_files $uri $uri/ /product-app/index.html;
            
            # 允许跨域
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
            add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }
        
        # 用户子应用静态资源
        location /user-app/ {
            alias /var/www/user-app/;
            try_files $uri $uri/ /user-app/index.html;
            add_header Access-Control-Allow-Origin *;
        }
        
        # 静态资源缓存
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

Docker Compose 部署

# docker-compose.yml
version: '3.8'

services:
  main-app:
    build: ./packages/main-app
    ports:
      - "3000:80"
    environment:
      - NODE_ENV=production
    depends_on:
      - product-app
      - user-app
    networks:
      - micro-frontend
  
  product-app:
    build: ./packages/product-app
    ports:
      - "3001:80"
    environment:
      - NODE_ENV=production
    networks:
      - micro-frontend
  
  user-app:
    build: ./packages/user-app
    ports:
      - "3002:80"
    environment:
      - NODE_ENV=production
    networks:
      - micro-frontend
  
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - main-app
      - product-app
      - user-app
    networks:
      - micro-frontend

networks:
  micro-frontend:
    driver: bridge

性能优化

预加载策略

// 空闲时预加载子应用
import { prefetchApps } from 'qiankun';

// 在主应用加载完成后预加载
window.addEventListener('load', () => {
  // 使用 requestIdleCallback 在空闲时执行
  requestIdleCallback(() => {
    prefetchApps([
      { name: 'product-app', entry: '//localhost:3001' },
      { name: 'user-app', entry: '//localhost:3002' }
    ]);
  });
});

// 或者根据用户行为预加载
document.querySelector('.products-link')?.addEventListener('mouseenter', () => {
  prefetchApps([{ name: 'product-app', entry: '//localhost:3001' }]);
});

缓存优化

// 子应用资源缓存
const cache = new Map();

async function loadApp(name: string, entry: string) {
  // 检查缓存
  if (cache.has(name)) {
    return cache.get(name);
  }
  
  // 加载并缓存
  const app = await fetchAppResources(entry);
  cache.set(name, app);
  
  return app;
}

// 使用 Service Worker 缓存
// sw.js
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  
  // 缓存子应用静态资源
  if (url.pathname.includes('/micro-apps/')) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request).then((response) => {
          const clone = response.clone();
          caches.open('micro-apps-v1').then((cache) => {
            cache.put(event.request, clone);
          });
          return response;
        });
      })
    );
  }
});

最佳实践总结

微前端架构最佳实践:

架构设计:
✓ 明确边界,按业务域划分子应用
✓ 保持子应用的独立性和自治性
✓ 统一技术规范和通信协议
✓ 建立共享组件库和工具包

技术选型:
✓ 新项目考虑 Module Federation
✓ 遗留系统集成使用 qiankun
✓ 统一依赖版本管理
✓ 做好技术栈兼容性评估

通信设计:
✓ 最小化应用间通信
✓ 使用事件总线或全局状态
✓ 定义清晰的通信接口
✓ 避免直接 DOM 操作跨应用

样式管理:
✓ 使用 CSS Modules 或命名空间
✓ 评估 Shadow DOM 的适用性
✓ 统一设计系统和样式变量
✓ 避免全局样式污染

性能优化:
✓ 实施预加载策略
✓ 共享公共依赖
✓ 使用缓存减少重复加载
✓ 监控各子应用性能指标

部署运维:
✓ 实现独立构建和部署
✓ 配置 CORS 和资源跨域
✓ 实施灰度发布策略
✓ 建立监控和告警机制

微前端不是银弹,它适合特定的场景和团队规模。在采用之前,要充分评估其带来的复杂性是否值得,以及团队是否具备相应的技术能力来驾驭这种架构。