🚀微前端与模块联邦的深度结合(基于vue+vite)

1️⃣ 项目背景与挑战

随着业务规模的扩大,传统的单体应用架构逐渐暴露出开发效率低、协作难、部署风险高等问题。为了解决这些痛点,我们团队决定引入微前端架构,并结合模块联邦技术,实现真正意义上的解耦与资源共享。

2️⃣ 技术选型

2.1 核心框架

  • Vue 3:主流的前端框架,支持 Composition API
  • Vite:极速开发与构建工具
  • @micro-zoe/micro-app:微前端框架,基于 Web Components
  • @originjs/vite-plugin-federation:Vite 生态下的模块联邦实现
  • Pinia:新一代状态管理
  • pnpm:高效包管理器

3️⃣ 架构设计

3.1 整体架构

js 复制代码
admin-micro-app/
├── apps/
│   └── main/           # 主应用
├── micro/
│   ├── modules/        # 联邦模块(共享库/组件)
│   ├── login/          # 登录子应用
│   └── child/          # 业务子应用
└── scripts/            # 自定义脚手架

3.2 模块联邦的实现

共享模块的配置

在micro/modules目录中,我们通过@originjs/vite-plugin-federation插件配置共享模块:

js 复制代码
// scripts/models/vite/plugins/provider.ts
export const ModuleFederationProviderPlugin = () => {
  const exposes = moduleFederationUtils.getExposes();
  return federation({
    name: 'modules',
    filename: 'remoteEntry.js',
    exposes
  });
};

共享模块的暴露配置通过moduleFederationUtils.getExposes()自动生成,它会扫描micro/modules/src目录下的所有文件,并生成相应的暴露配置:

js 复制代码
// scripts/utils/module-federation.ts
export const getExposes = () => { 
  const dir = pathUtils.getMicroModulesDir();
  let exposes = moduleFederationUtils.filePathsToExposes(
    moduleFederationUtils.resolveCodeFiles(pathUtils.getMicroModulesDir())
  );
  moduleFederationUtils.createAbsolutePath(exposes, dir);
  return exposes;
}

getExposes的返回值大概长这个样子

消费端的配置

主应用和子应用作为消费端,通过以下配置引用共享模块:

js 复制代码
// scripts/models/vite/plugins/consumer.ts
export const ModuleFederationConsumerPlugin = () => {
  const ports = fsx.readJSONSync(pathUtils.getPortsJsonPath());
  const external = process.env.NODE_ENV === 'dev' 
    ? `new Promise(resolve=>resolve('http://localhost:${ports['@micro/modules']}/assets/remoteEntry.js'))` 
    : `new Promise(resolve=>resolve('/v2/micro/modules/assets/remoteEntry.js'))`;
    
  return federation({
    name: 'layout',
    filename: 'remoteEntry.js',
    remotes: {
      modules: {
        external,
        externalType: "promise"
      }
    }
  });
};

消费端配置之后即可在主或子应用里面通过这种形式引入

js 复制代码
// 引用共享的Vue
import { createApp } from 'modules/vue';
// 引用共享的Vue Router
import { createRouter } from 'modules/vue-router';
// 引用共享的组件
import { SomeComponent } from 'modules/@shared/components';

同时为了提供TS提示的能力需要在当前应用的根目录新建一个tsconfig.json

js 复制代码
{
  "extends": "@shared/tsconfig",
  "compilerOptions": {
    "paths": {
      "modules/*": ["../../micro/modules/src/*"],
      "shared/*": ["../../shared/*"],
    }
  }
}

3.3 微前端的实现

主应用配置

js 复制代码
// apps/main/src/main.ts
microApp.start({
  'router-mode': 'native',
  prefetchDelay: 1000,
  preFetchApps: MicroUtils.getMicroPreFetch(),
  plugins: {}
});

子应用加载

js 复制代码
<!-- apps/main/src/components/MicroApp/index.vue -->
<template>
  <MicroAppDevMode 
    v-if="envUtils.isDev()" 
    :app-name="props.appName" 
    :init-data="props.initData" 
    @mounted="microAppMounted" 
  />
  <MicroAppProdMode 
    v-else 
    :app-name="props.appName" 
    :init-data="props.initData" 
  />
