Vue 3 微前端架构

概述

微前端(Micro Frontends)是一种将大型前端应用拆分为多个独立、可部署的小型应用的架构模式。每个微应用可以使用不同的技术栈,独立开发、测试和部署,同时能够无缝集成到同一个页面中。Vue 3提供了良好的微前端支持,本集将深入探讨Vue 3微前端架构,包括主流框架(Single-SPA和qiankun)的使用方法、微前端通信、状态管理、路由管理以及最佳实践,帮助你构建可扩展、易维护的大型Vue 3应用。

核心知识点

1. 微前端架构模式

核心原则

  • 独立开发与部署:每个微应用独立开发、测试和部署
  • 技术栈无关:支持不同技术栈(Vue、React、Angular等)
  • 单一职责:每个微应用负责特定业务功能
  • 共享与隔离:共享基础资源,同时保持运行时隔离
  • 渐进式迁移:支持从单体应用逐步迁移到微前端架构

常见架构模式

  1. 基座模式(Host-remote)

    • 一个主应用(基座)负责加载和管理多个子应用
    • 子应用独立开发和部署
    • 适合大型企业应用
  2. 路由分发模式

    • 通过路由配置将不同URL分发到不同微应用
    • 适合单页应用
    • 代表框架:Single-SPA
  3. 组合式应用模式

    • 将应用拆分为多个独立组件,运行时组合
    • 适合组件化程度高的应用

2. Single-SPA 框架

Single-SPA是第一个微前端框架,通过路由配置将不同微应用集成到同一个页面中。

安装与配置

# 安装Single-SPA CLI
npm install -g create-single-spa

# 创建主应用
create-single-spa
# 选择 "single-spa root config"

# 创建Vue 3微应用
create-single-spa
# 选择 "single-spa application / parcel"
# 选择 "vue"

主应用配置

创建src/root-config.ts

import { registerApplication, start } from 'single-spa'
import { constructApplications, constructRoutes, constructLayoutEngine } from 'single-spa-layout'
import microfrontendLayout from './microfrontend-layout.html'

const routes = constructRoutes(microfrontendLayout)
const applications = constructApplications({
  routes,
  loadApp({ name }) {
    return import(`../apps/${name}/src/${name}.ts`)
  }
})
const layoutEngine = constructLayoutEngine({
  routes,
  applications
})

applications.forEach(registerApplication)
layoutEngine.activate()
start()

创建src/microfrontend-layout.html

<single-spa-router>
  <nav>
    <application name="@org/navbar"></application>
  </nav>
  <div class="main-content">
    <route path="/">
      <application name="@org/home"></application>
    </route>
    <route path="/about">
      <application name="@org/about"></application>
    </route>
    <route path="/dashboard">
      <application name="@org/dashboard"></application>
    </route>
  </div>
</single-spa-router>

Vue 3微应用配置

创建src/main.ts

import { h, createApp } from 'vue'
import singleSpaVue from 'single-spa-vue'
import App from './App.vue'
import router from './router'
import store from './store'

const appOptions = {
  el: '#vue-app',
  render() {
    return h(App, {
      props: {
        // single-spa props
        name: this.name,
        mountParcel: this.mountParcel,
        singleSpa: this.singleSpa
      }
    })
  },
  router,
  store
}

const vueLifecycles = singleSpaVue({
  createApp,
  appOptions,
  handleInstance(app) {
    app.use(router)
    app.use(store)
  }
})

export const bootstrap = vueLifecycles.bootstrap
export const mount = vueLifecycles.mount
export const unmount = vueLifecycles.unmount

3. qiankun 框架

qiankun是蚂蚁金服开源的微前端框架,基于Single-SPA,提供了更完善的微前端解决方案。

安装与配置

# 安装qiankun
npm install qiankun

主应用配置

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { registerMicroApps, start } from 'qiankun'

const app = createApp(App)
app.use(router)
app.mount('#app')

// 注册微应用
registerMicroApps([
  {
    name: 'vue-app',
    entry: '//localhost:8081',
    container: '#micro-app-container',
    activeRule: '/vue-app'
  },
  {
    name: 'react-app',
    entry: '//localhost:8082',
    container: '#micro-app-container',
    activeRule: '/react-app'
  }
])

