Complete module federation example with rspack

WHAT TO KNOW - Sep 1 - - Dev Community

A Complete Guide to Module Federation with Rspack: Building Modular and Scalable Web Applications

Introduction

The modern web is constantly evolving, demanding faster loading times, improved performance, and enhanced user experiences. Traditional monolithic frontend architectures are often unable to keep pace with these demands. Enter Module Federation, a groundbreaking feature introduced in Webpack 5, that enables building modular and scalable web applications by allowing separate applications to share code and resources.

However, the traditional Webpack often struggles with performance issues when dealing with large projects. Rspack emerges as a powerful alternative, leveraging the Rust programming language for speed and efficiency, offering a compelling solution for developers seeking a faster and more robust build system.

This article delves into the world of Module Federation, exploring its capabilities and demonstrating how to leverage it effectively with Rspack for building truly modular and scalable web applications.

Understanding Module Federation

Module Federation is a mechanism that allows breaking down a large application into smaller, independent modules or micro-frontends. These modules can be developed, deployed, and updated independently, resulting in a highly flexible and maintainable architecture.

Key Concepts:

  • Remote Containers: These are modules exposed by a separate application or build. They can be consumed by other applications through the import() function.
  • Shared Containers: These containers hold code that needs to be shared between multiple applications. They provide a single source of truth for shared dependencies, reducing redundancy and ensuring consistency across the application landscape.
  • Exposed Modules: These are modules or components specifically designed to be accessed from other applications. They are defined within a remoteEntry.js file and can be imported by other applications using the @scope/name convention.
  • Consumed Modules: These modules are imported from other applications. They leverage the exposed modules of remote containers, enabling code sharing and collaboration.

The Power of Rspack

Rspack, built with Rust, excels at handling large, complex projects with its unparalleled performance and efficiency. Here's why it's a perfect match for Module Federation:

  • Speed: Rspack offers significantly faster build times compared to Webpack, especially for large applications and projects with extensive dependencies.
  • Parallelism: Rspack leverages the multi-core capabilities of modern CPUs, enabling parallel processing and further accelerating the build process.
  • Stability: The Rust programming language ensures a high level of stability and reliability, making Rspack a robust choice for critical production environments.

Setting up the Development Environment

Before diving into code examples, let's set up the necessary environment. This example will utilize npm as the package manager.

1. Install Node.js and Rspack:

Ensure you have Node.js installed on your system. You can download the latest version from the official website: https://nodejs.org/

Install Rspack globally:

npm install -g rspack
Enter fullscreen mode Exit fullscreen mode

2. Project Structure:

We will create a simple project structure with two applications, app1 and app2. Each application will have its own package.json file and a src directory containing the source code.

my-app
├── app1
│   └── src
│       └── index.js
└── app2
    └── src
        └── index.js
Enter fullscreen mode Exit fullscreen mode

Building a Simple Module Federation Example

1. app1 - The Remote Application:

  • package.json:
{
  "name": "app1",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "rspack serve --config rspack.config.js",
    "build": "rspack build --config rspack.config.js"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • rspack.config.js:
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  devServer: {
    port: 3000,
  },
  plugins: [
    {
      apply(compiler) {
        compiler.hooks.afterResolvers.tap("ModuleFederationPlugin", (compiler) => {
          compiler.options.output.publicPath = "http://localhost:3000/";
          const ModuleFederationPlugin = require("rspack/lib/plugins/ModuleFederationPlugin");
          compiler.options.plugins.push(
            new ModuleFederationPlugin({
              name: "app1",
              filename: "remoteEntry.js",
              exposes: {
                "./Counter": "./src/Counter",
              },
              shared: {
                react: {
                  singleton: true,
                  requiredVersion: false,
                },
                "react-dom": {
                  singleton: true,
                  requiredVersion: false,
                },
              },
            }),
          );
        });
      },
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode
  • src/Counter.js:
import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
<div>
 <h1>
  Counter: {count}
 </h1>
 <button =="" onclick="{()">
  setCount(count + 1)}&gt;Increment
 </button>
</div>
);
};

export default Counter;
Enter fullscreen mode Exit fullscreen mode
  • src/index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<counter>
</counter>
);
Enter fullscreen mode Exit fullscreen mode

