第265集:微应用构建方案

概述

微前端是一种将大型前端应用拆分为多个独立的小型应用的架构模式。在 Vue 3 中,可以使用多种技术来构建微应用,如 Webpack Module Federation、Single-SPA、qiankun 等。本集将深入探讨 Vue 3 微应用的构建方案,包括核心概念、配置、实战示例和最佳实践等。

微应用的核心概念

1. 微前端的优势

  • 技术栈无关:不同的微应用可以使用不同的技术栈
  • 独立开发和部署:每个微应用可以独立开发、测试和部署
  • 增量迁移:可以逐步将旧应用迁移到新架构
  • 团队自治:不同的团队可以负责不同的微应用
  • 更好的可维护性:每个微应用的代码量更小,更容易维护

2. 微前端的核心挑战

  • 应用间通信:微应用之间需要有效的通信机制
  • 样式隔离:避免不同微应用的样式冲突
  • 状态管理:跨微应用的状态管理
  • 路由管理:微应用之间的路由同步
  • 性能优化:避免多个微应用导致的性能问题

微应用构建方案

在 Vue 3 中,可以使用以下几种方案来构建微应用:

  1. Webpack Module Federation:Webpack 5 提供的模块联邦功能,允许不同应用共享模块
  2. Single-SPA:一个用于构建微前端应用的框架
  3. qiankun:基于 Single-SPA 的微前端框架,提供了更完善的功能

Webpack Module Federation

Webpack Module Federation 是 Webpack 5 引入的一个新特性,允许不同的 Webpack 构建之间共享模块,无需将它们打包在一起。

1. 核心概念

  • Host:主应用,消费其他应用提供的模块
  • Remote:远程应用,提供模块给其他应用消费
  • Shared:共享的依赖,如 React、Vue 等

2. 配置示例

远程应用配置
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp', // 远程应用名称
      filename: 'remoteEntry.js', // 远程入口文件
      exposes: {
        './Button': './src/components/Button.vue', // 暴露的组件
        './utils': './src/utils/index.js' // 暴露的工具函数
      },
      shared: {
        vue: {
          singleton: true, // 单例模式
          requiredVersion: '^3.3.0' // 要求的 Vue 版本
        }
      }
    })
  ]
}
主应用配置
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp', // 主应用名称
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js' // 远程应用入口
      },
      shared: {
        vue: {
          singleton: true,
          requiredVersion: '^3.3.0'
        }
      }
    })
  ]
}
在主应用中使用远程组件
<!-- App.vue -->
<template>
  <div>
    <h1>主应用</h1>
    <!-- 使用远程组件 -->
    <RemoteButton />
  </div>
</template>

<script setup>
// 动态导入远程组件
const RemoteButton = defineAsyncComponent(() => import('remoteApp/Button'))
</script>

Single-SPA

Single-SPA 是一个用于构建微前端应用的框架,它允许在同一个页面中加载多个独立的应用。

1. 核心概念

  • Application:微应用,每个微应用可以是一个独立的 Vue、React 或 Angular 应用
  • Parcel:比应用更小的单元,可以是一个组件或一个功能模块
  • Router:负责管理不同微应用的路由

2. 配置示例

安装依赖
npm install single-spa single-spa-vue
微应用配置
// src/main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import singleSpaVue from 'single-spa-vue'
import App from './App.vue'
import routes from './router'

const router = createRouter({
  history: createWebHistory('/vue-app'),
  routes
})

const app = createApp(App)
app.use(router)

// 导出 single-spa 生命周期函数
const vueLifecycle = singleSpaVue({
  app,
  createApp,
  rootComponent: App,
  router,
  mountProps: {
    // 传递给微应用的属性
    message: 'Hello from host'
  }
})

export const bootstrap = vueLifecycle.bootstrap
export const mount = vueLifecycle.mount
export const unmount = vueLifecycle.unmount
主应用配置
// src/main.js
import { registerApplication, start } from 'single-spa'

// 注册微应用
registerApplication({
  name: 'vue-app', // 微应用名称
  app: () => import('http://localhost:3002/js/app.js'), // 微应用入口
  activeWhen: ['/vue-app'], // 激活条件
  customProps: {
    // 传递给微应用的自定义属性
    apiUrl: 'http://api.example.com'
  }
})

// 启动 single-spa
start()
主应用 HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Single-SPA Host</title>
</head>
<body>
  <!-- 微应用挂载点 -->
  <div id="vue-app"></div>
  <!-- 加载 single-spa -->
  <script src="https://unpkg.com/single-spa"></script>
  <!-- 加载主应用脚本 -->
  <script src="/js/main.js"></script>
</body>
</html>

qiankun

