阿里云主机折上折
  • 微信号
Current Site:Index > Module Federation application

Module Federation application

Author:Chuan Chen 阅读数:45199人阅读 分类: 构建工具

Basic Concepts of Module Federation

Module Federation is a revolutionary feature introduced in Webpack 5 that enables code sharing between different Webpack builds. This mechanism breaks the limitations of traditional micro-frontend solutions, allowing applications to dynamically load modules exposed by other applications, achieving true code sharing and runtime integration. Unlike traditional methods like DLL or externals, Module Federation does not require dependency relationships to be determined at build time but dynamically decides them at runtime.

Core concepts include:

  • Host: The application that consumes modules from other applications
  • Remote: The application that exposes modules for use by other applications
  • Shared: Shared dependencies that can be used by multiple applications

How Module Federation Works

Module Federation relies on Webpack's Container Interface and Overridable Runtime. When Module Federation is configured, Webpack generates a special entry file for each application, which contains the implementation of the application's container.

The workflow is roughly as follows:

  1. The Remote application determines the modules to expose during the build process.
  2. The Host application asynchronously loads the Remote application's container at runtime.
  3. The container manages module loading and shared dependencies.
  4. When the Host needs a module, it requests it from the Remote container.
// Webpack configuration for the Remote application
new ModuleFederationPlugin({
  name: 'app1',
  filename: 'remoteEntry.js',
  exposes: {
    './Button': './src/components/Button',
  },
  shared: ['react', 'react-dom']
});

// Webpack configuration for the Host application
new ModuleFederationPlugin({
  name: 'app2',
  remotes: {
    app1: 'app1@http://localhost:3001/remoteEntry.js',
  },
  shared: ['react', 'react-dom']
});

Configuring Module Federation

Module Federation is primarily configured using the ModuleFederationPlugin. Here is a complete configuration example:

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...other Webpack configurations
  plugins: [
    new ModuleFederationPlugin({
      name: 'dashboard',  // Application name, must be unique
      filename: 'remoteEntry.js',  // Generated entry file name
      exposes: {  // Modules exposed to other applications
        './DashboardApp': './src/bootstrap',
        './Widget': './src/components/Widget'
      },
      remotes: {  // Referenced remote applications
        marketing: 'marketing@http://localhost:8081/remoteEntry.js',
        auth: 'auth@http://localhost:8082/remoteEntry.js'
      },
      shared: {  // Shared dependencies
        react: { singleton: true, eager: true },
        'react-dom': { singleton: true, eager: true },
        '@material-ui/core': { singleton: true },
        'react-router-dom': { singleton: true }
      }
    })
  ]
};

Options for shared dependencies:

  • singleton: Ensures only one version is loaded
  • eager: Loads immediately instead of asynchronously
  • requiredVersion: Specifies the required version
  • strictVersion: Throws an error if versions do not match

Practical Application Scenarios

Micro-Frontend Architecture

Module Federation is particularly suitable for micro-frontend scenarios, where different teams can independently develop and deploy their applications and then combine them at runtime:

// Main application loading components from sub-applications
import React, { lazy, Suspense } from 'react';

const ProductList = lazy(() => import('products/ProductList'));
const ShoppingCart = lazy(() => import('cart/ShoppingCart'));

function App() {
  return (
    <div>
      <Suspense fallback="Loading Products...">
        <ProductList />
      </Suspense>
      <Suspense fallback="Loading Cart...">
        <ShoppingCart />
      </Suspense>
    </div>
  );
}

Plugin System

Building an extensible plugin system where the main application provides core functionality and plugins offer additional capabilities:

// Main application dynamically loads plugins
async function loadPlugin(pluginName) {
  const plugin = await import(`plugins/${pluginName}`);
  plugin.initialize();
}

// Plugin provider exposes interfaces
exposes: {
  './analyticsPlugin': './src/plugins/analytics'
}

Cross-Project Shared Component Library

Multiple projects share UI components to avoid duplication and version inconsistencies:

// Component library project exposes components
exposes: {
  './Button': './src/components/Button',
  './Modal': './src/components/Modal'
}

