HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • Vue & CSS 进阶

    • 第0章:工具链与开发体验
    • 第1章:TypeScript 核心
    • 第2章:CSS 现代能力
    • 第3章:Vue3 核心与原理
    • 第4章:路由与状态管理
    • 第5章:工程化与性能
    • 第6章:测试与质量
    • 第7章:CSS 进阶专题
    • 第8章:项目实战A - SaaS仪表盘
    • 第9章:项目实战B - 内容社区
    • 第10章:Vue 内核深入
    • 第11章:微前端与部署
    • CSS 变量体系与主题系统深度指南
    • 前端安全与防护专题
    • CSS 专题:动效优化与微交互
    • CSS 专题:图片与图形处理
    • 实时通信与WebSocket专题
    • 开发工具与调试技巧专题
    • 新兴技术与未来趋势专题
    • 移动端开发与PWA专题
    • 附录A:代码片段库
    • 附录B:练习题集

第11章:微前端与部署

掌握微前端架构设计,实现 CI/CD 自动化部署,建立完整的监控体系

📚 本章目标

通过本章学习,你将掌握:

  • Module Federation 微前端架构
  • qiankun/wujie 微前端方案
  • CI/CD 自动化部署流程
  • 性能监控与错误追踪
  • 生产环境优化策略

🏗️ 11.1 微前端架构设计

11.1.1 微前端核心概念

微前端是一种将前端应用拆分为多个独立、可部署的小型应用的架构模式。

// 微前端架构图
interface MicroFrontendArchitecture {
  // 主应用 (Shell App)
  shellApp: {
    name: 'shell'
    responsibilities: ['路由管理', '应用注册', '共享依赖']
  }
  
  // 子应用 (Micro Apps)
  microApps: Array<{
    name: string
    entry: string
    container: string
    activeRule: string
    props?: Record<string, any>
  }>
  
  // 共享依赖
  sharedDependencies: string[]
  
  // 通信机制
  communication: {
    props: '父子通信'
    customEvents: '自定义事件'
    globalState: '全局状态'
    postMessage: '跨域通信'
  }
}

11.1.2 技术选型对比

方案优点缺点适用场景
Module Federation原生支持、性能好学习成本高新项目、技术栈统一
qiankun成熟稳定、生态好侵入性强老项目改造、多技术栈
wujie无侵入、性能好相对较新对性能要求高的项目
single-spa灵活度高配置复杂复杂业务场景

⚡ 11.2 Module Federation 实战

11.2.1 主应用配置

// webpack.config.js (主应用)
const ModuleFederationPlugin = require('@module-federation/webpack')

module.exports = {
  mode: 'development',
  devServer: {
    port: 3000,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        'userApp': 'userApp@http://localhost:3001/remoteEntry.js',
        'productApp': 'productApp@http://localhost:3002/remoteEntry.js',
        'orderApp': 'orderApp@http://localhost:3003/remoteEntry.js'
      },
      shared: {
        vue: {
          singleton: true,
          requiredVersion: '^3.0.0'
        },
        'vue-router': {
          singleton: true,
          requiredVersion: '^4.0.0'
        },
        pinia: {
          singleton: true,
          requiredVersion: '^2.0.0'
        }
      }
    })
  ]
}

11.2.2 子应用配置

// webpack.config.js (用户管理子应用)
const ModuleFederationPlugin = require('@module-federation/webpack')

module.exports = {
  mode: 'development',
  devServer: {
    port: 3001,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'userApp',
      filename: 'remoteEntry.js',
      exposes: {
        './UserList': './src/components/UserList.vue',
        './UserForm': './src/components/UserForm.vue',
        './UserStore': './src/stores/userStore.ts'
      },
      shared: {
        vue: {
          singleton: true,
          requiredVersion: '^3.0.0'
        },
        'vue-router': {
          singleton: true,
          requiredVersion: '^4.0.0'
        },
        pinia: {
          singleton: true,
          requiredVersion: '^2.0.0'
        }
      }
    })
  ]
}

