第十八章:微前端与 Module Federation
18.1 什么是 Module Federation
Module Federation(模块联邦)是 Webpack 5 引入的微前端核心特性,允许应用在运行时动态加载其他应用的模块。Vite 通过社区插件实现了类似能力。
核心概念
- 宿主(Host):主应用,消费远程模块
- 远程(Remote):子应用,暴露模块供消费
- 共享(Shared):共享依赖,避免重复加载
18.2 Vite 中的 Module Federation 方案
主流插件对比
| 插件 | 特点 | 适用场景 |
|---|---|---|
@originjs/vite-plugin-federation |
最成熟,兼容 Webpack MF | 生产环境 |
vite-plugin-module-federation |
轻量级,快速接入 | 开发测试 |
@module-federation/vite |
官方最新方案,功能完整 | 新项目推荐 |
18.3 快速搭建微前端架构
安装依赖
bash
# 宿主和远程应用都需要安装
npm install @module-federation/vite -D
远程应用配置(子应用)
javascript
// remote-app/vite.config.js
import { defineConfig } from 'vite'
import federation from '@module-federation/vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
federation({
name: 'remote_app', // 远程应用名称
filename: 'remoteEntry.js', // 暴露的入口文件
exposes: {
'./Button': './src/components/Button.vue',
'./store': './src/store/index.ts'
},
shared: ['vue', 'vue-router', 'pinia'] // 共享依赖
})
],
build: {
target: 'esnext',
minify: false,
cssCodeSplit: false
}
})
宿主应用配置(主应用)
javascript
// host-app/vite.config.js
import { defineConfig } from 'vite'
import federation from '@module-federation/vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
federation({
name: 'host_app',
remotes: {
remote_app: 'http://localhost:5001/assets/remoteEntry.js'
},
shared: ['vue', 'vue-router', 'pinia']
})
]
})
使用远程模块
vue
<!-- host-app/src/App.vue -->
<template>
<div>
<h1>宿主应用</h1>
<!-- 使用远程组件 -->
<RemoteButton />
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 动态导入远程组件
const RemoteButton = defineAsyncComponent(() => import('remote_app/Button'))
</script>
18.4 高级配置
共享依赖策略
javascript
federation({
shared: {
vue: {
singleton: true, // 确保单例
requiredVersion: '^3.3.0',
eager: false
},
'vue-router': {
singleton: true,
requiredVersion: '^4.2.0'
}
}
})
动态远程加载
javascript
// 运行时动态加载远程模块
async function loadRemoteApp(url) {
// 动态添加远程
await __federation_import(`${url}/remoteEntry.js`)
// 动态导入模块
const module = await import('dynamic_remote/Component')
return module.default
}
版本冲突处理
javascript
federation({
shared: (pkgName, shareConfig) => {
// 自定义共享策略
if (pkgName === 'vue') {
return {
...shareConfig,
version: '3.3.4',
shareScope: 'default'
}
}
return shareConfig
}
})
18.5 与 Webpack Module Federation 互操作
Vite 消费 Webpack 远程
javascript
// vite.config.js
federation({
remotes: {
webpack_app: 'webpack_app@http://localhost:3000/remoteEntry.js'
}
})
Webpack 消费 Vite 远程
javascript
// webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
vite_app: 'vite_app@http://localhost:5001/assets/remoteEntry.js'
}
})
]
}
注意:互操作时需确保:
- 共享依赖版本兼容
- 构建目标一致(ESM 格式)
- 跨域配置正确
18.6 生产环境部署
跨域配置
javascript
// 远程应用添加 CORS 头
server: {
cors: {
origin: ['https://host-app.com'],
methods: ['GET', 'HEAD']
}
}
CDN 部署
javascript
// 根据环境动态配置
const remotes = {
remote_app: process.env.NODE_ENV === 'production'
? 'remote_app@https://cdn.example.com/remoteEntry.js'
: 'remote_app@http://localhost:5001/assets/remoteEntry.js'
}
版本管理
javascript
// 使用版本标签
const REMOTE_VERSION = '1.2.0'
const remoteUrl = `https://cdn.example.com/${REMOTE_VERSION}/remoteEntry.js`
18.7 性能优化
预加载远程资源
html
<!-- 在 HTML 中预加载 -->
<link rel="prefetch" href="http://localhost:5001/assets/remoteEntry.js">
懒加载路由
javascript
// router/index.js
const routes = [
{
path: '/remote',
component: () => import('remote_app/Page')
}
]
共享依赖优化
javascript
federation({
shared: {
// 只共享必要的依赖
lodash: { singleton: false, eager: false },
moment: { singleton: false } // 允许重复加载
}
})
18.8 常见问题
Q1: 样式丢失
原因 :远程组件的样式未正确打包
解决:
javascript
build: {
cssCodeSplit: false // 不拆分 CSS
}
Q2: 共享状态不同步
原因 :Pinia/Vuex 未正确共享
解决:
javascript
shared: {
pinia: {
singleton: true,
eager: true
}
}
Q3: TypeScript 类型提示缺失
解决:生成类型声明文件
javascript
// 远程应用生成 .d.ts
exposes: {
'./Button': './src/components/Button.vue',
'./types': './src/types/index.ts' // 导出类型
}
Q4: 热更新失效
原因 :远程模块不支持 HMR
解决:开发时使用本地模式,生产时使用远程模式
18.9 最佳实践
1. 微前端拆分原则
- 按业务域拆分(用户中心、订单中心)
- 独立团队独立部署
- 共享依赖版本统一管理
2. 通信机制
javascript
// 使用 CustomEvent 进行跨应用通信
// 发送事件
window.dispatchEvent(new CustomEvent('app-event', {
detail: { type: 'USER_LOGGED_IN', data: user }
}))
// 监听事件
window.addEventListener('app-event', (e) => {
console.log('收到消息:', e.detail)
})
3. 错误边界
vue
<template>
<Suspense>
<RemoteComponent />
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { onErrorCaptured } from 'vue'
onErrorCaptured((err) => {
console.error('远程组件加载失败:', err)
return false // 阻止错误继续传播
})
</script>
18.10 未来趋势
Vite 原生 Module Federation
Vite 团队正在推进原生支持,未来可能无需插件:
- 更快的热更新
- 更好的开发体验
- 与 Vite 生态深度集成
与 RSC 的结合
React Server Components + Module Federation 可能成为新的微前端方案。
本章小结
Module Federation 让 Vite 项目具备了微前端能力:
- 独立开发、独立部署
- 运行时动态集成
- 共享依赖避免重复加载
选择合适插件,遵循最佳实践,可以构建可扩展的微前端架构。
下一步:下一章将介绍 Vite 项目的性能监控与分析。