// Consumer project
const Button = React.lazy(() => import('ui-library/Button'));

Advanced Usage and Techniques

Dynamic Remote Configuration

Remote addresses can be determined at runtime rather than hardcoded in the configuration:

// Dynamically set remote
const getRemote = (remoteName) => {
  return `${remoteName}@${window.ENV.REMOTES[remoteName]}/remoteEntry.js`;
};

// Use dynamic import
const RemoteComponent = React.lazy(
  () => import('dynamicRemote/Component')
);

Shared State Management

Multiple applications sharing a Redux store or other states:

// Main application exposes store
exposes: {
  './store': './src/store'
}

// Sub-application uses shared store
const store = await import('hostApp/store');
store.dispatch({ type: 'UPDATE' });

Version Conflict Resolution

When shared dependency versions do not match, advanced configurations can resolve the issue:

shared: {
  lodash: {
    requiredVersion: '^4.17.0',
    singleton: true,
    version: '4.17.21',
    strictVersion: false
  }
}

Performance Optimization Strategies

Preloading Remotes

Use Webpack's prefetch/preload features to load Remotes in advance:

// Add magic comments in the code
const ProductList = lazy(
  () => import(/* webpackPrefetch: true */ 'products/ProductList')
);

On-Demand Loading

Load only necessary modules to reduce initial loading time:

// Dynamically load non-critical modules
function loadPaymentModule() {
  return import('payment/PaymentGateway');
}

Shared Dependency Optimization

Configure shared dependencies properly to avoid duplicate loading:

shared: {
  react: {
    singleton: true,
    requiredVersion: '^17.0.0'
  },
  'react-dom': {
    singleton: true,
    requiredVersion: '^17.0.0'
  }
}

Common Issues and Solutions

Circular Dependency Handling

When two applications reference each other, circular dependencies may occur. Solutions include:

  1. Refactor code to extract common parts into a third application.
  2. Use shared dependencies instead of direct references.
  3. Introduce an intermediate layer for decoupling.

Hot Module Replacement in Development Environment

Configure the development server for cross-origin support and hot module replacement:

devServer: {
  headers: {
    "Access-Control-Allow-Origin": "*"
  },
  hot: true,
  liveReload: false
}

Production Environment Deployment

For production environments, consider CDN, version control, and caching strategies:

output: {
  publicPath: 'https://cdn.example.com/app1/',
  filename: '[name].[contenthash].js'
}

Security Considerations

Origin Verification

Only load Remotes from trusted sources:

remotes: {
  trustedApp: `promise new Promise(resolve => {
    if (isTrustedOrigin(location.origin)) {
      resolve(trustedApp@${trustedUrl}/remoteEntry.js);
    }
  })`
}

Content Security Policy

Configure appropriate CSP policies:

Content-Security-Policy: 
  script-src 'self' https://trusted-cdn.com;
  connect-src 'self' https://api.example.com;

Sandbox Isolation

Use iframes or Web Workers to isolate third-party code:

// Load Remote in an iframe
const iframe = document.createElement('iframe');
iframe.src = 'https://remote-app.com';
document.body.appendChild(iframe);

Comparison with Other Technologies

Comparison with iframes

Advantages:

  • Better performance with no additional DOM overhead
  • Shared dependencies reduce duplicate loading
  • Tighter integration with shared routing and state

Disadvantages:

  • Less security than iframe isolation
  • Potential CSS and global variable conflicts

Comparison with Single SPA

Module Federation:

  • Finer-grained code sharing (module level)
  • No need for centralized routing configuration
  • Native Webpack support, simpler builds

Single SPA:

  • Better framework agnosticism
  • More comprehensive lifecycle management
  • Better suited for combining fully independent applications

Comparison with NPM Packages

Module Federation advantages:

  • Dynamic updates at runtime without redeployment
  • On-demand loading reduces initial bundle size
  • Supports version negotiation and sharing

NPM package advantages:

  • More mature development tool support
  • Better TypeScript type system support
  • More thorough build-time optimizations

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.