11.2.3 动态加载子应用

<!-- 主应用 - 动态加载子应用 -->
<template>
  <div class="shell-app">
    <nav class="main-nav">
      <router-link to="/users">用户管理</router-link>
      <router-link to="/products">商品管理</router-link>
      <router-link to="/orders">订单管理</router-link>
    </nav>
    
    <main class="main-content">
      <router-view />
    </main>
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

// 动态导入子应用组件
const UserList = defineAsyncComponent(() => import('userApp/UserList'))
const UserForm = defineAsyncComponent(() => import('userApp/UserForm'))
const ProductList = defineAsyncComponent(() => import('productApp/ProductList'))
const OrderList = defineAsyncComponent(() => import('orderApp/OrderList'))

// 路由配置
const routes = [
  {
    path: '/users',
    component: UserList
  },
  {
    path: '/users/new',
    component: UserForm
  },
  {
    path: '/products',
    component: ProductList
  },
  {
    path: '/orders',
    component: OrderList
  }
]
</script>

11.2.4 子应用间通信

// 共享状态管理
// shared/stores/globalStore.ts
import { defineStore } from 'pinia'

export const useGlobalStore = defineStore('global', {
  state: () => ({
    user: null as User | null,
    theme: 'light' as 'light' | 'dark',
    notifications: [] as Notification[]
  }),
  
  actions: {
    setUser(user: User) {
      this.user = user
      // 通知其他应用用户信息变更
      window.dispatchEvent(new CustomEvent('user-changed', { detail: user }))
    },
    
    setTheme(theme: 'light' | 'dark') {
      this.theme = theme
      document.documentElement.setAttribute('data-theme', theme)
    },
    
    addNotification(notification: Notification) {
      this.notifications.push(notification)
    }
  }
})

// 事件总线
class EventBus {
  private events: Map<string, Function[]> = new Map()
  
  on(event: string, callback: Function) {
    if (!this.events.has(event)) {
      this.events.set(event, [])
    }
    this.events.get(event)!.push(callback)
  }
  
  off(event: string, callback: Function) {
    const callbacks = this.events.get(event)
    if (callbacks) {
      const index = callbacks.indexOf(callback)
      if (index > -1) {
        callbacks.splice(index, 1)
      }
    }
  }
  
  emit(event: string, data?: any) {
    const callbacks = this.events.get(event)
    if (callbacks) {
      callbacks.forEach(callback => callback(data))
    }
  }
}

export const eventBus = new EventBus()

🚀 11.3 qiankun 微前端方案

11.3.1 主应用配置

// main.ts (主应用)
import { registerMicroApps, start } from 'qiankun'

// 注册微应用
registerMicroApps([
  {
    name: 'userApp',
    entry: '//localhost:3001',
    container: '#user-container',
    activeRule: '/users',
    props: {
      data: { theme: 'light' }
    }
  },
  {
    name: 'productApp',
    entry: '//localhost:3002',
    container: '#product-container',
    activeRule: '/products'
  },
  {
    name: 'orderApp',
    entry: '//localhost:3003',
    container: '#order-container',
    activeRule: '/orders'
  }
])

// 启动微前端
start({
  sandbox: {
    strictStyleIsolation: true,
    experimentalStyleIsolation: true
  },
  prefetch: 'all',
  singular: false
})

11.3.2 子应用配置

// main.ts (子应用)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'

let app: any = null

function render(props: any = {}) {
  const { container } = props
  app = createApp(App)
  
  app.use(router)
  app.use(createPinia())
  
  app.mount(container ? container.querySelector('#app') : '#app')
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

// 微前端生命周期
export async function bootstrap() {
  console.log('子应用启动')
}

export async function mount(props: any) {
  console.log('子应用挂载', props)
  render(props)
}

export async function unmount() {
  console.log('子应用卸载')
  if (app) {
    app.unmount()
    app = null
  }
}

11.3.3 样式隔离

/* 主应用样式 */
.shell-app {
  font-family: 'Arial', sans-serif;
}

/* 子应用样式隔离 */
#user-container {
  /* 使用 CSS 变量实现主题共享 */
  --primary-color: #1890ff;
  --text-color: #333;
}

