基于模块联邦打通多系统的探索

前言

模块联邦(Module Federation)是一种前端技术,允许不同构建的应用之间共享模块。类似于微服务架构在前端的体现。通过模块联邦,可以实现跨应用的代码共享,这对于构建微前端架构非常有用。

  1. 远程应用(Remote Application):暴露模块的应用,其他应用可以引用这些模块。
  2. 宿主应用(Host Application):引用远程应用模块的应用。

背景

前不久,临时接到个需求,想要实现串联多个系统,能够实现自由切换又能分开单独运行,也要数据共享也要相互引用(实在是既要又要还要,十分符合当下产品的设计思维)。无奈谁还不是头牛马,只能绞尽脑汁造了。 好在平常的知识储备还是有的,monorepo、微前端、模块联邦顿时打入脑海,由于之前微前端和monorepo都搞过了,勉为其难的选择了模块联邦。于是开始着手打开各种搜索引擎各种Ai开始研究,没成想,越研究还觉得越对味哈哈哈哈!😀😀😀😀😀😀

一、架构设计

1、系统概念: 每个独立的前端项目都为一个独立系统;

2、系统互通: 能相互跳转(实现SSO单点登录),系统能访问其他系统的组件;

3、插件: 类似工具类,可提供各个系统/组件使用,可考虑独立库方式实现;

二、场景及目标

(一) 应用场景

系统太多且割裂,假设当前有多个系统主系统、系统2、...、系统N,且分别由多个不同的代码仓库管理

(二) 目标效果

  • 多个系统既能集成运行又能单独运行;
  • 数据共享和统一登录;
  • 系统互认,各系统之间能够相互引用;

三、技术实现

(一) 技术选型

  1. 技术栈 vue3+pinia+vite
  2. ui框架 element-plus + Geeker Admin
  3. 核心插件 @originjs/vite-plugin-federation( 模块联邦 )

(二) 项目创建

首先以Geeker Admin 为脚手架模板(远程拉取),分别创建三个系统项目main-system、system2和system3,并分别安装依赖

shell 复制代码
pnpm install

接着在每个项目中分别安装上模块联邦插件

注:建议安装1.3.6版本,最新版本有 样式丢失的问题

shell 复制代码
pnpm install @originjs/vite-plugin-federation@1.3.6 -D

安装完事后,可以分别运行,看看效果

注:路由模式建议使用history模式, 刷新404问题可以通过nginx配置解决

shell 复制代码
pnpm dev
  • 主系统
  • 系统2
  • 系统3

(三) 具体实现

首先,先确定功能的host(应用者)跟remote(提供者)。

比如在主系统上开发设计一个系统菜单组件,供所有系统使用,那么在这个功能上主系统就是remote,其他系统都是host。

  • 将系统菜单组件暴露出去:
ts 复制代码
// main-system 中 vite.config.ts
import federation from "@originjs/vite-plugin-federation";
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  ...
  return {
    ...
      plugins:[
        ...,
        federation({
          name: "remote_main", // 远程模块
          filename: "remoteEntry.js", // 远程资源路径
          exposes: { // 暴露的组件
            "./SysNavComp": "./src/remote/components/SysNav/index.vue"
          },
          shared: ["vue", "vue-router", "pinia"] // 共享的依赖
        }),
      ]
}
  • 其他系统需要跟这个资源包建立远程连接
js 复制代码
// 系统2、3 的vite.config.ts
import federation from "@originjs/vite-plugin-federation";
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  ...
  return {
    ...
      plugins:[
        ...,
        federation({
          remotes: { // 远程模块跟地址
            remote_main: "主系统服务地址/remoteEntry.js"
          },
          shared: ["vue", "vue-router", "pinia"] // 共享的依赖
        }),
      ]
}
  • 完事后就可以在页面中引入
js 复制代码
<template>
  <el-config-provider :locale="locale" :size="assemblySize" :button="buttonConfig">
    <SysNavComp />
    <router-view></router-view>
  </el-config-provider>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from "vue";
  ...
const SysNavComp = defineAsyncComponent(() => import("remote_main/SysNavComp"));
</script>

大致效果如下,实现各个系统既能独立运行又能自由切换:

(四) 功能拓展

  • 主系统除了能将功能暴露出去供其他系统使用外,其他系统也能提供组件、页面或者方法供主系统使用。
