Setting up remote components and services with webpack module federation

Motivation

Setting up Federated remote application

  1. Install webpack 5 in your application
npm i -D webpack webpack-cli webpack-dev-server
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;
module.exports = {
// Where files should be sent once they are bundled
entry: './src/index.ts',
output: {
publicPath: '/remote',
path: path.join(__dirname, '/dist'),
filename: 'index.[hash].js',
},
// webpack 5 comes with devServer which loads in development mode
devServer: {
host: '0.0.0.0',
port: 3000,
open: true,
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx']
},
// Rules of how webpack will take our files, complie & bundle them for the browser
module: {
rules: [
{
test: /\.(ts|tsx)?$/,
loader: 'ts-loader',
exclude: [/node_modules/],
}
],
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new ModuleFederationPlugin({
name: 'remote',
library: { type: 'var', name: 'remote' },
filename: 'remoteEntry.js',
shared: {
'react': { singleton: true, eager: true } ,
'react-dom': { singleton: true, eager: true }
}
}),
],
};
{
"name": "remote_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@types/react": "^17.0.30",
"@types/react-dom": "^17.0.11",
"@types/react-router-config": "^5.0.3",
"@types/react-router-dom": "^5.3.2"
"html-webpack-plugin": "^5.4.0",
"ts-loader": "^9.2.6",
"typescript": "^4.5.2",
"webpack": "^5.58.1",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2"
}
}
import('./bootstrap');
export {};
import React from 'react';
import ReactDOM from 'react-dom';

Container App Setup

  1. Install webpack 5 in your application
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;
module.exports = {
// Where files should be sent once they are bundled
entry: './src/index.ts',
output: {
publicPath: '/host',
path: path.join(__dirname, '/dist'),
filename: 'index.[hash].js',
},
// webpack 5 comes with devServer which loads in development mode
devServer: {
host: '0.0.0.0',
port: 3001,
open: true,
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx']
},
// Rules of how webpack will take our files, complie & bundle them for the browser
module: {
rules: [
{
test: /\.(ts|tsx)?$/,
loader: 'ts-loader',
exclude: [/node_modules/],
}
],
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new ModuleFederationPlugin({
name: "host",
remoteType: 'var',
remotes: {
remote: "remote" //remote module
},
shared: {
'react': { singleton: true, eager: true } ,
'react-dom': { singleton: true, eager: true }
}
}),
],
};
{
"name": "host_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@types/react": "^17.0.30",
"@types/react-dom": "^17.0.11",
"@types/react-router-config": "^5.0.3",
"@types/react-router-dom": "^5.3.2"
"html-webpack-plugin": "^5.4.0",
"ts-loader": "^9.2.6",
"typescript": "^4.5.2",
"webpack": "^5.58.1",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Host App</title>
<script src="http://localhost:3000/remote/remoteEntry.js"></script
</head>
<body>
<div id="app"></div>
</body>
</html>
import('./bootstrap');
export {};
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.tsx'
ReactDOM.render(<App/>, document.getElementById('app'));

Consuming common components

  1. Create a component in remote application (src/RemoteApp.tsx).
import React from 'react';
//Imports

const RemoteApp = ({children }) => {
return <div>Just a remote module</div>;
};

export default RemoteApp;
...
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new ModuleFederationPlugin({
name: 'remote',
library: { type: 'var', name: 'remote' },
filename: 'remoteEntry.js',
exposes: {
"./RemoteApp": "./src/RemoteApp.tsx",
}
shared: {
'react': { singleton: true, eager: true } ,
'react-dom': { singleton: true, eager: true }
}
}),
],
...
import React from 'react';//Imports
const RemoteApp = React.lazy(() => import('remote/RemoteApp'));
const App = () => {
return (
<React.Suspense fallback="loading">
<RemoteApp>
</RemoteApp>
</React.Suspense>
)
};
export default App;

Consuming common services

  1. Create a remote service inside remote module (Remote → src/remoteService.ts)
class RemoteService {
init = () => {
console.log("remote service called");
}
}

const remoteService = new RemoteService();

export default remoteService;
...
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new ModuleFederationPlugin({
name: 'remote',
library: { type: 'var', name: 'remote' },
filename: 'remoteEntry.js',
exposes: {
"./RemoteApp": "./src/RemoteApp.tsx",
"./remoteService": "./src/remoteService.ts"
}
shared: {
'react': { singleton: true, eager: true } ,
'react-dom': { singleton: true, eager: true }
}
}),
],
...
import React, { useEffect } from 'react';

//Imports
const RemoteApp = React.lazy(() => import('remote/RemoteApp'));
import remoteService from 'remote/remoteService';

const App = () => {
useEffect(() => {
remoteService.init();
}, []);
return (
<React.Suspense fallback="loading">
<RemoteApp>
</RemoteApp>
</React.Suspense>
)
};

export default App;

Deploying remote module to cloud and consuming in host application

  1. Build remote application with webpack and deploy to cloud provider. e.g. http://cloud-provider.com/remote
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Host App</title>
<script src="http://cloud-provider.com/remote/remoteEntry.js"></script
</head>

<body>
<div id="app"></div>
</body>
</html>

Common Issues

Working with typescript

  1. You need to add a type(.d.ts) in host application to work with remote types (Host → src/global.d.ts)
/// <reference types="react" />

declare module "remote/RemoteApp" {
const RemoteApp: React.ComponentType;

export default RemoteApp;
}

declare module "remote/remoteService" {
}

Loading modules async

const remoteService = await import('remote/remoteService');

--

--

Front-end developer https://www.linkedin.com/in/adarsh-somani.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store