/* 全局样式重置 */
#user-container * {
  box-sizing: border-box;
}

🔄 11.4 CI/CD 自动化部署

11.4.1 GitHub Actions 配置

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'pnpm'
      
      - name: Install dependencies
        run: pnpm install
      
      - name: Run tests
        run: pnpm test:coverage
      
      - name: Run E2E tests
        run: pnpm test:e2e
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'pnpm'
      
      - name: Install dependencies
        run: pnpm install
      
      - name: Build application
        run: pnpm build
        env:
          NODE_ENV: production
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-files
          path: dist/
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          working-directory: ./

11.4.2 Docker 配置

# Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json pnpm-lock.yaml ./

# 安装依赖
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# 复制源代码
COPY . .

# 构建应用
RUN pnpm build

# 生产环境
FROM nginx:alpine

# 复制构建文件
COPY --from=builder /app/dist /usr/share/nginx/html

# 复制 nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf

# 暴露端口
EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
# nginx.conf
events {
  worker_connections 1024;
}

http {
  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  # Gzip 压缩
  gzip on;
  gzip_vary on;
  gzip_min_length 1024;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  # 缓存配置
  location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  # SPA 路由支持
  location / {
    try_files $uri $uri/ /index.html;
  }

  # API 代理
  location /api/ {
    proxy_pass http://backend:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

11.4.3 环境配置

// config/environments.ts
interface Environment {
  apiBaseUrl: string
  cdnUrl: string
  sentryDsn: string
  analyticsId: string
}

const environments: Record<string, Environment> = {
  development: {
    apiBaseUrl: 'http://localhost:3000/api',
    cdnUrl: 'http://localhost:3000',
    sentryDsn: '',
    analyticsId: ''
  },
  staging: {
    apiBaseUrl: 'https://api-staging.example.com',
    cdnUrl: 'https://cdn-staging.example.com',
    sentryDsn: 'https://staging-sentry.example.com',
    analyticsId: 'GA-STAGING-ID'
  },
  production: {
    apiBaseUrl: 'https://api.example.com',
    cdnUrl: 'https://cdn.example.com',
    sentryDsn: 'https://sentry.example.com',
    analyticsId: 'GA-PRODUCTION-ID'
  }
}

export const getEnvironment = (): Environment => {
  const env = process.env.NODE_ENV || 'development'
  return environments[env]
}

📊 11.5 性能监控与错误追踪

11.5.1 Sentry 错误监控

// plugins/sentry.client.ts
import * as Sentry from '@sentry/vue'
import { BrowserTracing } from '@sentry/tracing'

export default defineNuxtPlugin(() => {
  Sentry.init({
    app: nuxtApp.vueApp,
    dsn: 'YOUR_SENTRY_DSN',
    integrations: [
      new BrowserTracing({
        routingInstrumentation: Sentry.vueRouterInstrumentation(router)
      })
    ],
    tracesSampleRate: 1.0,
    environment: process.env.NODE_ENV
  })
})

11.5.2 Web Vitals 监控

// composables/useWebVitals.ts
export const useWebVitals = () => {
  const reportVital = (name: string, value: number, id: string) => {
    // 发送到分析服务
    if (typeof window !== 'undefined' && 'gtag' in window) {
      gtag('event', name, {
        event_category: 'Web Vitals',
        value: Math.round(name === 'CLS' ? value * 1000 : value),
        event_label: id,
        non_interaction: true
      })
    }
  }

  const measureWebVitals = () => {
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(reportVital)
      getFID(reportVital)
      getFCP(reportVital)
      getLCP(reportVital)
      getTTFB(reportVital)
    })
  }

  return { measureWebVitals }
}