</template>

4️⃣ 开发流程

4.1 开发环境启动

开发环境启动时,只会启动共享模块服务和主应用:

js 复制代码
/ scripts/models/commands/dev.ts
async devRoot() {
  const modules = ProjectFactory.create(
    EProjectType.Provider, 
    pathUtils.getAppProjectDir('@micro/modules')
  );
  await modules.microModulePreview();
  const mainApp = ProjectFactory.create(
    EProjectType.Consumer, 
    pathUtils.getAppProjectDir('@apps/main')
  );
  mainApp.serve();
}

在开发环境下,子应用不会在启动时全部加载,而是采用按需加载的方式

子应用启动流程:

js 复制代码
// apps/main/src/components/MicroApp/MicroAppDevMode.vue
onMounted(() => {
  if (!props.appName) return null;
  // 检查子应用是否已经启动
  if (getAllApps().includes(props.appName)) {
    loading.value = false;
    return null;
  }
  // 发送启动子应用的请求
  import.meta.hot.send({ 
    type: EAppDevSocketType.StartApp, 
    data: props.appName 
  });
  // 监听子应用启动完成事件
  import.meta.hot.on(EAppDevSocketType.AppStarted, startAppChange);
});

vite写一个服务器插件去监听客户端启动子应用的请求

子应用启动流程总结

  • 主应用启动时,只启动共享模块服务
  • 当用户访问子应用路由时,触发子应用加载
  • 主应用通过 WebSocket 发送启动子应用的请求
  • 子应用开发服务器启动,并通知主应用
  • 主应用收到通知后,加载子应用组件
  • 子应用组件通过 micro-app 加载子应用内容

构建流程

共享模块的构建
js 复制代码
// scripts/models/vite/configuration/provider-build.ts
export const ProviderBuildConfiguration = () => {
  return defineConfig({
    plugins: ViteBase.plugins?.concat([
      ModuleFederationProviderPlugin(),
      topLevelAwait({
        promiseExportName: "__tla",
        promiseImportName: i => `__tla_${i}`
      })
    ]),
    build: {
      minify: 'terser',
      terserOptions: {
        mangle: true,
        compress: {
          drop_console: true
        }
      }
    }
  });
};
主子应用的构建
js 复制代码
export const ConsumerBuildConfiguration = () => {
  return defineConfig({
    plugins: ViteBase.plugins?.concat([
      RedirectVueImports(),
      ModuleFederationConsumerPlugin()
    ]),
    optimizeDeps: {
      exclude: ['vue']
    },
    build: ViteBase.build,
    mode: 'production',
    css: ViteBase.css,
    resolve: ViteBase.resolve
  });
};

5️⃣ 实践效果

5.1 开发效率提升

  • 开发环境启动时间缩短 70%
  • 构建效率提升 50%
  • 代码复用率提升
  • 团队协作更顺畅

5.2 资源利用优化

  • 按需加载减少不必要的资源消耗
  • 共享模块减少重复代码
  • 构建产物优化,减少包体积

结语

如果想尝试微前端又想把公用资源进行抽离可以尝试一下这套解决方案,具体代码参考github.com/chaorenluo/...

相关推荐
前端小趴菜051 小时前
React-React.memo-props比较机制
前端·javascript·react.js
亿牛云爬虫专家2 小时前
Kubernetes下的分布式采集系统设计与实战:趋势监测失效引发的架构进化
分布式·python·架构·kubernetes·爬虫代理·监测·采集
摸鱼仙人~2 小时前
styled-components:现代React样式解决方案
前端·react.js·前端框架
kangkang-2 小时前
PC端基于SpringBoot架构控制无人机(三):系统架构设计
java·架构·无人机
sasaraku.3 小时前
serviceWorker缓存资源
前端
RadiumAg4 小时前
记一道有趣的面试题
前端·javascript
yangzhi_emo4 小时前
ES6笔记2
开发语言·前端·javascript
yanlele4 小时前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
ai小鬼头4 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
中微子5 小时前
React状态管理最佳实践
前端