不使用微前端:如何实现主应用和子模块动态管理与通信实现

1. 架构概述

moyu 采用 主应用 + 子模块 的微前端架构模式,通过 MoyuConfig 全局配置中心实现模块间的解耦和通信。整个架构支持动态模块加载、按需启用、跨模块组件调用等功能。

2. 项目结构

bash 复制代码
web_master/
├── src/                    # 主应用代码
├── modules/                # 子模块目录
│   ├── moyu-systemset-page/    # 系统设置模块
│   ├── moyu-assetmanage-page/  # 资产管理模块
│   └── ...                 # 其他子模块
├── static/js/config.js     # 全局配置中心
├── childModule.json        # 模块配置清单
├── src/development_extension.js # 开发环境模块导入文件
└── build/                  # 构建配置

3. 子项目创建流程

3.1 模块目录结构

每个子模块遵循统一的目录结构:

bash 复制代码
modules/moyu-xxx-page/
├── src/
│   ├── index.js            # 模块入口文件
│   ├── router.js           # 路由配置
│   ├── components/         # 组件目录
│   └── pages/              # 页面目录
└── package.json            # 模块包配置

3.2 模块入口文件 (index.js)

javascript 复制代码
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 复制代码
json{
  "sicap-systemset-page": {
    "open": true,
    "desc": "系统设置"
  },
  "sicap-assetmanage-page": {
    "open": false,
    "desc": "资产管理中心"
  }
}

自动化生成导入文件

ini 复制代码
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('子项目扩展已生成');
}

生成的导入文件

arduino 复制代码
js// src/development_extension.js
import 'sicap-systemset-page'
// import 'sicap-assetmanage-page'
import 'sicap-operationaudit-page'

4.2 Webpack 别名配置

javascript 复制代码
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 调用其他模块组件

javascript 复制代码
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 实现原理

javascript 复制代码
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 模块数据存储结构

css 复制代码
js// static/js/config.js 内部变量
var modules = {};  // 存储格式: { 'systemSet': { sysOverview: Component, ... }, 'operationAudit': { viewType1: Component, ... } }

6. Store 通信机制

6.1 子模块 Store 注册

子模块在 index.js 中可以提供 store 配置:

php 复制代码
js// 子模块 index.js
const store = {
  assetStore: {
    state: { /* ... */ },
    mutations: { /* ... */ },
    actions: { /* ... */ }
  }
};
​
pushComponent({
  key: 'assetmanage',
  components: components,
  router: router,
  store: store  // 提供 store 配置
});

6.2 Store 自动注册到主应用

javascript 复制代码
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

ini 复制代码
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 实例中,因此可以在任意组件中访问:

kotlin 复制代码
js// 在任何组件中
this.$store.state.assetStore.someState
this.$store.dispatch('assetStore/someAction')

7. 路由集成机制

7.1 子模块路由配置

arduino 复制代码
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 路由自动收集

ini 复制代码
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 主应用路由初始化

scss 复制代码
js// src/main.js
const { router, asyncRouter, asyncRouterConfigCenter } = initRouter();
store.commit('SET_ASYNCROUTER', { asyncRouter });
store.commit('SET_ASYNCROUTERCONFIGCENTER', { asyncRouterConfigCenter });

8. 打包构建流程

8.1 开发环境构建

  1. 启动构建脚本 : npm run dev
  2. 生成模块导入文件 : prepare.createFile()
  3. 监听配置变化 : prepare.watchConfig()
  4. Webpack 别名配置: 模块名映射到实际路径
  5. 热重载: 修改代码自动刷新

8.2 生产环境构建

  1. 读取 childModule.json: 获取所有启用的模块
  2. 静态分析依赖: Webpack 分析模块依赖关系
  3. 代码分割: 按模块进行代码分割
  4. 生成最终包: 包含主应用和所有启用的子模块

8.3 模块下载与同步

javascript 复制代码
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') 为例:

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│  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 模块依赖管理

  • 尽量减少模块间的强依赖
  • 使用 getComponentByNameflag 参数处理可选依赖

这个架构设计使得 moyu 能够支持大型企业级应用的模块化开发,同时保持良好的开发体验和运行时性能。

相关推荐
兆子龙2 小时前
前端工程师转型 AI Agent 工程师:后端能力补全指南
前端·javascript
长安11082 小时前
web后端----HTTP协议与浏览器F12
前端·网络协议·http
前端大波2 小时前
Web Vitals 与前端性能监控实战
前端·javascript
AlienZHOU3 小时前
从零开始,跟着写一个产品级 Coding Agent
前端
RichardZhiLi3 小时前
大前端全栈实践课程:章节二(前端工程化建设)
前端
毕设源码-赖学姐3 小时前
【开题答辩全过程】以 基于VUE的环保网站设计为例,包含答辩的问题和答案
前端·javascript·vue.js
ZTrainWilliams3 小时前
swagger-mcp-toolkit 让 AI编辑器 更快“读懂并调用”你的接口
前端·后端·mcp
伊步沁心3 小时前
深入 useEffect:为什么 cleanup 总比 setup 先跑?顺手手写节流防抖 Hook
前端