1. 架构概述
moyu 采用 主应用 + 子模块 的微前端架构模式,通过 MoyuConfig 全局配置中心实现模块间的解耦和通信。整个架构支持动态模块加载、按需启用、跨模块组件调用等功能。
2. 项目结构
web_master/
├── src/ # 主应用代码
├── modules/ # 子模块目录
│ ├── moyu-systemset-page/ # 系统设置模块
│ ├── moyu-assetmanage-page/ # 资产管理模块
│ └── ... # 其他子模块
├── static/js/config.js # 全局配置中心
├── childModule.json # 模块配置清单
├── src/development_extension.js # 开发环境模块导入文件
└── build/ # 构建配置
3. 子项目创建流程
3.1 模块目录结构
每个子模块遵循统一的目录结构:
modules/moyu-xxx-page/
├── src/
│ ├── index.js # 模块入口文件
│ ├── router.js # 路由配置
│ ├── components/ # 组件目录
│ └── pages/ # 页面目录
└── package.json # 模块包配置
3.2 模块入口文件 (index.js)
js// modules/moyu-systemset-page/src/index.js
import { pushComponent } from 'MoyuConfig'
import store from './store.js'
import router from './router.js'
import customSetMixin from './home/CustomSetMixin.js'
const components = {
sysOverview: () => import('./home/HomeCardPage.vue'),
// ... 其他组件
};
pushComponent({
key: 'systemSet', // 模块唯一标识
components: components, // 组件集合
router: router, // 路由配置
store: store // 子模块状态
});
3.3 模块注册机制
key: 模块唯一标识,在全局配置中作为命名空间- components: 所有可被其他模块调用的组件
- router: 模块路由配置
- store: 子模块状态
4. 模块导入机制
4.1 配置驱动导入
childModule.json 配置文件
json{
"sicap-systemset-page": {
"open": true,
"desc": "系统设置"
},
"sicap-assetmanage-page": {
"open": false,
"desc": "资产管理中心"
}
}
自动化生成导入文件
js// build/service/developConfig.js
function createFile(allModulesData) {
const modules = [];
let allModules = {};
if (!allModulesData) {
const configContent = fs.readFileSync(path.resolve(__dirname, '../../childModule.json'), 'utf-8')
allModules = JSON.parse(configContent);
} else {
allModules = allModulesData;
}
Object.keys(allModules).forEach(moduleName => {
if (allModules[moduleName].open) {
modules.push(`import '${moduleName}'`); // 启用模块
} else {
modules.push(`// import '${moduleName}'`); // 注释禁用模块
}
});
const developContent = modules.join('\n');
fs.writeFileSync(path.resolve(__dirname, '../../src/development_extension.js'), developContent);
console.log('子项目扩展已生成');
}
生成的导入文件
js// src/development_extension.js
import 'sicap-systemset-page'
// import 'sicap-assetmanage-page'
import 'sicap-operationaudit-page'
4.2 Webpack 别名配置
js// webpack.dev.conf.js
function getModuleAlias() {
Object.keys(allModules).forEach(moduleName => {
moduleAlias[moduleName] = path.resolve(__dirname, `../modules/${moduleName}/src/index.js`)
});
return moduleAlias;
}
resolve: {
alias: webpackAlias // 模块名 → 文件路径映射
}
5. 模块关联与通信
5.1 跨模块组件调用
使用 getComponentByName 调用其他模块组件
js// modules/sicap-identityauthe-page/src/pages/statisticsreport/CustomReport/CustomSetMixin.js
import { getComponentByName } from 'MoyuConfig';
// 从 operationAudit 模块获取 viewType1 组件
const viewType = getComponentByName('operationAudit', 'viewType'); // 运维审计中心
const AlarmView = getComponentByName('systemSet', 'AlarmView'); // 系统设置
export default {
components: {
viewType,
AlarmView,
// ... 其他组件
}
};
getComponentByName 实现原理
js// static/js/config.js
exports.getComponentByName = function(moduleName, componentName, flag) {
try {
let module = getModuleByName(moduleName); // 获取模块组件集合
if(module[componentName]) {
return module[componentName]; // 返回具体组件
} else {
throw new Error('Can not find component by name ' + componentName);
}
} catch (error) {
if (flag) {
// 返回错误提示组件
return { template: '<s-alert title="请安装'+ moduleName + '模块,并导出' + componentName + '组件" type="error"></s-alert>' };
} else {
console.error(error.message)
}
}
}
5.2 模块数据存储结构
js// static/js/config.js 内部变量
var modules = {}; // 存储格式: { 'systemSet': { sysOverview: Component, ... }, 'operationAudit': { viewType1: Component, ... } }
6. Store 通信机制
6.1 子模块 Store 注册
子模块在 index.js 中可以提供 store 配置:
js// 子模块 index.js
const store = {
assetStore: {
state: { /* ... */ },
mutations: { /* ... */ },
actions: { /* ... */ }
}
};
pushComponent({
key: 'assetmanage',
components: components,
router: router,
store: store // 提供 store 配置
});
6.2 Store 自动注册到主应用
js// static/js/config.js - pushComponent 方法
// 创建全局对象
global.MoyuConfig = global.MoyuConfig || {}
factory(global.MoyuConfig)
if (component.store) {
let stores = component.store;
for (var key in stores) {
if (Object.prototype.hasOwnProperty.call(stores, key))
childStores.push({
'key': key, // store 模块名
'value': stores[key] // store 配置对象
});
}
}
6.3 主应用初始化 Store
js// src/main.js
const store = new Vuex.Store(rootStore);
const childStores = getChildStores();
for (let i = 0; i < childStores.length; i++) {
store.registerModule(childStores[i].key, childStores[i].value);
}
6.4 跨模块 Store 访问
由于所有子模块的 store 都注册到了同一个 Vuex 实例中,因此可以在任意组件中访问:
js// 在任何组件中
this.$store.state.assetStore.someState
this.$store.dispatch('assetStore/someAction')
7. 路由集成机制
7.1 子模块路由配置
js// modules/sicap-logaudit-page/src/router.js
const rootRouter = [
{
path: '/logAudit',
component: 'Home',
name: 'logAudit',
children: [],
meta: { /* ... */ }
}
];
const logAudit = [ /* 子路由配置 */ ];
export default {
rootRouter,
childRouter: {
logAudit
}
}
7.2 路由自动收集
js// static/js/config.js - pushComponent 方法
if (component.router) {
if (component.router.rootRouter && component.router.rootRouter.length) {
rootRouters = rootRouters.concat(component.router.rootRouter);
}
if (component.router.childRouter) {
var childRouter = component.router.childRouter;
for (var key in childRouter) {
if (Object.prototype.hasOwnProperty.call(childRouter, key))
childRouters[component.key] = childRouter[key];
}
}
}
7.3 主应用路由初始化
js// src/main.js
const { router, asyncRouter, asyncRouterConfigCenter } = initRouter();
store.commit('SET_ASYNCROUTER', { asyncRouter });
store.commit('SET_ASYNCROUTERCONFIGCENTER', { asyncRouterConfigCenter });
8. 打包构建流程
8.1 开发环境构建
- 启动构建脚本:
npm run dev - 生成模块导入文件:
prepare.createFile() - 监听配置变化:
prepare.watchConfig() - Webpack 别名配置: 模块名映射到实际路径
- 热重载: 修改代码自动刷新
8.2 生产环境构建
- 读取 childModule.json: 获取所有启用的模块
- 静态分析依赖: Webpack 分析模块依赖关系
- 代码分割: 按模块进行代码分割
- 生成最终包: 包含主应用和所有启用的子模块
8.3 模块下载与同步
js// build/blow.js - 子项目下载脚本
async function getAllProject() {
// 获取主项目分支和地址
const branchName = execSync('git symbolic-ref --short HEAD');
const stdout = execSync('git config --get remote.origin.url');
// 下载所有子模块到对应分支
for (let moduleName of Object.keys(allModules)) {
await gitClone(`${baseDir}/${moduleName}.git`, `./modules/${moduleName}`, {
checkout: branchName.trim()
});
}
}
9. 完整调用链路示例
以 getComponentByName('operationAudit', 'viewType1') 为例:
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. 调用 getComponentByName('operationAudit', 'viewType') │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. getModuleByName('operationAudit') │
│ → 从 modules['operationAudit'] 获取组件集合 │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. 返回 modules['operationAudit']['viewType'] │
│ → 对应的 Vue 组件 │
└─────────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. 组件在当前页面中正常使用 │
└─────────────────────────────────────────────────────────────────────────┘
10. 关键优势
| 特性 | 说明 |
|---|---|
| 模块解耦 | 子模块独立开发、测试、部署 |
| 按需加载 | 通过配置控制模块启用状态 |
| 跨模块通信 | 统一的组件调用和 Store 访问机制 |
| 开发友好 | 配置驱动,无需手动维护导入语句 |
| 版本同步 | 自动下载对应分支的子模块 |
| 错误处理 | 组件缺失时提供友好的错误提示 |
11. 最佳实践
11.1 组件导出规范
- 所有需要被其他模块调用的组件必须在
index.js的 components 对象中导出 - 组件命名应具有描述性,避免冲突
11.2 Store 命名规范
- Store 模块名应与组件 key 保持一致或具有明确关联
- 避免不同模块使用相同的 Store 名称
11.3 路由命名规范
- 路由名称应包含模块前缀,如
assetmanage_assetList - 避免路由名称冲突
11.4 模块依赖管理
- 尽量减少模块间的强依赖
- 使用
getComponentByName的flag参数处理可选依赖
这个架构设计使得 moyu 能够支持大型企业级应用的模块化开发,同时保持良好的开发体验和运行时性能。