# Webpack module federation-a new choice for micro front-end architecture

Webpack Module Federation is a revolutionary feature introduced in Webpack 5 that completely changes the way micro-frontend architecture is implemented. Module Federation allows different web applications (or micro-frontend applications) to dynamically share code at runtime without physical sharing in the traditional packaging or publishing process. This means that each micro-application can be independently developed, built, and deployed, while also being able to easily share components, libraries, and even business logic.

### Basic Concepts

* Container application (Container): As the host of the micro-frontend architecture, responsible for loading and coordinating various micro-applications.
    
* Remote application (Remote): An independent micro-application that can expose its own modules to other applications for use, or consume modules from other applications.
    

### Implementation steps

#### 1\. Container application configuration

In the `webpack.config.js` of the container application, use `ModuleFederationPlugin` to declare the source of the remote micro-application.

```javascript
// webpack.config.js (Container)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...Other Configuration
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
        app2: 'app2@http://localhost:3002/remoteEntry.js',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};
```

Here, the `remotes` field specifies the name of the remote micro-app and its remote entry file URL. The `shared` configuration indicates which modules should be shared as singletons, such as React and ReactDOM, to avoid duplicate loading.

#### 2\. Remote application configuration

In each remote application's `webpack.config.js`, `ModuleFederationPlugin` is also used, but this time to expose its own modules.

```javascript
// webpack.config.js (Remote App1)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...Other Configuration
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      exposes: {
        './MyComponent': './src/components/MyComponent',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};
```

The `exposes` field defines which modules will be exposed externally. In this example, the MyComponent component can be imported and used from the container application or other micro applications.

#### 3\. Consuming remote modules

In the container application or another remote application, you can directly import the remotely exposed modules.

```jsx
// In a component of Container or another Remote App
import MyComponent from 'app1/MyComponent';

function App() {
  return (
    <div>
      <h1>Container App</h1>
      <MyComponent />
    </div>
  );
}

export default App;
```

### Advantages

* Independent development and deployment: Each micro-application can be independently developed, built, and deployed, which improves development efficiency and deployment flexibility.
    
* On-demand loading: Only when a module is actually used, the corresponding remote code will be loaded, which optimizes the first screen loading time and overall performance.
    
* Version management and isolation: Each micro-application can freely upgrade its dependencies to avoid version conflicts.
    
* Easy to maintain and expand: The loose coupling feature of module federation makes it easy and quick to add or remove micro-applications.
    

Webpack module federation provides an efficient and flexible solution for the development and maintenance of modern Web applications by simplifying the code sharing mechanism in the micro-frontend architecture.

### Actual combat case: Building a simple micro-frontend application

Let's use a simple example to demonstrate how to use Webpack module federation to build two micro-applications: a container application and a remote application.

#### 1\. Create a container application

First, create a new React application as a container application:

```bash
npx create-react-app container-app
cd container-app
```

Install webpack and webpack-cli (note that since create-react-app already includes Webpack, it is usually not necessary to install it separately. This is only for demonstration purposes):

```bash
npm install webpack webpack-cli --save-dev
```

Modify package.json and add a startup script to configure Webpack:

```json
"scripts": {
  "start": "webpack serve --config webpack.config.js",
  // ...
}
```

Create `webpack.config.js` and configure `Module Federation Plugin`:

```javascript
// webpack.config.js (Container App)
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...Other Configuration
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new ModuleFederationPlugin({
      name: 'containerApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3010/remoteEntry.js',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};
```

#### 2\. Create a remote application

Create a remote application in another directory:

```bash
npx create-react-app remote-app
cd remote-app
```

Similarly, modify `package.json`, add the startup script, and install `webpack` and `webpack-cli` (for example only):

```bash
npm install webpack webpack-cli --save-dev
```

Configure the Module Federation Plugin in remote-app's webpack.config.js to expose the component:

```javascript
// webpack.config.js (Remote App)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // ...Other Configuration
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './MyWidget': './src/MyWidget',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};
```

Create the `MyWidget.js` component in the `remote-app/src` directory:

```jsx
// MyWidget.js
import React from 'react';

const MyWidget = () => {
  return <h1>Hello from Remote App!</h1>;
};

export default MyWidget;
```

#### 3\. Container application consumes remote components

Go back to container-app and import remote components where needed:

```jsx
// container-app/src/App.js
import React from 'react';
import MyWidget from 'remoteApp/MyWidget';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <MyWidget />
      </header>
    </div>
  );
}

export default App;
```

#### 4\. Start the application

Start two applications separately:

```bash
# In the remote application directory
npm start --port 3010
# In the container application directory
npm start
```

Now, when you access the container application in the browser, you should see that the components from the remote application are successfully loaded and displayed.

### Advanced Usage and Best Practices

#### 1\. Dynamic loading and lazy loading

In a real project, you may want to dynamically load remote applications based on user behavior or specific conditions. Webpack module federation supports asynchronous loading, just use the `import()` function when importing.

```jsx
// container-app/src/App.js
import React, { lazy, Suspense } from 'react';
const MyWidget = lazy(() => import('remoteApp/MyWidget'));

function App() {
  return (
    <div className="App">
      <Suspense fallback={<div>Loading...</div>}>
        <MyWidget />
      </Suspense>
    </div>
  );
}

export default App;
```

In this way, the MyWidget component will be loaded on demand when needed, improving the first screen loading speed.

#### 2\. Version management and dependency management

In the micro-frontend architecture, ensuring the compatibility of dependency versions between different applications is key. Using the `shared` configuration of `ModuleFederationPlugin`, you can specify the version range and loading strategy of the shared module (for example, `singleton`, `strictVersion`, etc.).

```javascript
// webpack.config.js
new ModuleFederationPlugin({
  // ...
  shared: {
    react: { version: '^17.0.0', singleton: true },
    'react-dom': { version: '^17.0.0', singleton: true },
  },
}),
```

#### 3\. Routing Integration

In the micro-frontend architecture, routing management is an important component. You can use libraries like `react-router-dom`, combined with `Microfrontends-Router` or custom solutions to implement cross-application routing jumps.

```jsx
// container-app/src/Routes.js
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import App1 from './App1';
import App2 from './App2';

function Routes() {
  return (
    <Router>
      <Switch>
        <Route path="/app1" component={App1} />
        <Route path="/app2" component={App2} />
      </Switch>
    </Router>
  );
}

export default Routes;
```

#### 4\. State management

For shared state requirements, you can use state management libraries such as Redux, MobX, or Context API, or state management libraries designed specifically for micro-frontends such as `single-spa-redux`, `qiankun`'s `store` solution, etc.

#### 5\. Shared services and public libraries

In addition to components, you can also share services and public libraries. For example, create a dedicated remote application to provide API services, or share a public HTTP library.

```javascript
// webpack.config.js (Remote App for Services)
new ModuleFederationPlugin({
  name: 'services',
  filename: 'remoteEntry.js',
  exposes: {
    './ApiService': './src/services/ApiService',
    './HttpLibrary': './src/libs/http-library',
  },
  shared: {
    // ...Other shared libraries
  },
}),
```

#### 6\. Error handling and logging

In order to ensure the stable operation of the micro-frontend application, global error capture and logging need to be implemented. You can use `window.onerror`, `try...catch` statements, or use a dedicated logging library such as log4js.

```jsx
// container-app/src/index.js
window.onerror = function (errorMessage, fileName, lineNumber, columnNumber, error) {
	// Record error information
	console.error(errorMessage, fileName, lineNumber, columnNumber, error);
	// ...other processing logic
	return true; // Prevent browser default error handling
};
```
