Vue 3 微前端实战:用模块联邦(Module Federation)优雅共享组件 🚀
摘要:还在为跨项目共享组件而烦恼吗?每次组件更新都要发版 npm,再到各个项目里更新依赖,简直是"沟通两小时,发布一下午"。今天,我们来聊聊微前端的利器------模块联邦(Module Federation),让你丝滑地实现组件实时共享!
🤔 前言:我们遇到了什么问题?
在现代前端开发中,组件化思想已深入人心。但当我们的应用变得越来越庞大,甚至拆分成多个独立项目时,一个新的问题浮出水面:
-
组件复用难 :一个设计精美的
Button
或一个功能复杂的DataGrid
组件,如何在多个项目中复用? -
传统方式的痛点:
- 复制粘贴:最原始的方式,但也是最糟糕的。一旦组件有 Bug 或需求变更,你需要去每个项目里都改一遍,简直是噩梦。
- 发布为 NPM 包:这是标准的解决方案。但它也带来了流程上的繁琐:修改组件 -> 打包 -> 发布 npm -> 其他项目更新依赖 -> 重新部署。一个小小的文案修改,可能都要走完这一整套流程,效率极低。
有没有一种方式,可以让 A 项目的组件,直接、实时地被 B 项目使用,就像使用内部组件一样简单?
答案是肯定的!模块联邦 (Module Federation) 就是为此而生的。
✨ 什么是模块联邦?
模块联邦是 Webpack 5 提出的一个革命性功能(现在 Vite 也有了强大的社区插件支持),它允许一个 JavaScript 应用在运行时动态地加载另一个应用的代码。
听起来有点抽象?我们把它具象化:
- Remote (提供方) :一个独立的应用,它将自己的某些模块(比如组件、函数)"暴露"出去,供其他应用使用。
- Host (消费方) :另一个独立的应用,它可以"引用"并渲染 Remote 应用暴露出来的模块。
这种方式的最大优势在于:Remote 应用的更新可以无需 Host 应用重新部署,只要 Remote 重新部署,Host 刷新页面就能获取到最新的组件!
话不多说,让我们撸起袖子,直接开干!
场景设定
为了方便演示,我们创建两个 Vue 3 项目:
-
packages-center
(Remote - 提供方)- 职责:存放和暴露公共组件。
- 运行在:
http://localhost:3001
- 我们将从中导出一个名为
CoolButton.vue
的组件。
-
main-app
(Host - 消费方)- 职责:主应用,需要使用
packages-center
的组件。 - 运行在:
http://localhost:3000
- 职责:主应用,需要使用
本文将优先介绍目前更流行的 Vite 方案,之后会补充 Webpack 的配置方法。
🚀 实战演练:Vite 篇 (推荐)
Vite 凭借其极速的开发体验,在 Vue 3 社区中备受欢迎。我们可以使用 @originjs/vite-plugin-federation
插件来轻松实现模块联邦。
第一步:配置组件提供方 (packages-center
)
1. 安装插件
Bash
bash
# 进入 packages-center 项目目录
npm install @originjs/vite-plugin-federation --save-dev
2. 创建要共享的组件
在 src/components/
目录下创建一个 CoolButton.vue
。
代码段
xml
// src/components/CoolButton.vue
<template>
<button class="cool-button">
<slot>✨ 一个来自远方的酷按钮</slot>
</button>
</template>
<script setup>
// 你的组件可以有复杂的逻辑
console.log('CoolButton 已加载!');
</script>
<style scoped>
.cool-button {
padding: 12px 24px;
border-radius: 8px;
border: none;
background: linear-gradient(45deg, #FF6B6B, #FFD93D);
color: white;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.cool-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}
</style>
3. 修改 vite.config.js
这是最核心的配置!我们需要告诉 Vite 要暴露哪些模块。
JavaScript
javascript
// packages-center/vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
// 确保开发服务器端口是我们设定的 3001
server: {
port: 3001,
},
plugins: [
vue(),
federation({
// 模块联邦的全局唯一名称
name: 'packages-center',
// 导出的入口文件名
filename: 'remoteEntry.js',
// exposes: 定义需要暴露的模块
exposes: {
// './CoolButton' 是外部访问的别名
// './src/components/CoolButton.vue' 是组件的实际路径
'./CoolButton': './src/components/CoolButton.vue',
},
// shared: 定义需要与 Host 共享的依赖
// 这可以避免重复加载,减小包体积,并防止因版本不一致导致的问题
shared: ['vue']
})
],
build: {
target: 'esnext' // 浏览器兼容性目标
}
})
OK!启动 packages-center
项目 (npm run dev
),我们的组件就已经成功"广播"出去了!
第二步:配置组件消费方 (main-app
)
现在,我们来 main-app
项目里接收并使用这个按钮。
1. 安装插件 (同上)
Bash
bash
# 进入 main-app 项目目录
npm install @originjs/vite-plugin-federation --save-dev
2. 修改 vite.config.js
JavaScript
php
// main-app/vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
server: {
port: 3000,
},
plugins: [
vue(),
federation({
name: 'main-app',
// remotes: 声明需要引用的远程模块
remotes: {
// 'packages_center' 是我们在本地使用的别名
// 'packages-center@http://localhost:3001/assets/remoteEntry.js' 是远程模块的地址
'packages_center': 'packages-center@http://localhost:3001/assets/remoteEntry.js',
},
// 共享的依赖,应该和 remote 方保持一致
shared: ['vue']
})
],
})
注意 :Vite Dev Server 启动时,remoteEntry.js
通常位于 /assets/
目录下。
3. 在页面中使用远程组件
最佳实践是使用 Vue 的 defineAsyncComponent
进行异步加载,这样只有在需要时才会去下载远程组件的代码,并且可以优雅地处理加载状态。
修改你的 App.vue
:
代码段
xml
// main-app/src/App.vue
<template>
<div class="main-container">
<h1>欢迎来到主应用 (Host)</h1>
<p>下面这个超酷的按钮,就是从 `packages-center` 实时加载的!</p>
<Suspense>
<template #default>
<RemoteCoolButton @click="handleRemoteClick" />
</template>
<template #fallback>
<div>正在努力加载远程按钮...</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
// 异步加载远程组件
// 'packages_center' 对应 vite.config.js 中 remotes 的 key
// '/CoolButton' 对应 remote 应用 exposes 的 key
const RemoteCoolButton = defineAsyncComponent(() => import('packages_center/CoolButton'));
const handleRemoteClick = () => {
alert('你点击了来自 `packages-center` 的按钮!');
}
</script>
<style>
.main-container {
text-align: center;
margin-top: 60px;
}
</style>
现在,同时启动 packages-center
和 main-app
两个项目 。访问 http://localhost:3000
,你就能看到那个酷炫的按钮了!
不信?去 packages-center
项目里修改一下 CoolButton.vue
的文案或样式,保存后,回到 main-app
刷新页面------你会发现按钮已经神奇地同步更新了!是不是很酷?
📦 Webpack 篇
如果你仍在使用 Vue CLI (基于 Webpack),配置同样简单。
1. 提供方 (packages-center
) 配置
在 vue.config.js
中配置 ModuleFederationPlugin
。
JavaScript
css
// packages-center/vue.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
publicPath: 'auto',
devServer: {
port: 3001,
},
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'packages_center',
filename: 'remoteEntry.js',
exposes: {
'./CoolButton': './src/components/CoolButton.vue',
},
shared: {
vue: {
singleton: true, // 保证 vue 实例唯一
requiredVersion: require('./package.json').dependencies.vue,
},
},
}),
],
},
};
2. 消费方 (main-app
) 配置
JavaScript
css
// main-app/vue.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
publicPath: 'auto',
devServer: {
port: 3000,
},
configureWebpack: {
plugins: [
new ModuleFederationPlugin({
name: 'main_app',
remotes: {
'packages_center': 'packages_center@http://localhost:3001/remoteEntry.js',
},
shared: {
vue: {
singleton: true,
requiredVersion: require('./package.json').dependencies.vue,
},
},
}),
],
},
};
组件的使用方式与 Vite 方案完全相同,依然是通过 defineAsyncComponent
异步加载。
📌 核心要点与避坑指南
shared
是关键 :务必共享vue
、vue-router
、pinia
等核心库。否则,每个应用都会打包一份,不仅增大体积,还可能因为实例不唯一导致各种奇怪的 Bug。- 异步加载与
Suspense
:远程组件加载需要网络请求,使用defineAsyncComponent
和<Suspense>
能极大提升用户体验,避免页面白屏。 - CSS 样式隔离 :在编写共享组件时,尽量使用
scoped
CSS 来避免样式污染。如果需要共享全局样式,需要团队约定好加载顺序和命名规范。 - TypeScript 支持 :跨应用共享组件时,TS 类型定义会丢失。你需要为远程模块手动编写
.d.ts
声明文件,或者使用一些社区工具来自动生成。 - 版本控制 :虽然可以实时更新,但也带来了版本不一致的风险。重要更新建议做好通知,避免
breaking change
影响消费方应用。
总结
模块联邦为前端微服务化提供了一种极其优雅和高效的实现方案。通过本文的实战,相信你已经掌握了如何在 Vue 3 项目中利用它来共享组件。告别繁琐的 npm 发布流程,拥抱更加动态和灵活的架构吧!
这只是模块联邦的冰山一角,它还可以用于共享工具函数、路由、甚至整个页面。快在你的项目中尝试一下,体验开发的"加速度"!