js 复制代码
// 子系统 vite.config,ts
import federation from "@originjs/vite-plugin-federation";
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  ...
  return {
    ...
      plugins:[
        ...,
        federation({
          remotes: { // 远程模块跟地址
            remote_main: "主系统服务地址/assets/remoteEntry.js"
          },
          exposes: {
            "./HomePage": "./src/views/home/index.vue",
            "./UseProTablePage": "./src/views/proTable/useProTable/index.vue"
          },
          shared: ["vue", "vue-router", "pinia"] // 共享的依赖
        }),
      ]
}

在主系统中应用

js 复制代码
// 主系统 vite.config.ts
import federation from "@originjs/vite-plugin-federation";
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  ...
  return {
    ...
      plugins:[
        ...,
        federation({
          name: "remote_main", // 远程模块
          filename: "remoteEntry.js", // 远程资源路径
          exposes: { // 暴露的组件
            "./SysNavComp": "./src/remote/components/SysNav/index.vue"
          },
          remotes: {
            remote_main1: "系统2服务地址/remoteEntry.js",
          },
          shared: ["vue", "vue-router", "pinia"] // 共享的依赖
        }),
      ]
}
js 复制代码
<template>
  <div class="home card">
    <HomePageComp>
      <p>hello world</p>
    </HomePageComp>
    <UseProTablePageComp />
  </div>
</template>

<script setup lang="ts" name="home">
import { defineAsyncComponent } from "vue";
const HomePageComp = defineAsyncComponent(() => import("remote_main1/HomePage"));
const UseProTablePageComp = defineAsyncComponent(() => import("remote_main1/UseProTablePage"));
</script>

<style scoped lang="scss">
@import "./index";
</style>

大致效果

  • 后续可以将一些公共的js代码抽离出来做成插件包,供各个系统使用。

(五) 测试效果

本地体验地址: http://192.168.120.178:8882/home

四、数据共享

  • 本地缓存
  • 跨窗口通讯
  • ......

!!!前提条件得符合浏览器的同源协议。


五、打包部署

将各个系统分别进行打包

shell 复制代码
pnpm build:pro

除了主项目,其他子项目打包后的bundle文件夹名可以约定以 'sys+数字' 的形式区分。

打包并整合完后的bundle结构,外层是主系统,sys2是系统2,sys3是系统3

这里用的nginx本地部署

nginx 复制代码
#nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

  # 日志格式配置
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';


    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0; 
    keepalive_timeout  65;

    #gzip  on;
    gzip on;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;


    server {
        #配置nginx启动的端口,服务器名字(本地localhost)
        listen       8882; 
        server_name  192.168.120.178;
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers X-Requested-With;
        add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

        root  html;
        index  index.html index.htm;
        #charset koi8-r;

        # upstream backend_server {
        #     server mock.mengxuegu.com/mock;
        # }
        # access_log  logs/host.access.log  main;

        location /api/ {
            # rewrite ^/api/(.*) /v1/$1 break;
            proxy_pass https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e/;
            proxy_set_header Host $http_host; #后台可以获取到完整的ip+端口号
            proxy_set_header X-Real-IP $remote_addr; #后台可以获取到用户访问的真实ip地址
        }
        #配置启动nginx后打开的静态文件html页面
        #静态文件一般是前端项目打包之后的dist文件(该文件下的html文件为启动页面)
        location / {
            # root  html;
            # index  index.html index.htm;
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Allow-Credentials' 'true';
            error_page 404 = @purge_cache;
            try_files $uri $uri/ /index.html;
        }

        location /sys2/ {
            root  html;
            index  /sys2/index.html index.htm;
            proxy_set_header Host $http_host; #后台可以获取到完整的ip+端口号
            proxy_set_header X-Real-IP $remote_addr; #后台可以获取到用户访问的真实ip地址
            error_page 404 = @purge_cache;
            try_files $uri $uri/ /sys2/index.html;
        }
        location /sys3/ {
            root  html;
            index  /sys3/index.html index.htm;
            proxy_set_header Host $http_host; #后台可以获取到完整的ip+端口号
            proxy_set_header X-Real-IP $remote_addr; #后台可以获取到用户访问的真实ip地址
            error_page 404 = @purge_cache;
            try_files $uri $uri/ /sys3/index.html;
        }
        

        location = /favicon.ico {
            log_not_found off;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax