Skip to content

Module Federation ​

Webpack 5 内置模块联邦,支持在运行时加载远程模块。

核心概念 ​

Host(消费者) ←→ Remote(提供者)
      ↓
共享依赖 shared(react, vue, lodash 等)
Host(消费者) ←→ Remote(提供者)
      ↓
共享依赖 shared(react, vue, lodash 等)

配置示例 ​

Remote(提供者) ​

js
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote_app',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
        './store': './src/store',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true },
      },
    }),
  ],
}
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote_app',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
        './store': './src/store',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true },
      },
    }),
  ],
}

Host(消费者) ​

js
// webpack.config.js
new ModuleFederationPlugin({
  name: 'host_app',
  remotes: {
    remote_app: 'remote_app@http://localhost:3001/remoteEntry.js',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
})
// webpack.config.js
new ModuleFederationPlugin({
  name: 'host_app',
  remotes: {
    remote_app: 'remote_app@http://localhost:3001/remoteEntry.js',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
})

使用远程组件 ​

jsx
// React 中动态加载
const RemoteButton = React.lazy(() => import('remote_app/Button'))

function App() {
  return (
    <Suspense fallback="Loading...">
      <RemoteButton />
    </Suspense>
  )
}
// React 中动态加载
const RemoteButton = React.lazy(() => import('remote_app/Button'))

function App() {
  return (
    <Suspense fallback="Loading...">
      <RemoteButton />
    </Suspense>
  )
}
js
// 普通 JS 中使用
const container = await import('remote_app/store')
// 普通 JS 中使用
const container = await import('remote_app/store')

配置项详解 ​

配置项类型说明
namestring模块名称,唯一标识
filenamestringremoteEntry 文件名
exposesobject暴露给外部的模块
remotesobject引用的远程模块
sharedobject共享依赖配置

shared 配置 ​

js
shared: {
  react: {
    singleton: true,     // 全局单例(避免多实例)
    requiredVersion: '^18.0.0',
    eager: true,          // 立即加载
    strictVersion: false, // 版本严格匹配
  },
  // 或者使用数组
  shared: ['react', 'react-dom'],
}
shared: {
  react: {
    singleton: true,     // 全局单例(避免多实例)
    requiredVersion: '^18.0.0',
    eager: true,          // 立即加载
    strictVersion: false, // 版本严格匹配
  },
  // 或者使用数组
  shared: ['react', 'react-dom'],
}

Module Federation 与 qiankun 对比 ​

维度Module Federationqiankun
架构去中心化中心化(基座模式)
粒度模块级共享应用级加载
隔离无沙箱有沙箱
共享依赖内置共享机制需手动配置
构建工具Webpack 5+不限
学习成本中等低

实战技巧 ​

1. 共享第三方库 ​

js
// 所有应用配置相同的 shared,避免重复加载
shared: {
  react: { singleton: true, eager: true },
  'react-dom': { singleton: true },
  'react-router-dom': { singleton: true },
}
// 所有应用配置相同的 shared,避免重复加载
shared: {
  react: { singleton: true, eager: true },
  'react-dom': { singleton: true },
  'react-router-dom': { singleton: true },
}

2. 避免版本冲突 ​

js
shared: {
  antd: {
    singleton: true,
    requiredVersion: '^5.0.0',
    strictVersion: false, // 允许小版本差异
  },
}
shared: {
  antd: {
    singleton: true,
    requiredVersion: '^5.0.0',
    strictVersion: false, // 允许小版本差异
  },
}

3. 动态远程加载 ​

js
// 运行时动态加载远程模块
function loadRemote(url, scope, module) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = url
    script.onload = async () => {
      await __webpack_init_sharing__('default')
      const container = window[scope]
      await container.init(__webpack_share_scopes__.default)
      const factory = await container.get(module)
      resolve(factory())
    }
    script.onerror = reject
    document.head.appendChild(script)
  })
}
// 运行时动态加载远程模块
function loadRemote(url, scope, module) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = url
    script.onload = async () => {
      await __webpack_init_sharing__('default')
      const container = window[scope]
      await container.init(__webpack_share_scopes__.default)
      const factory = await container.get(module)
      resolve(factory())
    }
    script.onerror = reject
    document.head.appendChild(script)
  })
}