模块联邦:更快的微前端方式!

什么是模块联邦

在前端项目中,不同团队之间的业务模块可能有耦合,比如A团队的页面里有一个富文本模块(组件),而B团队 的页面恰好也需要使用这个富文本模块。

传统模式下,B团队只能去抄A团队的代码,把这个组件放到自己的项目了。

为了解决不同服务之间组件共享的问题,Webpack 的模块联邦功能应用而生。借助模块联邦,可以在B服务运行时,动态加载A服务暴露的模块。

Vite通过vite-plugin-federation 插件也可以实现

模块联邦的核心优势

结合模块联邦的原理与功能,它具有以下显著优势:

  • 独立部署:各个应用可独立开发、构建和部署,互不依赖。
  • 运行时共享:模块在运行时动态加载,无需在构建阶段打包进主应用。
  • 版本控制:支持为共享模块指定版本,便于多项目之间的依赖管理。
  • 减少重复代码:多个应用可共享功能模块,避免重复实现。

相比 qiankun 等微前端方案,模块联邦在灵活性上更具优势。qiankun 侧重于加载整个子应用,而模块联邦支持按需加载特定模块,粒度更细,使用更灵活。

如何实现

以Vite+Vue为例。

假设我们有 Vite 应用:

  • ui-library:组件提供者,暴露一个按钮组件 SharedButton.vue
  • main-app:主应用,运行时动态加载 ui-library 中的按钮组件来使用。
js 复制代码
shixiaoshi-demo/
│
├── ui-library/     # 远程模块:暴露组件
│   ├── src/SharedButton.vue
│   ├── vite.config.js
│
└── main-app/       # 主项目:加载远程组件
    ├── src/App.vue
    ├── vite.config.js

1、安装模块联邦插件

我们需要使用 vite-plugin-federation 插件。

css 复制代码
npm install vite-plugin-federation --save-dev

2、暴露组件

  • ui-library/src/SharedButton.vue
js 复制代码
<template>
  <button class="shared-btn">我是共享按钮</button>
  </template>

  <script setup>
  </script>

  <style scoped>
  .shared-btn {
  padding: 10px;
  background-color: teal;
  color: white;
  border: none;
  border-radius: 5px;
}
</style>
  • ui-library/vite.config.js
js 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    vue(),
    federation({
      name: 'ui_library',
      filename: 'remoteEntry.js',
      exposes: {
        './SharedButton': './src/SharedButton.vue',
      },
      shared: ['vue'],
    }),
  ],
  build: {
    target: 'esnext',
    minify: false,
    cssCodeSplit: false,
  },
});

启动服务

ini 复制代码
npm run dev -- --port=5001

暴露地址:http://localhost:5001/assets/remoteEntry.js

3、加载远程组件

  • main-app/vite.config.js
js 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    vue(),
    federation({
      name: 'main_app',
      remotes: {
        ui_library: 'http://localhost:5001/assets/remoteEntry.js',
      },
      shared: ['vue'],
    }),
  ],
});
  • main-app/src/App.vue
js 复制代码
<template>
  <div>
  <h1>主应用</h1>
  <RemoteButton />
  </div>
  </template>

  <script setup>
  import { defineAsyncComponent } from 'vue';

// 从 ui-library 中动态加载共享组件
const RemoteButton = defineAsyncComponent(() => import('ui_library/SharedButton'));
</script>
  • 启动主应用
arduino 复制代码
npm run dev

访问 http://localhost:5173,你会看到来自 ui-library 的按钮组件成功渲染!

主应用与子组件关系图

rust 复制代码
main-app (主应用)
   |
   | -- 在运行时访问 -->
   |    http://localhost:5001/assets/remoteEntry.js
   |
   | -- 加载 -->
   |    ui-library 中暴露的 SharedButton.vue
   |
   | -- 使用 -->
   |    作为主应用的本地组件一样渲染

缺陷

在实际使用中,模块联邦也存在一些问题。

以我们项目开发中遇到的问题为例,当应用 A 加载应用 B 中的某个组件时,B 的代码执行环境会落在 A 的作用域下。这会带来一些潜在影响:

  • 依赖冲突问题:如果 B 服务依赖特定版本的 Vue,而 A 服务的 Vue 版本不同,实际运行时组件会使用 A 的 Vue 实例,可能导致行为异常,特别是在响应式系统、生命周期等细节方面。
  • 状态失效问题:如果 B 中的组件依赖 Vuex 或 Pinia 等状态管理工具,运行时由于上下文切换,可能无法正确获取或响应状态,导致 store 在远程加载后失效。
  • 构建配置差异问题 :例如,B 使用了自动引入插件(如 unplugin-auto-import),可以在代码中直接使用 refcomputed 等 Vue API 而无需显式导入;但若 A 没有启用相同插件,加载 B 的组件时会因缺少这些 API 导致运行错误,除非显式引入相关函数。

因此,在使用模块联邦时,建议主应用与远程模块在框架版本、构建工具、插件配置等方面尽可能保持一致,或通过适配包装进行隔离,以减少因上下文差异带来的问题。

相关推荐
dog shit7 分钟前
web第十次课后作业--Mybatis的增删改查
android·前端·mybatis
我有一只臭臭7 分钟前
el-tabs 切换时数据不更新的问题
前端·vue.js
七灵微11 分钟前
【前端】工具链一本通
前端
Nueuis1 小时前
微信小程序前端面经
前端·微信小程序·小程序
_r0bin_4 小时前
前端面试准备-7
开发语言·前端·javascript·fetch·跨域·class
IT瘾君4 小时前
JavaWeb:前端工程化-Vue
前端·javascript·vue.js
potender4 小时前
前端框架Vue
前端·vue.js·前端框架
站在风口的猪11084 小时前
《前端面试题:CSS预处理器(Sass、Less等)》
前端·css·html·less·css3·sass·html5
程序员的世界你不懂5 小时前
(9)-Fiddler抓包-Fiddler如何设置捕获Https会话
前端·https·fiddler
MoFe15 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore