English

Unlock the power of micro-frontends with JavaScript Module Federation in Webpack 5. Learn how to build scalable, maintainable, and independent web applications.

JavaScript Module Federation with Webpack 5: A Comprehensive Guide to Micro-frontends

In the ever-evolving landscape of web development, building large and complex applications can be a daunting task. Traditional monolithic architectures often lead to increased development time, deployment bottlenecks, and challenges in maintaining code quality. Micro-frontends have emerged as a powerful architectural pattern to address these challenges, allowing teams to build and deploy independent parts of a larger web application. One of the most promising technologies for implementing micro-frontends is JavaScript Module Federation, introduced in Webpack 5.

What are Micro-frontends?

Micro-frontends are an architectural style where a frontend app is decomposed into smaller, independent units, which can be developed, tested, and deployed autonomously by different teams. Each micro-frontend is responsible for a specific business domain or feature, and they are composed together at runtime to form the complete user interface.

Think of it like a company: instead of having one giant development team, you have multiple smaller teams focusing on specific areas. Each team can work independently, allowing for faster development cycles and easier maintenance. Consider a large e-commerce platform like Amazon; different teams might manage the product catalog, shopping cart, checkout process, and user account management. These could all be independent micro-frontends.

Benefits of Micro-frontends:

Challenges of Micro-frontends:

What is JavaScript Module Federation?

JavaScript Module Federation is a Webpack 5 feature that allows you to share code between separately compiled JavaScript applications at runtime. It enables you to expose parts of your application as "modules" that can be consumed by other applications, without needing to publish to a central repository like npm.

Think of Module Federation as a way to create a federated ecosystem of applications, where each application can contribute its own functionality and consume functionality from other applications. This eliminates the need for build-time dependencies and allows for truly independent deployments.

For example, a design system team can expose UI components as modules, and different application teams can consume these components directly from the design system application, without needing to install them as npm packages. When the design system team updates the components, the changes are automatically reflected in all consuming applications.

Key Concepts in Module Federation:

Setting up Module Federation with Webpack 5: A Practical Guide

Let's walk through a practical example of setting up Module Federation with Webpack 5. We'll create two simple applications: a Host application and a Remote application. The Remote application will expose a component, and the Host application will consume it.

1. Project Setup

Create two separate directories for your applications: `host` and `remote`.

```bash mkdir host remote cd host npm init -y npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev npm install react react-dom cd ../remote npm init -y npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev npm install react react-dom ```

2. Remote Application Configuration

In the `remote` directory, create the following files:

src/index.js:

```javascript import React from 'react'; import ReactDOM from 'react-dom/client'; import RemoteComponent from './RemoteComponent'; const App = () => (

Remote Application

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

src/RemoteComponent.jsx:

```javascript import React from 'react'; const RemoteComponent = () => (

This is a Remote Component!

Rendered from the Remote Application.

); export default RemoteComponent; ```

webpack.config.js:

```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const path = require('path'); module.exports = { entry: './src/index', mode: 'development', devServer: { port: 3001, static: { directory: path.join(__dirname, 'dist'), }, }, output: { publicPath: 'auto', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'remote', filename: 'remoteEntry.js', exposes: { './RemoteComponent': './src/RemoteComponent', }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { extensions: ['.js', '.jsx'], }, }; ```

Create `public/index.html` with basic HTML structure. Important is `

`

3. Host Application Configuration

In the `host` directory, create the following files:

  • `src/index.js`: Entry point for the application.
  • `webpack.config.js`: Webpack configuration file.

src/index.js:

```javascript import React, { Suspense } from 'react'; import ReactDOM from 'react-dom/client'; const RemoteComponent = React.lazy(() => import('remote/RemoteComponent')); const App = () => (

Host Application

Loading Remote Component...
}>
); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(); ```

webpack.config.js:

```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const path = require('path'); module.exports = { entry: './src/index', mode: 'development', devServer: { port: 3000, static: { directory: path.join(__dirname, 'dist'), }, }, output: { publicPath: 'auto', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'host', remotes: { remote: 'remote@http://localhost:3001/remoteEntry.js', }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { extensions: ['.js', '.jsx'], }, }; ```

Create `public/index.html` with basic HTML structure (similar to remote app). Important is `

`

4. Install Babel

In both the `host` and `remote` directories, install Babel dependencies:

```bash npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader ```

5. Run the Applications

In both the `host` and `remote` directories, add the following script to `package.json`:

```json "scripts": { "start": "webpack serve" } ```

Now, start both applications:

```bash cd remote npm start cd ../host npm start ```

Open your browser and navigate to `http://localhost:3000`. You should see the Host application with the Remote Component rendered inside it.

Explanation of Key Configuration Options:

Advanced Module Federation Techniques

Module Federation offers many advanced features that can help you build even more sophisticated micro-frontend architectures.

Dynamic Remotes

Instead of hardcoding the URLs of remote applications in the Webpack configuration, you can load them dynamically at runtime. This allows you to easily update the location of remote applications without having to rebuild the host application.

For example, you could store the URLs of remote applications in a configuration file or a database and load them dynamically using JavaScript.

```javascript // In webpack.config.js remotes: { remote: `promise new Promise(resolve => { const urlParams = new URLSearchParams(window.location.search); const remoteUrl = urlParams.get('remote'); // Assume remoteUrl is something like 'http://localhost:3001/remoteEntry.js' const script = document.createElement('script'); script.src = remoteUrl; script.onload = () => { // the key of module federation is that the remote app is // available using the name in the remote resolve(window.remote); }; document.head.appendChild(script); })`, }, ```

Now you can load the host app with a query parameter `?remote=http://localhost:3001/remoteEntry.js`

Versioned Shared Modules

Module Federation can automatically handle versioning and deduplication of shared modules to ensure that only one compatible version of each module is loaded. This is especially important when dealing with large and complex applications that have many dependencies.

You can specify the version range of each shared module in the Webpack configuration.

```javascript // In webpack.config.js shared: { react: { singleton: true, eager: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }, }, ```

Custom Module Loaders

Module Federation allows you to define custom module loaders that can be used to load modules from different sources or in different formats. This can be useful for loading modules from a CDN or from a custom module registry.

Sharing State between Micro-frontends

One of the challenges of micro-frontend architectures is sharing state between different micro-frontends. There are several approaches you can take to address this challenge:

Best Practices for Implementing Micro-frontends with Module Federation

Here are some best practices to keep in mind when implementing micro-frontends with Module Federation:

Real-World Examples of Module Federation in Action

While specific case studies are often confidential, here are some generalized scenarios where Module Federation can be incredibly useful:

Conclusion

JavaScript Module Federation in Webpack 5 provides a powerful and flexible way to build micro-frontend architectures. It allows you to share code between separately compiled JavaScript applications at runtime, enabling independent deployments, technology diversity, and improved team autonomy. By following the best practices outlined in this guide, you can leverage Module Federation to build scalable, maintainable, and innovative web applications.

The future of frontend development is undoubtedly leaning towards modular and distributed architectures. Module Federation provides a crucial tool for building these modern systems, enabling teams to create complex applications with greater speed, flexibility, and resilience. As the technology matures, we can expect to see even more innovative use cases and best practices emerge.