2. app2 - The Consuming Application:

  • package.json:
{
  "name": "app2",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "rspack serve --config rspack.config.js",
    "build": "rspack build --config rspack.config.js"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • rspack.config.js:
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  devServer: {
    port: 3001,
  },
  plugins: [
    {
      apply(compiler) {
        compiler.hooks.afterResolvers.tap("ModuleFederationPlugin", (compiler) =&gt; {
          compiler.options.output.publicPath = "http://localhost:3001/";
          const ModuleFederationPlugin = require("rspack/lib/plugins/ModuleFederationPlugin");
          compiler.options.plugins.push(
            new ModuleFederationPlugin({
              name: "app2",
              filename: "remoteEntry.js",
              remotes: {
                app1: "app1@http://localhost:3000/remoteEntry.js",
              },
              shared: {
                react: {
                  singleton: true,
                  requiredVersion: false,
                },
                "react-dom": {
                  singleton: true,
                  requiredVersion: false,
                },
              },
            }),
          );
        });
      },
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode
  • src/index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));

const App = () =&gt; {
  const [Counter, setCounter] = React.useState(null);

  React.useEffect(() =&gt; {
    const loadCounter = async () =&gt; {
      const { default: CounterComponent } = await import("app1/Counter");
      setCounter(CounterComponent);
    };
    loadCounter();
  }, []);

  return (
<div>
 <h1>
  App 2
 </h1>
 {Counter &amp;&amp;
 <counter>
 </counter>
 }
</div>
);
};

root.render(
<app>
</app>
);
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code

  1. app1 (Remote Application):

    • rspack.config.js: This configures the Module Federation plugin for app1.
      • name: Defines the name of the remote application, which will be used by consuming applications to reference it.
      • filename: Specifies the name of the remoteEntry.js file containing the exposed modules.
      • exposes: Lists the modules exposed by the remote application. In this case, we expose Counter from ./src/Counter.js.
      • shared: Defines dependencies that need to be shared between app1 and consuming applications. This ensures consistency and avoids duplicate installations.
  2. app2 (Consuming Application):

    • rspack.config.js: Configures Module Federation for app2.
      • remotes: Specifies the remote applications to be consumed. The key is the name of the remote application (app1), and the value is the URL of its remoteEntry.js file.
      • shared: Defines shared dependencies to ensure consistency with app1.
  3. src/index.js (Consuming Application):

    • Import the remote module dynamically: The import() function dynamically loads the Counter component from app1 at runtime.
    • Display the imported component: Once the remote module is loaded, the Counter component is rendered within app2.

Running the Example

  1. Start app1:
   cd my-app/app1
   npm run start
Enter fullscreen mode Exit fullscreen mode
  1. Start app2:
   cd my-app/app2
   npm run start
Enter fullscreen mode Exit fullscreen mode

Now, you should have two applications running on different ports. Navigate to http://localhost:3001/ in your browser to see app2, which will display the counter component from app1.

Building and Deploying Applications

Once the development is complete, you can build the applications for deployment using the npm run build command within each application directory. This will generate the optimized bundles for production.

Advanced Concepts and Best Practices

  • Micro-Frontends: Module Federation excels at building complex applications composed of multiple independent micro-frontends, each with its own development team and deployment schedule.
  • Code Sharing and Collaboration: Shared dependencies and the ability to import modules from other applications promote code sharing and collaboration between development teams.
  • Lazy Loading and Performance: By dynamically loading remote modules only when needed, Module Federation can significantly improve the initial loading time and performance of your application.
  • Dynamic Remotes: The remotes property in the Module Federation plugin can be dynamic, allowing you to load different remote applications based on user roles, preferences, or other conditions.
  • Webpack Compatibility: Although primarily associated with Rspack, Module Federation is also fully supported by Webpack 5, providing developers with flexibility in their build tools.

Conclusion

Module Federation with Rspack is a powerful combination that unlocks a new level of modularity, scalability, and performance for building modern web applications. By leveraging its capabilities, you can create applications composed of independent modules, promote code sharing, and achieve optimal performance through lazy loading.

This guide provides a solid foundation for exploring Module Federation and Rspack. As you delve deeper, consider the advanced concepts and best practices outlined above to maximize the benefits of this powerful technology.

Remember that this example is a simple illustration of Module Federation's capabilities. With a bit of creativity and a deep understanding of the concepts, you can unlock its true potential and build truly exceptional web applications!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .