架构概述
moyu 采用 主应用 + 子模块 的微前端架构模式,通过 MoyuConfig 全局配置中心实现模块间的解耦和通信。整个架构支持动态模块加载、按需启用、跨模块组件调用等功能。
项目结构
web_master/
├── src/ # 主应用代码
├── modules/ # 子模块目录
│ ├── moyu-systemset-page/ # 系统设置模块
│ ├── moyu-assetmanage-page/ # 资产管理模块
│ └── ... # 其他子模块
├── static/js/config.js # 全局配置中心
├── childModule.json # 模块配置清单
├── src/development_extension.js # 开发环境模块导入文件
└── build/ # 构建配置
1. 全局注册中心(moyuConfig)
位置:static/js/config.js
moyuConfig 充当主项目和子模块之间的通信总线,提供以下核心能力:
| 方法 | 功能说明 |
|---|---|
pushComponent() | 子模块注册组件、路由、Store |
getComponentByName() | 跨模块获取组件 |
getModuleByName() | 获取指定模块 |
getChildStores() | 获取所有子模块 Store |
getRootRouters() | 获取根路由配置 |
getChildRouters() | 获取子路由配置 |
2. 模块配置开关
位置:childModule.json
json
{
"moyu-assetmanage-page": {
"open": true,
"desc": "资产管理中心"
},
"moyu-logaudit-page": {
"open": true,
"desc": "日志审计中心"
}
}
- ✅
open: true- 启用该模块 - ❌
open: false- 禁用该模块 - 📝
desc- 模块描述信息
3. 构建配置别名
位置:webpack.dev.conf.js / vite.config.js
构建工具读取 childModule.json,动态生成模块别名:
javascript
// webpack.dev.conf.js
function getModuleAlias() {
const moduleAlias = {}
Object.keys(allModules).forEach(moduleName => {
moduleAlias[moduleName] = path.resolve(__dirname, `../modules/${moduleName}/src/index.js`)
});
return moduleAlias;
}
javascript
// vite.config.js
function getModuleAlias() {
Object.keys(allModules).forEach(moduleName => {
alias[moduleName] = path.resolve(__dirname, `./modules/${moduleName}/src/index.js`)
});
}
🔄 工作流程
| 节点 | 类型 | 作用说明 |
|---|---|---|
A[子模块 index.js] | 运行时入口 | 每个业务子模块的 JS 入口文件,通过 pushComponent(...) 方法向 moyuConfig 注册自身能力(如组件、路由、Store 模块等)。 |
B[moyuConfig] | 全局配置收集器 | 一个单例对象,用于在应用启动前收集所有已加载子模块的注册信息。 |
C[childModule.json] | 配置文件 | 存放于子模块目录下,包含 enabled: true/false 等字段,控制该模块是否参与构建和运行。 |
D[构建工具] | 构建阶段插件 | 在 Vite 或 Webpack 构建过程中读取所有 childModule.json,根据开关决定是否打包该模块,并生成对应的路径别名(如 @/modules/user → 实际路径)。 |
E[Webpack / Vite] | 构建系统 | 最终使用的打包工具,通过 alias 支持模块按需引入,实现物理隔离与逻辑聚合。 |
F[全局中心 MoyuCore] | 运行时核心枢纽 | 聚合 moyuConfig 中的所有注册项,对外提供统一接口(如 getChildStores(), initRouter())。 |
G[main.js] | 应用主入口 | Vue 项目主文件,在启动时调用全局中心的方法,完成 Store、路由等的动态初始化。 |
H[Vuex Store] | 状态管理实例 | 通过 store.registerModule() 动态注册子模块的 Vuex 模块,实现状态按需加载。 |
📦 子模块结构示例
位置:modules/moyu-assetmanage-page/src/index.js
import { pushComponent } from 'MoyuConfig'
import router from './router.js'
const components = {
AssetStatistics: () => import('./pages/assetsmanage/Assets_AssetList/AssetList.vue'),
addAsset: () => import('./pages/assetsmanage/Assets_AssetList/AssetAdd/AssetAdd.vue'),
// ... 更多组件
};
pushComponent({
key: 'assetmanage',
components: components,
router: router
});
export default {
key: 'assetmanage',
components: components,
router: router
}
关键特性
| 特性 | 实现方式 |
|---|---|
| 懒加载 | 使用 () => import() 异步加载组件 |
| 模块标识 | 通过 key 字段唯一标识模块 |
| 路由独立 | 每个模块拥有独立的 router.js |
| 按需注册 | 通过 pushComponent 注册到全局 |
🎯 主项目集成
位置:src/main.js
import {
setComponentsPath,
getChildStores,
setI18n
} from 'moyuConfig';
// 初始化路由
const { router, asyncRouter, asyncRouterConfigCenter } = initRouter();
// 初始化 Vuex Store
const store = new Vuex.Store(rootStore);
// 动态注册子模块 Store
const childStores = getChildStores();
for (let i = 0; i < childStores.length; i++) {
store.registerModule(childStores[i].key, childStores[i].value);
}
// 创建 Vue 实例
const app = new Vue({
el: '#app',
i18n,
router,
store,
template: '<App/>',
components: { App }
});
🔌 跨模块组件调用
javascript
// 方式一:通过 MoyuConfig 获取
import { getComponentByName } from 'MoyuConfig'
const AssetList = getComponentByName('assetmanage', 'AssetStatistics');
// 方式二:直接通过别名导入(构建时解析)
import AssetList from 'moyu-assetmanage-page';
📊 架构优势总结
| 优势 | 说明 |
|---|---|
| 🧩 高度解耦 | 子模块独立开发,无需修改主项目代码 |
| ⚡ 按需加载 | 通过 childModule.json 控制模块启用 |
| 🔄 动态注册 | 运行时自动聚合路由、组件、Store |
| 📦 独立打包 | 支持 UMD 格式独立打包(webpack.package.conf.js) |
| 🛠️ 双构建支持 | 同时支持 Webpack 和 Vite 构建 |
| 🌐 跨模块通信 | 通过全局注册中心实现模块间组件调用 |
📁 关键文件清单
| 文件路径 | 作用 |
|---|---|
static/js/config.js | 全局注册中心 MoyuConfig |
childModule.json | 模块启用配置 |
src/main.js | 主项目入口,聚合子模块 |
modules/*/src/index.js | 子模块入口,注册组件 |
build/webpack.dev.conf.js | Webpack 开发配置 |
vite.config.js | Vite 构建配置 |
build/changeModules/webpack.package.conf.js | 子模块独立打包配置 |
🎯 打包构建流程
开发环境构建
- 启动构建脚本:
npm run dev - 生成模块导入文件:
prepare.createFile() - 监听配置变化:
prepare.watchConfig() - Webpack 别名配置: 模块名映射到实际路径
- 热重载: 修改代码自动刷新
生产环境构建
- 读取 childModule.json: 获取所有启用的模块
- 静态分析依赖: Webpack 分析模块依赖关系
- 代码分割: 按模块进行代码分割
- 生成最终包: 包含主应用和所有启用的子模块
关键优势
| 特性 | 说明 |
|---|---|
| 模块解耦 | 子模块独立开发、测试、部署 |
| 按需加载 | 通过配置控制模块启用状态 |
| 跨模块通信 | 统一的组件调用和 Store 访问机制 |
| 开发友好 | 配置驱动,无需手动维护导入语句 |
| 版本同步 | 自动下载对应分支的子模块 |
| 错误处理 | 组件缺失时提供友好的错误提示 |
🎓 总结
整个架构像一个 插件系统:
- 主框架(
main.js)提供运行环境和全局插座(MoyurConfig)- 子模块(
modules/*)是插件,启动时自动插入插座注册自己- 配置文件(
childModule.json)是开关,决定哪些插件生效- 构建工具(
Webpack/Vite)负责连线,通过别名让代码能找到彼此
这种设计使得新增功能模块时,只需在 modules 目录添加代码并修改 childModule.json,无需大量修改主项目代码,实现了高度的解耦和可扩展性。
1. 核心通信机制:全局注册中心 项目定义了一个全局对象
moyuConfig(位于 static/js/config.js),它充当了主项目和子模块之间的“通信总线”。
- 注册:子模块(如
modules/moyu-assetmanage-page/src/index.js)加载时,调用MoyuConfig.pushComponent方法,将自己的组件、路由、Store 注册到全局中心。 - 获取:主项目或其他模块需要通过
MoyuConfig.getComponentByName或MoyuConfig.getModuleByName来获取已注册的组件,实现跨模块调用,避免直接引用代码。
2. 模块解耦与动态启用 模块的启用与否由配置文件
childModule.json 控制,实现了按需加载。
- 配置开关:
childModule.json中定义了所有子模块(如moyu-assetmanage-page),通过open: true字段控制是否启用。 - 构建别名:构建工具(
webpack.dev.conf.js或vite.config.js)读取该 JSON 文件,动态生成 webpack/vite 的 alias 配置。这使得代码中可以通过模块名直接引用子模块入口,而无需关心物理路径。 - 物理隔离:子模块代码存放在
modules/目录下,拥有独立的src/index.js入口,逻辑上相互独立。
3. 路由与状态管理聚合
主项目启动时,动态收集子模块的路由和状态,实现统一化管理。
- 路由聚合:主项目
src/main.js中调用 initRouter,底层会读取MoyurConfig中收集的路由配置(getRootRouters等),将子模块路由动态挂载到主路由实例。 - 状态共享:主项目通过
MoyuConfig.getChildStores获取子模块的 Store 配置,并在 Vuex 初始化时通过store.registerModule动态注册,实现全局状态共享。
4. 组件按需加载
为了优化性能,子模块内部采用了懒加载策略。
- 异步组件:在子模块的
index.js中,组件定义使用箭头函数配合import()(如AssetStatistics: () => import(...)),确保只有当组件真正被使用时才会加载代码。 - 独立打包潜力:
build/changeModules/webpack.package.conf.js显示子模块支持配置为umd格式独立打包,这意味着未来支持通过 CDN 或微前端方式独立部署子模块。