11.5.3 自定义性能监控

// utils/performance.ts
class PerformanceMonitor {
  private metrics: Map<string, number> = new Map()
  
  // 开始计时
  startTiming(name: string) {
    this.metrics.set(name, performance.now())
  }
  
  // 结束计时
  endTiming(name: string) {
    const startTime = this.metrics.get(name)
    if (startTime) {
      const duration = performance.now() - startTime
      this.reportMetric(name, duration)
      this.metrics.delete(name)
    }
  }
  
  // 报告指标
  private reportMetric(name: string, value: number) {
    // 发送到监控服务
    fetch('/api/metrics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name,
        value,
        timestamp: Date.now(),
        url: window.location.href
      })
    }).catch(console.error)
  }
  
  // 监控资源加载
  monitorResourceLoading() {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        if (entry.entryType === 'resource') {
          this.reportMetric(`resource.${entry.name}`, entry.duration)
        }
      })
    })
    
    observer.observe({ entryTypes: ['resource'] })
  }
}

export const performanceMonitor = new PerformanceMonitor()

🎯 11.6 生产环境优化

11.6.1 代码分割优化

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 第三方库
          vendor: ['vue', 'vue-router', 'pinia'],
          // UI 组件库
          ui: ['element-plus', '@element-plus/icons-vue'],
          // 工具库
          utils: ['lodash-es', 'dayjs', 'axios'],
          // 图表库
          charts: ['echarts', 'chart.js']
        }
      }
    }
  }
})

11.6.2 缓存策略

// utils/cache.ts
class CacheManager {
  private cache = new Map<string, { data: any; expiry: number }>()
  
  set(key: string, data: any, ttl: number = 300000) { // 5分钟默认
    this.cache.set(key, {
      data,
      expiry: Date.now() + ttl
    })
  }
  
  get(key: string) {
    const item = this.cache.get(key)
    if (!item) return null
    
    if (Date.now() > item.expiry) {
      this.cache.delete(key)
      return null
    }
    
    return item.data
  }
  
  clear() {
    this.cache.clear()
  }
}

export const cacheManager = new CacheManager()

11.6.3 预加载策略

// utils/preload.ts
export const preloadResources = () => {
  // 预加载关键资源
  const criticalResources = [
    '/fonts/main.woff2',
    '/images/hero.jpg',
    '/api/user/profile'
  ]
  
  criticalResources.forEach(resource => {
    if (resource.endsWith('.woff2')) {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.href = resource
      link.as = 'font'
      link.type = 'font/woff2'
      link.crossOrigin = 'anonymous'
      document.head.appendChild(link)
    } else if (resource.startsWith('/api/')) {
      fetch(resource, { method: 'HEAD' })
    } else {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.href = resource
      link.as = 'image'
      document.head.appendChild(link)
    }
  })
}

🎯 本章总结

通过本章学习,你掌握了:

  1. 微前端架构:Module Federation、qiankun 方案对比
  2. Module Federation:主应用配置、子应用开发、通信机制
  3. qiankun 方案:应用注册、生命周期、样式隔离
  4. CI/CD 流程:GitHub Actions、Docker 部署、环境配置
  5. 性能监控:Sentry 集成、Web Vitals、自定义监控
  6. 生产优化:代码分割、缓存策略、预加载

📝 练习题

  1. 使用 Module Federation 搭建一个微前端应用
  2. 实现子应用间的状态共享和通信机制
  3. 配置完整的 CI/CD 部署流程
  4. 集成错误监控和性能监控系统
  5. 优化生产环境的加载性能

🔗 相关资源

  • Module Federation 文档
  • qiankun 官方文档
  • GitHub Actions 文档
  • Sentry 文档
  • Web Vitals
Prev
第10章:Vue 内核深入
Next
CSS 变量体系与主题系统深度指南