qiankun 是基于 Single-SPA 的微前端框架,提供了更完善的功能,如样式隔离、JS 沙箱、预加载等。

1. 核心特性

  • 简单易用:提供了简洁的 API,易于上手
  • 技术栈无关:支持 Vue、React、Angular 等多种技术栈
  • 样式隔离:自动处理样式隔离,避免样式冲突
  • JS 沙箱:提供了 JS 沙箱,避免全局变量污染
  • 预加载:支持预加载微应用,提高加载速度
  • 路由同步:自动处理微应用之间的路由同步

2. 配置示例

主应用配置
// main.js
import { createApp } from 'vue'
import { registerMicroApps, start } from 'qiankun'
import App from './App.vue'

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

// 注册微应用
registerMicroApps([
  {
    name: 'vue-app', // 微应用名称
    entry: '//localhost:3002', // 微应用入口
    container: '#vue-app', // 微应用挂载点
    activeRule: '/vue-app', // 激活规则
    props: {
      // 传递给微应用的属性
      apiUrl: 'http://api.example.com'
    }
  }
])

// 启动 qiankun
start()
微应用配置
// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import routes from './router'

// qiankun 生命周期函数
let app = null
let router = null

// 初始化应用
function initApp(container) {
  router = createRouter({
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue-app' : '/'),
    routes
  })
  
  app = createApp(App)
  app.use(router)
  app.mount(container ? container.querySelector('#app') : '#app')
}

// 导出 qiankun 生命周期函数
if (window.__POWERED_BY_QIANKUN__) {
  // 作为微应用运行
  export async function bootstrap() {
    console.log('Vue app bootstraped')
  }
  
  export async function mount(props) {
    console.log('Vue app mounted', props)
    initApp(props.container)
  }
  
  export async function unmount() {
    console.log('Vue app unmounted')
    app.unmount()
    app = null
    router = null
  }
} else {
  // 作为独立应用运行
  initApp(null)
}
微应用 vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { name } from './package.json'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    port: 3002,
    headers: {
      'Access-Control-Allow-Origin': '*' // 允许跨域
    }
  },
  build: {
    outDir: 'dist',
    lib: {
      name: `${name}-[name]`,
      entry: resolve(__dirname, 'src/main.js'),
      formats: ['umd']
    },
    rollupOptions: {
      output: {
        format: 'umd',
        entryFileNames: 'js/[name].js',
        chunkFileNames: 'js/[name].js',
        assetFileNames: '[ext]/[name].[ext]',
        globals: {
          vue: 'Vue'
        }
      },
      external: ['vue']
    }
  }
})

微应用间通信

微应用之间需要有效的通信机制,常用的通信方式有:

1. 基于 Props 的通信

主应用可以通过 props 向微应用传递数据:

// 主应用
registerMicroApps([
  {
    name: 'vue-app',
    entry: '//localhost:3002',
    container: '#vue-app',
    activeRule: '/vue-app',
    props: {
      userInfo: { name: '张三', age: 30 },
      onMessage: (message) => {
        console.log('收到微应用消息:', message)
      }
    }
  }
])

2. 基于 Event Bus 的通信

可以使用 Event Bus 进行微应用之间的通信:

// src/utils/eventBus.js
export const eventBus = {
  events: {},
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
  },
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data))
    }
  },
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback)
    }
  }
}

3. 基于 Shared Store 的通信

可以使用共享状态管理库进行跨微应用的状态管理:

// src/store/index.js
import { createStore } from 'vuex'

// 共享状态
const store = createStore({
  state: {
    count: 0,
    userInfo: null
  },
  mutations: {
    increment(state) {
      state.count++
    },
    setUserInfo(state, userInfo) {
      state.userInfo = userInfo
    }
  },
  actions: {
    async fetchUserInfo({ commit }) {
      // 模拟 API 请求
      const userInfo = await new Promise(resolve => {
        setTimeout(() => {
          resolve({ name: '张三', age: 30 })
        }, 1000)
      })
      commit('setUserInfo', userInfo)
    }
  }
})

export default store

样式隔离

为了避免不同微应用的样式冲突,可以使用以下几种方法:

1. CSS Modules

使用 CSS Modules 可以确保样式的局部性:

<template>
  <div class="container">
    <h1 class="title">微应用</h1>
  </div>
</template>

<style module>
.container {
  padding: 20px;
  background-color: #f5f5f5;
}

.title {
  color: #333;
  font-size: 24px;
}
</style>

2. CSS-in-JS

使用 CSS-in-JS 解决方案,如 styled-components、emotion 等:

<template>
  <Container>
    <Title>微应用</Title>
  </Container>
</template>

<script setup>
import { styled } from 'styled-components'