// 启动qiankun
start({
  sandbox: {
    strictStyleIsolation: true // 严格样式隔离
  }
})

主应用模板:

<template>
  <div>
    <h1>主应用</h1>
    <router-link to="/vue-app">Vue 微应用</router-link>
    <router-link to="/react-app">React 微应用</router-link>
    <div id="micro-app-container"></div>
  </div>
</template>

Vue 3微应用配置

创建public-path.ts

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

修改main.ts

import './public-path'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

let instance: any = null

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

// 独立运行时
export async function bootstrap() {
  console.log('Vue 3 app bootstraped')
}

export async function mount(props: any) {
  console.log('Vue 3 app mounted', props)
  render(props)
}

export async function unmount() {
  console.log('Vue 3 app unmounted')
  instance?.unmount()
  instance = null
}

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

修改vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    port: 8081,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  build: {
    lib: {
      entry: resolve(__dirname, 'src/main.ts'),
      name: 'vue-app'
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        format: 'umd',
        name: 'vue-app',
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

4. 微前端通信

基于Props的通信

// 主应用
registerMicroApps([
  {
    name: 'vue-app',
    entry: '//localhost:8081',
    container: '#micro-app-container',
    activeRule: '/vue-app',
    props: {
      message: 'Hello from main app',
      onMessage: (data: any) => {
        console.log('Message from micro app:', data)
      }
    }
  }
])

// 微应用
function render(props: any = {}) {
  const { message, onMessage } = props
  console.log('Message from main app:', message)
  
  // 向主应用发送消息
  onMessage?.('Hello from micro app')
  
  const { container } = props
  instance = createApp(App)
  instance.use(router)
  instance.mount(container ? container.querySelector('#app') : '#app')
}

基于Event Bus的通信

// 主应用和微应用共享Event Bus
class EventBus {
  private events: Record<string, Function[]> = {}
  
  on(event: string, callback: Function) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
  }
  
  emit(event: string, data: any) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data))
    }
  }
  
  off(event: string, callback: Function) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback)
    }
  }
}

// 主应用中
window.eventBus = new EventBus()

// 主应用监听事件
window.eventBus.on('micro-app-message', (data) => {
  console.log('Message from micro app:', data)
})

// 主应用发送事件
window.eventBus.emit('main-app-message', 'Hello from main app')

// 微应用中
// 监听事件
window.eventBus.on('main-app-message', (data) => {
  console.log('Message from main app:', data)
})

// 发送事件
window.eventBus.emit('micro-app-message', 'Hello from micro app')

基于状态管理的通信

使用Pinia或Vuex实现跨微应用状态管理:

// shared-store.ts
import { defineStore } from 'pinia'

export const useSharedStore = defineStore('shared', {
  state: () => ({
    count: 0,
    message: ''
  }),
  actions: {
    increment() {
      this.count++
    },
    setMessage(message: string) {
      this.message = message
    }
  }
})

在主应用和微应用中共享该store:

// 主应用
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)

// 微应用
import { createPinia } from 'pinia'
const pinia = window.__POWERED_BY_QIANKUN__ ? window.pinia : createPinia()
app.use(pinia)

5. 微前端路由管理

路由隔离

// Vue 3微应用路由配置
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/HomeView.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/AboutView.vue')
  }
]

// 微应用基础路径
const baseUrl = window.__POWERED_BY_QIANKUN__ ? '/vue-app' : '/'

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

export default router

路由同步

使用single-spa-vue-router或自定义实现路由同步:

