🚀微前端与模块联邦的深度结合(基于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/...

相关推荐
tuokuac19 分钟前
nginx配置前端请求转发到指定的后端ip
前端·tcp/ip·nginx
程序员爱钓鱼23 分钟前
Go语言实战案例-开发一个Markdown转HTML工具
前端·后端·go
万少1 小时前
鸿蒙创新赛 HarmonyOS 6.0.0(20) 关键特性汇总
前端
Aczone281 小时前
硬件(五) 存储、ARM 架构与指令系统
arm开发·嵌入式硬件·架构
闲看云起1 小时前
从 GPT 到 LLaMA:解密 LLM 的核心架构——Decoder-Only 模型
gpt·架构·llama
还有多远.1 小时前
jsBridge接入流程
前端·javascript·vue.js·react.js
蝶恋舞者1 小时前
web 网页数据传输处理过程
前端
非凡ghost1 小时前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求
吃饭最爱2 小时前
html的基础知识
前端·html
我没想到原来他们都是一堆坏人2 小时前
(未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
java·前端·python