Vue 3 微前端架构
概述
微前端(Micro Frontends)是一种将大型前端应用拆分为多个独立、可部署的小型应用的架构模式。每个微应用可以使用不同的技术栈,独立开发、测试和部署,同时能够无缝集成到同一个页面中。Vue 3提供了良好的微前端支持,本集将深入探讨Vue 3微前端架构,包括主流框架(Single-SPA和qiankun)的使用方法、微前端通信、状态管理、路由管理以及最佳实践,帮助你构建可扩展、易维护的大型Vue 3应用。
核心知识点
1. 微前端架构模式
核心原则
- 独立开发与部署:每个微应用独立开发、测试和部署
- 技术栈无关:支持不同技术栈(Vue、React、Angular等)
- 单一职责:每个微应用负责特定业务功能
- 共享与隔离:共享基础资源,同时保持运行时隔离
- 渐进式迁移:支持从单体应用逐步迁移到微前端架构
常见架构模式
基座模式(Host-remote):
- 一个主应用(基座)负责加载和管理多个子应用
- 子应用独立开发和部署
- 适合大型企业应用
路由分发模式:
- 通过路由配置将不同URL分发到不同微应用
- 适合单页应用
- 代表框架:Single-SPA
组合式应用模式:
- 将应用拆分为多个独立组件,运行时组合
- 适合组件化程度高的应用
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.unmount3. 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 // 实验性样式隔离
}
})最佳实践
合理划分微应用:
- 按业务领域划分
- 每个微应用职责单一
- 避免过多微应用
统一技术栈:
- 尽量使用相同技术栈
- 统一基础库版本
- 统一代码规范
共享公共资源:
- 共享UI组件库
- 共享工具函数
- 共享状态管理
优化加载性能:
- 懒加载微应用
- 预加载常用微应用
- 优化微应用打包体积
完善的错误处理:
- 微应用加载失败处理
- 微应用运行时错误捕获
- 错误日志收集
CI/CD流程:
- 自动化构建和部署
- 统一测试环境
- 版本管理
常见问题与解决方案
1. 微应用加载失败
问题:微应用无法正常加载
解决方案:
- 检查微应用入口地址是否正确
- 检查CORS配置
- 检查微应用打包配置
- 查看浏览器控制台错误信息
2. 样式冲突
问题:微应用之间样式冲突
解决方案:
- 使用CSS Modules
- 使用Shadow DOM
- 使用qiankun样式隔离
- 为微应用添加唯一前缀
3. 路由冲突
问题:微应用之间路由冲突
解决方案:
- 为每个微应用设置独立基础路径
- 使用路由守卫处理路由冲突
- 统一路由配置
4. 通信复杂
问题:微应用之间通信复杂
解决方案:
- 使用Event Bus
- 使用共享状态管理
- 基于Props的通信
- 避免过度通信
5. 性能问题
问题:微应用加载和运行性能问题
解决方案:
- 懒加载微应用
- 优化打包体积
- 使用预加载
- 优化微应用内部性能
进一步学习资源
课后练习
练习1:Single-SPA基础使用
- 安装Single-SPA CLI
- 创建主应用和Vue 3微应用
- 配置路由和应用加载
- 测试微应用集成
练习2:qiankun框架使用
- 安装qiankun
- 创建主应用和Vue 3微应用
- 配置微应用加载和生命周期
- 测试微应用集成
练习3:微应用通信
- 实现基于Props的通信
- 实现基于Event Bus的通信
- 实现基于Pinia的共享状态管理
- 测试不同通信方式
练习4:样式隔离
- 使用CSS Modules实现样式隔离
- 使用Shadow DOM实现样式隔离
- 测试样式隔离效果
练习5:路由管理
- 配置微应用独立路由
- 实现主应用和微应用路由同步
- 测试路由跳转
练习6:性能优化
- 实现微应用懒加载
- 优化微应用打包体积
- 测试加载性能
通过本集的学习,你应该对Vue 3微前端架构有了全面的了解。微前端架构适合构建大型、复杂的前端应用,能够提高开发效率和应用可维护性。合理使用Single-SPA或qiankun框架,结合良好的架构设计和最佳实践,可以帮助你构建高质量的Vue 3微前端应用。