// 主应用路由配置
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/vue-app/*',
      name: 'VueApp',
      component: () => import('../views/VueAppView.vue')
    },
    {
      path: '/react-app/*',
      name: 'ReactApp',
      component: () => import('../views/ReactAppView.vue')
    }
  ]
})

6. 样式隔离

CSS Modules

使用CSS Modules实现样式隔离:

<template>
  <div :class="styles.container">
    <h1 :class="styles.title">Vue 3 Micro App</h1>
  </div>
</template>

<style module>
.container {
  padding: 20px;
}

.title {
  color: #42b983;
}
</style>

Shadow DOM

使用Shadow DOM实现样式隔离:

function render(props: any = {}) {
  const { container } = props
  let mountPoint: HTMLElement
  
  if (container) {
    // 创建Shadow DOM
    const shadowRoot = container.attachShadow({ mode: 'open' })
    mountPoint = document.createElement('div')
    mountPoint.id = 'app'
    shadowRoot.appendChild(mountPoint)
  } else {
    mountPoint = document.getElementById('app') as HTMLElement
  }
  
  instance = createApp(App)
  instance.use(router)
  instance.mount(mountPoint)
}

qiankun样式隔离

qiankun内置样式隔离机制:

start({
  sandbox: {
    strictStyleIsolation: true, // 严格样式隔离
    experimentalStyleIsolation: true // 实验性样式隔离
  }
})

最佳实践

  1. 合理划分微应用

    • 按业务领域划分
    • 每个微应用职责单一
    • 避免过多微应用
  2. 统一技术栈

    • 尽量使用相同技术栈
    • 统一基础库版本
    • 统一代码规范
  3. 共享公共资源

    • 共享UI组件库
    • 共享工具函数
    • 共享状态管理
  4. 优化加载性能

    • 懒加载微应用
    • 预加载常用微应用
    • 优化微应用打包体积
  5. 完善的错误处理

    • 微应用加载失败处理
    • 微应用运行时错误捕获
    • 错误日志收集
  6. CI/CD流程

    • 自动化构建和部署
    • 统一测试环境
    • 版本管理

常见问题与解决方案

1. 微应用加载失败

问题:微应用无法正常加载

解决方案

  • 检查微应用入口地址是否正确
  • 检查CORS配置
  • 检查微应用打包配置
  • 查看浏览器控制台错误信息

2. 样式冲突

问题:微应用之间样式冲突

解决方案

  • 使用CSS Modules
  • 使用Shadow DOM
  • 使用qiankun样式隔离
  • 为微应用添加唯一前缀

3. 路由冲突

问题:微应用之间路由冲突

解决方案

  • 为每个微应用设置独立基础路径
  • 使用路由守卫处理路由冲突
  • 统一路由配置

4. 通信复杂

问题:微应用之间通信复杂

解决方案

  • 使用Event Bus
  • 使用共享状态管理
  • 基于Props的通信
  • 避免过度通信

5. 性能问题

问题:微应用加载和运行性能问题

解决方案

  • 懒加载微应用
  • 优化打包体积
  • 使用预加载
  • 优化微应用内部性能

进一步学习资源

  1. Single-SPA 官方文档
  2. qiankun 官方文档
  3. 微前端技术白皮书
  4. Vue 3 微前端实践
  5. 微前端架构设计

课后练习

  1. 练习1:Single-SPA基础使用

    • 安装Single-SPA CLI
    • 创建主应用和Vue 3微应用
    • 配置路由和应用加载
    • 测试微应用集成
  2. 练习2:qiankun框架使用

    • 安装qiankun
    • 创建主应用和Vue 3微应用
    • 配置微应用加载和生命周期
    • 测试微应用集成
  3. 练习3:微应用通信

    • 实现基于Props的通信
    • 实现基于Event Bus的通信
    • 实现基于Pinia的共享状态管理
    • 测试不同通信方式
  4. 练习4:样式隔离

    • 使用CSS Modules实现样式隔离
    • 使用Shadow DOM实现样式隔离
    • 测试样式隔离效果
  5. 练习5:路由管理

    • 配置微应用独立路由
    • 实现主应用和微应用路由同步
    • 测试路由跳转
  6. 练习6:性能优化

    • 实现微应用懒加载
    • 优化微应用打包体积
    • 测试加载性能

通过本集的学习,你应该对Vue 3微前端架构有了全面的了解。微前端架构适合构建大型、复杂的前端应用,能够提高开发效率和应用可维护性。合理使用Single-SPA或qiankun框架,结合良好的架构设计和最佳实践,可以帮助你构建高质量的Vue 3微前端应用。

« 上一篇 Vue 3无障碍设计 - 构建包容性前端应用 下一篇 » Vue 3全栈项目实战总结 - 从基础到高级的完整指南