微前端(二):使用federation实现模块共享

215 阅读5分钟

一、Webpack Federation 简介

Webpack Federation 是 Webpack 5 引入的一项功能,旨在实现模块之间的动态共享与加载。它允许不同的应用之间共享代码,独立开发和部署,从而降低开发和部署的耦合性。

二、配置方法

1. 远程应用(Remote)配置

远程应用是提供模块给其他应用使用的应用。以下是远程应用的 Webpack 配置示例:

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote', // 远程应用的名称
      filename: 'remoteEntry.js', // 暴露清单文件名
      exposes: {
        './Button': './src/components/Button', // 暴露 Button 组件
      },
      shared: {
        react: { singleton: true, requiredVersion: '18.0.0' }, // 共享依赖
        'react-dom': { singleton: true, requiredVersion: '18.0.0' },
      },
    }),
  ],
};

2. 宿主应用(Host)配置

宿主应用是消费远程应用模块的应用。以下是宿主应用的 Webpack 配置示例:


const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host', // 宿主应用的名称
      remotes: {
        remoteApp: 'remote@http://localhost:3001/remoteEntry.js', // 加载远程模块
      },
      shared: {
        react: { singleton: true, requiredVersion: '18.0.0' }, // 共享依赖
        'react-dom': { singleton: true, requiredVersion: '18.0.0' },
      },
    }),
  ],
};

3. 使用远程模块

在宿主应用中,可以通过动态导入的方式使用远程模块:

import React, { Suspense } from 'react';

// 动态加载远程模块
const RemoteButton = React.lazy(() => import('remoteApp/Button'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <h1>Module Federation Demo</h1>
      <RemoteButton />
    </Suspense>
  );
}

export default App;

三、方案的优缺点

优点

  1. 独立开发、启动:基于qiankun的微应用本地开发时必须要启动主应用,而 federation 可以通过加载远程的主/子应用资源而无需本地启动多个应用。
  2. 模块共享:多个应用可共享相同的依赖,避免重复加载,减少资源浪费。
  3. 跨栈组件:federation 最大的优势在于能实现 vue react 等跨栈组件之间的调用,在vue中能调用react组件,react中也能调用vue组件。
  4. 渐进式迁移:可以将老旧的单体应用逐步拆分为多个微服务模块,支持平滑迭代。

缺点

  1. 环境要求高:需要使用 Webpack 5,旧项目改造成本大。
  2. 代码封闭性问题:对于代码封闭性高的项目,不如 npm 复用方便。
  3. 共享模块的限制:被共享的库不能做 tree-shaking,可能导致打包体积增加。
  4. 运行时性能影响:运行时加载远程模块可能会对页面性能造成负面影响。
  5. 版本控制问题:运行时共享的模块版本控制较为复杂,需要谨慎处理。
  6. 远程模块类型支持问题:可能存在远程模块类型定义不完善的情况。

四、工程优化

1. 通过动态变量连接不同环境

配置 ModuleFederationPlugin 的 remotes 时可以使用 [变量名]的形式。

module.exports.generateRemotes = () => {
  return {
    UI: `UI@[window.appCommon]/remoteEntry.js`,
    NodeVendor: `NodeVendor@[window.appCommon]/vendor.js`,
    Utils: `Utils@[window.appCommon]/utils.js`,
    Bus: `Bus@[window.appCommon]/bus.js`,

    MonitorRoutes: `MonitorRoutes@[window.appMonitor]/monitorRoutes.js`,
    MonitorApi: `MonitorApi@[window.appMonitor]/monitorApi.js`,
    MonitorView: `MonitorView@[window.appMonitor]/monitorView.js`,

    OperationUI: `OperationUI@[window.appOperation]/operationUI.js`,
    OperationRoutes: `OperationRoutes@[window.appOperation]/operationRoutes.js`,
    OperationApi: `OperationApi@[window.appOperation]/operationApi.js`,
    OperationStore: `OperationStore@[window.appOperation]/operationStore.js`,
    OperationUtils: `OperationUtils@[window.appOperation]/operationUtils.js`,

    ShellUI: `ShellUI@[window.appShell]/ui.js`,
    ShellUtils: `ShellUtils@[window.appShell]/shellUtils.js`,
    ShellView: `ShellView@[window.appShell]/shellView.js`

  }
}

在应用的 index.js 中通过 fetch 调用接口或者 json 文件获取微应用的资源地址。例如,我是采用的 env.json 文件来配置资源地址。

fetch('/env.json').then(res => {
  if (res.status === 200) {
    res.json().then(data => {
      const { appCommon, appMonitor, appOperation, appShell } = data
      window.appCommon = appCommon
      window.appMonitor = appMonitor
      window.appOperation = appOperation
      window.appShell = appShell
      import('./bootstrap')  // 加载原初始化脚本
    })
  }
})
{
  "appCommon": "/app-common",
  "appMonitor": "/app-monitor",
  "appOperation": "/app-operation",
  "appShell": "/"
}

与 qiankun 方案对比

应用注册、加载

对于 federation 来说,应用注册就是 remote 资源的注册,加载就是 remote 资源的加载。

对于qiankun这种微前端框架来说,它需要开发者手动注册应用,通过监听 url 的变化,来调用应用的 mount/unmount 进行挂载或卸载。

应用隔离

应用隔离涉及到 js 与 css 隔离。 css 隔离2者采用的方案一般都是一致的,使用 scoped css 或者 css module 来实现。

JS 隔离方面2者就存在较大的差异了, qiankun 框架内部实现了沙箱用于应用之间的 window 隔离。而 federation 中的 remote 资源一般都是采用的模块化写法,打包之后通过立即执行函数、闭包能够避免模块之间的变量冲突,对于 window 对象没有预置的处置,需要开发者手动实现。

应用通信

qiankun中对于应用间的通信,提供了 initGlobalState API为主应用创建公共 state 并在 mount 钩子中注入给子应用,子应用通过 onGlobalStateChange API 监听 state 变化,通过 setGlobalState 修改公共state。

federation 需要用户自己实现事件总线及公共状态的管理,通过模块化方式注册为 remote 供微应用之间调用,可以通过单例模式、闭包实现微应用访问的是同一个状态或事件总线。

路由管理

qiankun 使用 single-spa 管理不同路由下的微应用 mount/unmount。

federation 关注于模块共享,未提供路由层面的管理,需要用户自己手动实现。通过 react-dom-router,vue-router 的实现原理,用户需要自己对 popstate、hashchange等事件进行拦截,以 mount/unmount 微应用。

五、总结

Webpack Federation 为微前端架构的实现提供了一种强大的解决方案。它通过动态加载模块和共享依赖,实现了应用之间的解耦和独立开发。然而,它也存在一些限制,如对环境的要求较高和运行时性能的潜在影响。在实际项目中,需要根据具体需求权衡其优缺点,合理选择是否使用 Webpack Federation 实现微前端架构。