JavaScript Module Federation: Sharing Code Between WordPress Plugins

Module Federation (Webpack 5) allows JavaScript modules to be loaded from a remote URL at runtime — meaning one WordPress plugin can expose a React component, utility library, or data store, and another plugin can consume it without bundling a copy. This solves the classic WordPress “multiple jQuery / multiple React” problem for plugin ecosystems where multiple plugins need to share a common library.

Problem: Multiple WordPress plugins developed by the same team share JavaScript utility code — date formatters, API wrappers, UI component libraries — but there is no mechanism to share this code between independent plugin builds without duplicating it or publishing to npm.

Solution: Use Module Federation (webpack 5) to expose shared modules from one plugin's build and consume them in another at runtime. Configure the ModuleFederationPlugin in each plugin's webpack.config.js, declare shared singletons (React, lodash), and load remote modules dynamically with import('remote/Component'). No npm publishing is required.


The Webpack configs below set up a host plugin that consumes a shared component library from a remote plugin, configure shared dependencies (React, ReactDOM) so they are loaded only once, and show how to lazy-load the remote module in a React component.


// ── plugin-a/webpack.config.js (the REMOTE — exposes components) ──────────
const { ModuleFederationPlugin } = require( 'webpack' ).container;

module.exports = {
    entry: './src/index.js',
    output: {
        publicPath: 'auto',   // Webpack infers the URL from the script src
        filename:   'remoteEntry.js',
    },
    plugins: [
        new ModuleFederationPlugin( {
            name:     'PluginA',
            filename: 'remoteEntry.js',
            exposes:  {
                './DatePicker':    './src/components/DatePicker',
                './useSettings':   './src/hooks/useSettings',
                './api':           './src/utils/api',
            },
            shared: {
                react:     { singleton: true, requiredVersion: '^18.0.0' },
                'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
                '@wordpress/data': { singleton: true },
            },
        } ),
    ],
};


// ── plugin-b/webpack.config.js (the HOST — consumes from Plugin A) ────────
const { ModuleFederationPlugin } = require( 'webpack' ).container;

module.exports = {
    plugins: [
        new ModuleFederationPlugin( {
            name:    'PluginB',
            remotes: {
                // PluginA exposes its remoteEntry.js from its plugin URL
                PluginA: 'PluginA@[pluginARemoteUrl]/remoteEntry.js',
            },
            shared: {
                react:     { singleton: true, requiredVersion: '^18.0.0' },
                'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
            },
        } ),
    ],
};

// ── plugin-b/src/AdminPage.js — lazy load the remote component ────────────
import { lazy, Suspense } from 'react';

// Dynamically import the DatePicker exposed by Plugin A
const DatePicker = lazy( () => import( 'PluginA/DatePicker' ) );

export function AdminPage() {
    return (
        Loading picker… }>
             console.log( date ) } />
        
    );
}

// ── PHP: inject the remote URL so Webpack can resolve it at runtime ───────
// plugin-b/plugin-b.php
add_action( 'admin_enqueue_scripts', function () {
    wp_localize_script( 'plugin-b-admin', 'pluginBConfig', [
        'pluginARemoteUrl' => plugins_url( '', WP_PLUGIN_DIR . '/plugin-a/dist' ),
    ] );
} );


NOTE: Module Federation's singleton: true ensures only one copy of React loads across all federated modules — but this only works if both plugins use the same major React version; if one plugin requires React 17 and another requires React 18, the singleton constraint will cause a runtime error; audit all plugin React version requirements before deploying Module Federation in a multi-plugin environment.