const Container = styled.div`
  padding: 20px;
  background-color: #f5f5f5;
`

const Title = styled.h1`
  color: #333;
  font-size: 24px;
`
</script>

3. 样式前缀

为微应用的样式添加统一的前缀:

<template>
  <div class="vue-app-container">
    <h1 class="vue-app-title">微应用</h1>
  </div>
</template>

<style scoped>
.vue-app-container {
  padding: 20px;
  background-color: #f5f5f5;
}

.vue-app-title {
  color: #333;
  font-size: 24px;
}
</style>

4. Shadow DOM

使用 Shadow DOM 可以完全隔离样式:

// 为微应用创建 Shadow DOM
const shadowRoot = container.attachShadow({ mode: 'open' })
// 将微应用挂载到 Shadow DOM
app.mount(shadowRoot)

实战示例:构建一个完整的微应用

1. 项目结构

├── host-app/              # 主应用
│   ├── public/            # 静态资源
│   ├── src/               # 源代码
│   │   ├── App.vue        # 主应用组件
│   │   ├── main.js        # 入口文件
│   │   └── router/        # 路由配置
│   ├── package.json       # 项目配置
│   └── vite.config.js     # Vite 配置
├── vue-app/               # Vue 微应用
│   ├── public/            # 静态资源
│   ├── src/               # 源代码
│   │   ├── App.vue        # 微应用组件
│   │   ├── main.js        # 入口文件
│   │   └── router/        # 路由配置
│   ├── package.json       # 项目配置
│   └── vite.config.js     # Vite 配置
└── react-app/             # React 微应用
    ├── public/            # 静态资源
    ├── src/               # 源代码
    ├── package.json       # 项目配置
    └── vite.config.js     # Vite 配置

2. 主应用配置

// host-app/src/main.js
import { createApp } from 'vue'
import { registerMicroApps, start } from 'qiankun'
import App from './App.vue'
import router from './router'

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

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

// 启动 qiankun
start()

3. Vue 微应用配置

// vue-app/src/main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import routes from './router'

let app = null
let router = null

function initApp(container) {
  router = createRouter({
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue-app' : '/'),
    routes
  })
  
  app = createApp(App)
  app.use(router)
  app.mount(container ? container.querySelector('#app') : '#app')
}

if (window.__POWERED_BY_QIANKUN__) {
  export async function bootstrap() {
    console.log('Vue app bootstraped')
  }
  
  export async function mount(props) {
    console.log('Vue app mounted', props)
    initApp(props.container)
  }
  
  export async function unmount() {
    console.log('Vue app unmounted')
    app.unmount()
    app = null
    router = null
  }
} else {
  initApp(null)
}

微应用的最佳实践

1. 技术栈选择

  • 主应用建议使用稳定的技术栈,如 Vue 3、React 18 等
  • 微应用可以根据需求选择合适的技术栈
  • 尽量使用相同的技术栈,减少兼容性问题

2. 应用拆分策略

  • 按业务域拆分:将不同的业务域拆分为不同的微应用
  • 按功能模块拆分:将不同的功能模块拆分为不同的微应用
  • 按团队拆分:不同的团队负责不同的微应用

3. 通信机制

  • 优先使用基于 Props 的通信方式
  • 对于复杂的通信需求,使用 Event Bus 或 Shared Store
  • 避免过度依赖全局状态

4. 样式隔离

  • 使用 CSS Modules 或 CSS-in-JS 确保样式局部性
  • 为微应用的样式添加统一的前缀
  • 考虑使用 Shadow DOM 进行完全的样式隔离

5. 性能优化

  • 启用微应用的预加载
  • 优化微应用的代码分割
  • 避免不必要的微应用加载
  • 使用懒加载优化初始加载速度

总结

微前端是一种将大型前端应用拆分为多个独立小型应用的架构模式,它具有技术栈无关、独立开发和部署、增量迁移等优势。在 Vue 3 中,可以使用 Webpack Module Federation、Single-SPA、qiankun 等技术来构建微应用。

本集介绍了微应用的核心概念、构建方案、应用间通信、样式隔离和最佳实践等内容,并提供了完整的实战示例。通过合理使用微前端架构,可以提高大型应用的可维护性和扩展性,实现团队自治和独立部署。

在实际项目中,应该根据项目需求和团队情况,选择合适的微前端方案,并遵循最佳实践,确保微应用的性能和可维护性。

下集预告

第266集:库模式打包优化 - 深入探讨 Vue 3 库模式打包的优化策略,包括代码分割、树摇、多格式输出等。

« 上一篇 Vue 3 库开发与发布实战:构建高质量开源库 下一篇 » Vue 3 库模式打包优化实战:构建高质量库