提示:记录工作中遇到的需求及解决办法
在现代大型前端项目开发中,多团队协作时往往面临代码隔离与集成的挑战。为了解决这一问题,我们需要一种能够让各个微前端模块既能独立开发部署,又能作为完整系统一部分的解决方案。基于 Vite 的模块联邦插件 @originjs/vite-plugin-federation
提供了一种去中心化的微前端架构实现方式,实现了组件、路由的跨应用共享和动态加载。本文将结合实际项目经验,详细介绍如何利用模块联邦技术在 「vue3 生态」中构建去中心化的微前端架构。
node: 18.20.5
{
"vite": "^6.0.2",
"@originjs/vite-plugin-federation": "1.4.1",
}
提示:以下是本篇文章正文内容,下面案例可供参考
@originjs/vite-plugin-federation
是 Vite 生态中实现模块联邦功能的插件,它允许多个独立应用在运行时共享模块和代码,无需重复构建或打包依赖。这一特性在去中心化微前端架构中尤为关键。
一个微前端模块的典型配置如下:
federation({
name: "pageAModule", // 微前端模块名称
filename: "pageAEntry.js", // 入口文件名
exposes: {
// 暴露的模块,此处为路由
'./routes': './src/routes/index.ts'
},
remote: {
menuModule: ModuleUrl, // 引入的远程模块
}
shared: ['vue', 'vue-router', 'pinia'] // 共享依赖
})
// 使用远程模块
import 'menuModule/xxx'
传统微前端架构通常采用 “基座模式”,即一个主应用作为基座控制其他子应用的加载和渲染。而去中心化微前端架构没有明确的主应用,各个子应用可以独立部署、独立运行,同时又能无缝协作。
去中心化微前端架构的主要特点:
虽然我们采用去中心化的微前端架构,但在后台管理系统中,我们仍然需要一个统一的菜单管理模块(menuModule)。来提供系统的 layout 和管理一些公共文件。
menuModule 模块需要在所有的页面项目中引入,且所有项目在 vite 的 federation 中都只需引入这一个 menuModule 模块就行。 其他剩余的模块引入方式,在下文有提到。
menuModule 承担以下职责:
在实际项目中,每个页面的微前端模块的联邦配置通常包含以下部分:
federation({
name: config.projectPrefix + 'Module', // 微前端模块名称
filename: config.projectPrefix + 'Entry.js', // 入口文件名
exposes: {
'./routes': './src/routes/index.ts', // 暴露路由配置(关键)
},
remotes: {
// 后台管理系统中需要单独引入菜单模块
menuModule: ModuleUrl,
},
shared: ['vue', 'vue-router', 'pinia', 'dayjs', 'axios', 'sass', 'element-plus'], // 共享依赖
})
在构建微前端模块时,vite-plugin-federation
会进行以下处理:
exposes
配置中声明的路由模块传统的模块联邦使用方式是预先声明远程模块并直接导入:
// 常规的模块联邦导入方法
import Component from 'remoteModule/Component'
这种方式存在一个重要限制:「无法使用动态字符串拼接模块名」。例如,以下代码在模块联邦中不起作用:
// 这种写法在模块联邦中不支持
const moduleName = 'remoteModule'
import Component from `${
moduleName}/Component` // 不支持!
这样就会导致一个问题就是 在去中心化微前端架构中,每个项目模块在开发时并不知道全局系统中到底有多少个联邦模块,也无法预先确定所有模块的名称和地址。为了支持新增模块的灵活扩展,需要一个动态的机制来发现和加载模块。通常,我们会通过一个配置文件(如 registry.json)来集中管理所有联邦模块的注册信息,允许新模块随时加入系统。然而,官方的模块联邦导入方式,它不支持动态拼接模块名称的字符串。
为了解决这一问题,我实现了一个支持动态拼接模块名称的加载函数 getRemoteEntries。通过该函数,我们可以在运行时根据配置文件动态获取模块的 URL 并加载模块,从而实现真正的动态模块发现和集成。这种方式不仅解决了官方导入方式的限制,还为系统的扩展性和灵活性提供了强有力的支持。
为解决上述限制,我实现了 getRemoteEntries
函数,通过 @originjs/vite-plugin-federation
的 get
API 实现,运行时动态的模块声明和加载:
// src/utils/federation.ts
export const getRemoteEntries = async (name ? : string, moduleName ? : string): Promise < any[] > => {
try {
// 从注册中心获取所有可用模块的信息
const response = await axios.get(`${
baseUrl}/federation/registry.json`)
// 定义过滤条件
const filterByName