模块联邦Module Federation中Vite Plugin和Vue Bridge(for Vue v3)的正确使用

761 阅读2分钟

鉴于官网针对通过模块联邦构建微前端应用文档不清晰,所以做的一点补充文章;此文章只针对微前端实现路由拆分,如何通信,如何css拆分可以另起文章介绍

1. 参考资料

官网: module-federation.io/zh/guide/st…

vite-plugin: module-federation.io/zh/guide/ba…

Vue Bridge: module-federation.io/zh/practice…

简单介绍一下其中2个插件的功能,vite-plugin在子应用中负责将我们的组件或者部分应用或者整个应用形成一个js暴露出去供其他模块联邦使用;在父应用中vite-plugin负责导入;Vue Bridge则是一个重新包装vue子应用的工具函数,如果你想导出的组件是单个组件,不涉及内部路由切换,则不需要包装,子应用重新包装后,子应用将具有自己的路由,自己进行内部组件渲染运行挂在,这对于想实现微前端来说很重要

2. 子应用配置

首先在src文件夹下新增一个export-app.ts,如果你的项目原本就是一个结构复杂的项目,想将其快速的拆分成一个个子应用,那么你可以新建一个exports文件夹,里面针对各个子应用各自新建xx-app.ts,快速通过路由快速的划分掉各个子系统;

export-app.ts

import App from './App.vue';
import router from './router';
// 如果原系统改造时只想暴露一部分的路由当做一个单独的子应用
// 在router/index.ts中新创建一个空路由,但是原路由守卫等配置可直接复用
// import { customRouter } from './router';
// customRouter.addRoutes([//   ...// ])
import './assets/main.css';
import { createPinia } from 'pinia';
import { createBridgeComponent } from '@module-federation/bridge-vue3';
export default createBridgeComponent({
  rootComponent: App,
  appOptions: ({ app }) => {
    // Optional: adding a plugin to the new Vue instance on the host application side
    app.use(createPinia());
    return { router };
    // return { customRouter };
  },
});

vite.config.ts

import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import vueDevTools from 'vite-plugin-vue-devtools';
import { federation } from '@module-federation/vite';
// https://vite.dev/config/
export default defineConfig({
  server: {
    origin: 'http://localhost:5174',
    port: 5174,
  },
  plugins: [
    vue(),
    vueJsx(),
    vueDevTools(),
    federation({
      name: 'remote1',
      filename: 'remoteEntry.js',
      exposes: {
        './exportapp': './src/export-app.ts',
        // 多个应用时自己添加
        // './appA': './src/export-app/xxx-app.ts',
        // './appB': './src/export-app/xxx-appB.ts',
      },
      shared: ['vue', 'vue-router'],
    }),
  ],
  build: {
    target: 'chrome89',
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
});

3. 主应用配置

vite.config.ts

import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import vueDevTools from 'vite-plugin-vue-devtools';
import federation from '@originjs/vite-plugin-federation';
// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
    vueDevTools(),
    federation({
      name: 'host',
      remotes: {
        remote1: import.meta.env.DEV
          ? 'http://localhost:5174/remoteEntry.js'
          : 'http://xxxxx/remoteEntry.js',
        // 其他应用打包发布后,替换为服务器地址
      },
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
});

router.ts

import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import { createRemoteComponent } from '@module-federation/bridge-vue3';
const Remote1 = createRemoteComponent({
  loader: () => import('remote1/exportapp'),
});
// const RemoteA = createRemoteComponent({
//   loader: () => import('remote1/exportappA'),
// });
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    {
      path: '/about/:pathMatch(.*)*',
      name: 'about',
      component: Remote1,
    },
    // {
    //   path: '/APath/:pathMatch(.*)*',
    //   name: 'about',
    //   component: RemoteA,
    // },
  ],
});
export default router;

最终效果

正确的加载了子应用,而且刷新也不会丢失状态