微前端架构实战:Webpack Module Federation跨应用共享与路由配置

微前端架构通过将大型单体前端应用拆分为多个独立部署的子应用,解决了前端工程化中的团队协作和持续交付效率问题。Webpack 5引入的Module Federation(模块联邦)原生支持跨应用共享模块,无需引入qiankun等框架即可实现微前端。对于Vue3生态和React框架混用的技术栈,Module Federation提供了一套统一的跨应用组件共享方案。

Module Federation核心概念与Host端配置

Module Federation定义了两个角色:Host(容器应用)消费远程模块,Remote(远程应用)暴露模块。每个应用可以同时扮演两种角色。核心配置项包括name(应用唯一标识)、remotes(依赖的远程应用)、exposes(暴露的模块)和shared(共享依赖)。

// webpack.config.js (Host应用)
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;

module.exports = {
  output: {
    publicPath: "http://localhost:3000/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        userApp: "userApp@http://localhost:3001/remoteEntry.js",
        orderApp: "orderApp@http://localhost:3002/remoteEntry.js",
      },
      shared: {
        vue: { singleton: true, requiredVersion: deps.vue },
        "vue-router": { singleton: true, requiredVersion: deps["vue-router"] },
        pinia: { singleton: true, requiredVersion: deps.pinia },
      },
    }),
  ],
};

shared配置中的singleton: true确保Vue实例全局唯一,避免多个子应用各自加载Vue后组件通信失败。requiredVersion指定版本要求,版本不兼容时WebpackPlugin会抛出警告。publicPath必须配置为绝对路径,因为远程应用加载的异步chunk需要知道资源基础路径。如果是HTTPS域名部署,publicPath也要对应切换。

Remote端模块暴露与组件共享

子应用通过exposes暴露组件、路由配置或工具函数给Host消费。同时通过shared声明与Host共享的依赖,避免重复加载。

// webpack.config.js (userApp远程应用)
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;

module.exports = {
  output: {
    publicPath: "http://localhost:3001/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "userApp",
      filename: "remoteEntry.js",
      exposes: {
        "./UserList": "./src/components/UserList.vue",
        "./UserRouter": "./src/router/userRouter.js",
        "./useUserStore": "./src/stores/userStore.js",
        "./UserTypes": "./src/types/user.ts",
      },
      shared: {
        vue: { singleton: true, requiredVersion: deps.vue },
        "vue-router": { singleton: true, requiredVersion: deps["vue-router"] },
        pinia: { singleton: true, requiredVersion: deps.pinia },
      },
    }),
  ],
};

exposes的key是消费时使用的模块路径(userApp/UserList),value是子应用内的实际文件路径。暴露路由模块可以实现子应用自治路由注入Host路由表。暴露Pinia store使得Host和子应用共享同一状态实例。filename指定远程入口文件名,通常命名为remoteEntry.js,Host通过该文件发现子应用暴露的所有模块。

跨应用路由配置与动态加载

微前端路由有两种方案:路由集中式(Host管理所有路由)和路由分布式(子应用注册各自路由)。推荐分布式路由方案,子应用通过暴露路由配置数组由Host动态合并:

// userApp: src/router/userRouter.js
export default [
  {
    path: "/users",
    name: "UserList",
    component: () => import("userApp/UserList"),
    meta: { title: "用户管理", icon: "user" }
  },
  {
    path: "/users/:id",
    name: "UserDetail",
    component: () => import("userApp/UserDetail"),
    meta: { title: "用户详情", hidden: true }
  }
];

// Host: src/router/index.js
import { createRouter, createWebHistory } from "vue-router";

const routes = [
  {
    path: "/",
    component: () => import("@/views/Layout.vue"),
    children: [
      { path: "", redirect: "/dashboard" },
      { 
        path: "dashboard", 
        name: "Dashboard",
        component: () => import("@/views/Dashboard.vue")
      }
    ]
  }
];

// 动态加载子应用路由
async function loadRemoteRoutes() {
  const userRouter = (await import("userApp/UserRouter")).default;
  const orderRouter = (await import("orderApp/OrderRouter")).default;
  
  routes[0].children.push(...userRouter, ...orderRouter);
}

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export { router, loadRemoteRoutes };

loadRemoteRoutes函数在Host应用初始化时调用,通过import()动态加载远程路由模块。子应用路由以子路由形式挂载到Layout下方,共享侧边栏导航和顶栏。需要处理路由404兜底:将catch-all路由放在动态路由注册之后,否则子路由会被catch-all提前拦截。路由守卫中需要判断远程模块是否已加载,避免在模块未就绪时跳转到子应用页面导致白屏。

共享依赖版本冲突处理与降级策略

多个子应用依赖不同版本的同一库时,shared配置需要开启singleton降级或allowVersionMismatch。降级策略确保功能可用但可能存在兼容性风险:

// 允许版本不匹配(谨慎使用)
shared: {
  vue: {
    singleton: true,
    requiredVersion: "^3.3.0",
    strictVersion: false,
    // strictVersion为false时,版本不匹配仅警告不报错
  },
  axios: {
    singleton: false,  // 允许多版本共存
    requiredVersion: deps.axios,
  }
}

// 类型声明共享(TypeScript项目)
// types.d.ts
declare module "userApp/UserList" {
  import { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

对于无法共享的复杂依赖(如UI组件库的不同大版本),使用singleton: false让各自加载独立实例。TypeScript项目需要为远程模块声明类型文件,否则编译期类型检查报错。生产环境需配置runtime error boundary捕获远程模块加载失败的异常,降级展示本地兜底组件或错误提示。remoteEntry.js文件需配置CDN缓存策略,同时通过版本号或hash值实现缓存失效。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/wei-qian-duan-jia-gou-shi-zhan-webpackmodulefederation-kua/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