模块联邦:更快的微前端方式!
什么是模块联邦
在前端项目中,不同团队之间的业务模块可能有耦合,比如A团队的页面里有一个富文本模块(组件),而B团队 的页面恰好也需要使用这个富文本模块。
传统模式下,B团队只能去抄A团队的代码,把这个组件放到自己的项目了。
为了解决不同服务之间组件共享的问题,Webpack 的模块联邦功能应用而生。借助模块联邦,可以在B服务运行时,动态加载A服务暴露的模块。
Vite通过vite-plugin-federation 插件也可以实现
模块联邦的核心优势
结合模块联邦的原理与功能,它具有以下显著优势:
- 独立部署:各个应用可独立开发、构建和部署,互不依赖。
- 运行时共享:模块在运行时动态加载,无需在构建阶段打包进主应用。
- 版本控制:支持为共享模块指定版本,便于多项目之间的依赖管理。
- 减少重复代码:多个应用可共享功能模块,避免重复实现。
相比 qiankun 等微前端方案,模块联邦在灵活性上更具优势。qiankun 侧重于加载整个子应用,而模块联邦支持按需加载特定模块,粒度更细,使用更灵活。
如何实现
以Vite+Vue为例。
假设我们有 Vite 应用:
**ui-library**
:组件提供者,暴露一个按钮组件SharedButton.vue
。**main-app**
:主应用,运行时动态加载ui-library
中的按钮组件来使用。
shixiaoshi-demo/
│
├── ui-library/ # 远程模块:暴露组件
│ ├── src/SharedButton.vue
│ ├── vite.config.js
│
└── main-app/ # 主项目:加载远程组件├── src/App.vue├── vite.config.js
1、安装模块联邦插件
我们需要使用 vite-plugin-federation 插件。
npm install vite-plugin-federation --save-dev
2、暴露组件
ui-library/src/SharedButton.vue
<template><button class="shared-btn">我是共享按钮</button></template><script setup></script><style scoped>.shared-btn {padding: 10px;background-color: teal;color: white;border: none;border-radius: 5px;
}
</style>
ui-library/vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import federation from '@originjs/vite-plugin-federation';export default defineConfig({plugins: [vue(),federation({name: 'ui_library',filename: 'remoteEntry.js',exposes: {'./SharedButton': './src/SharedButton.vue',},shared: ['vue'],}),],build: {target: 'esnext',minify: false,cssCodeSplit: false,},
});
启动服务
npm run dev -- --port=5001
暴露地址:http://localhost:5001/assets/remoteEntry.js
3
、加载远程组件
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({plugins: [vue(),federation({name: 'main_app',remotes: {ui_library: 'http://localhost:5001/assets/remoteEntry.js',},shared: ['vue'],}),],
});
main-app/src/App.vue
<template><div><h1>主应用</h1><RemoteButton /></div></template><script setup>import { defineAsyncComponent } from 'vue';// 从 ui-library 中动态加载共享组件
const RemoteButton = defineAsyncComponent(() => import('ui_library/SharedButton'));
</script>
- 启动主应用
npm run dev
访问 http://localhost:5173
,你会看到来自 ui-library
的按钮组件成功渲染!
主应用与子组件关系图
main-app (主应用)|| -- 在运行时访问 -->| http://localhost:5001/assets/remoteEntry.js|| -- 加载 -->| ui-library 中暴露的 SharedButton.vue|| -- 使用 -->| 作为主应用的本地组件一样渲染
缺陷
在实际使用中,模块联邦也存在一些问题。
以我们项目开发中遇到的问题为例,当应用 A 加载应用 B 中的某个组件时,B 的代码执行环境会落在 A 的作用域下。这会带来一些潜在影响:
- 依赖冲突问题:如果 B 服务依赖特定版本的 Vue,而 A 服务的 Vue 版本不同,实际运行时组件会使用 A 的 Vue 实例,可能导致行为异常,特别是在响应式系统、生命周期等细节方面。
- 状态失效问题:如果 B 中的组件依赖 Vuex 或 Pinia 等状态管理工具,运行时由于上下文切换,可能无法正确获取或响应状态,导致 store 在远程加载后失效。
- 构建配置差异问题:例如,B 使用了自动引入插件(如 unplugin-auto-import),可以在代码中直接使用
ref
、computed
等 Vue API 而无需显式导入;但若 A 没有启用相同插件,加载 B 的组件时会因缺少这些 API 导致运行错误,除非显式引入相关函数。
因此,在使用模块联邦时,建议主应用与远程模块在框架版本、构建工具、插件配置等方面尽可能保持一致,或通过适配包装进行隔离,以减少因上下文差异带来的问题。
关于我
一个热爱技术分享的资深前端工程师,技术栈为Vue、React、Threejs,当然对一些前沿的技术也比较感兴趣,如微前端、鸿蒙开发、油猴脚本开发等。
如果你对前端技术也充满热爱或者希望关注一些前沿技术,欢迎加群讨论~
当然,如果你有任何面试、工作上遇到的技术问题也都可以在群里提问,有时间我就会及时回答~