Skip to main content

Discovering autonomous development using Module Federation Webpack (Part 2)

article banner

A Monorepo is a single repository containing multiple distinct projects, with well-defined relationships.

Monorepo is more than code and tools. A monorepo changes your organization and the way you think about code. By adding consistency, lowering the friction in creating new projects and performing large scale refactoring, by facilitating code sharing and cross-team collaboration, it'll allow your organization to work more efficiently.

Monorepos have a lot of advantages, but to make them work you need to have the right tools. Monorepo tool Nx by Nrwl (Narwhal Extension) is the most preferred next generation build system with first class monorepo support and powerful integrations.

Nx is an extension for the Angular CLI implementing monorepo-style development. It is an open source toolkit created by Ex googlers at company called Narwhal for developing enterprise or large scale applications.

Microfrontends is a front-end web development pattern in which a single application may be built from disparate builds. It is analogous to a microservices approach but for client-side single-page applications written in JavaScript. It is a solution to de-composition and routing for multiple front-end applications.

Module Federation, a powerful feature introduced in Webpack 5, allows developers to load separate bundles of code at runtime, making it an ideal choice for implementing micro frontend architecture. This article will guide you through the process of creating a React micro frontend application using Module Federation, with a focus on providing clear code examples.

How can you create Micro Frontend based ReactJS Web Application?

  • Host app loading remote components
    1. App1 - Host app
      1. Uses modules exposed by App2
      2. Uses dynamic imports
      3. Shares react & react-dom (singleton)
    2. App2 - Standalone remote app
        1. Exposes modules
        2. Shares react & react-dom (singleton)
      Shares react
    3. In App1 webpack.config.js file:
      // App1 webpack.config.js 
      	
      	module.exports = { 
      	... 
      	plugins: [ 
      	new ModuleFederationPlugin({ 
      	name: “app1”, 
      	remotes: { 
      	app2: “app2@http://localhost:3002/remoteEntry.js”, 
      	}, 
      	shared: { 
      	react: { singleton: true }, 
      	"react-dom": { singleton: true },
      	},
      	}), 
      	... 
      	], 	
      	
    4. In App2 webpack.config.js file:
      // App2 webpack.config.js
      	
      		
      		module.exports = {
      		...
      		plugins: [
      		new ModuleFederationPlugin({
      		name: “app2”,
      		filename: "remoteEntry.js",
      		exposes: {
      		"./Button": "./src/Button",
      		},
      		shared: {
      		react: { singleton: true },
      		"react-dom": { singleton: true },
      		},
      		}),
      		...
      		],
      		...
      
      	
            
      	
    5. In App1:
      // app1
      	
      		
      		const RemoteButton = React.lazy(() => import("app2/Button"));
      		const App = () => (

      App 1

      
      		);
      	
    6. In App2:
      // app2
      	
      	const Button = () => ;
      	export default Button;
      	
  • Nested remote components
    1. app1 - host app
      1. async loads button container from app2
      2.  
      3.  
    2. app2 - standalone app
      1. exposes button container
      2. async loads button from app3
    3. app3 - standalone app
      1. exposes button
    4. all apps
      1. share react & react-dom (singleton)
        Shares react
  • Multiple layered host & remote apps
    1. main-app - top-level host app
      1. uses react libs from lib-app
      2. uses button from component-app
    2. component-app - middle-level host & remote app
      1. uses react libs from lib-app
      2. exposes button
    3. lib-app - low-level pure remote app
      1. exposes react & react-dom
        Shares react
  • Sharing data - content providers
    1. app1 - host app
      1. uses a React context from shared-library
      2. wraps a context provider (value = ‘Billy’)
      3. async loads Welcome from app2
    2. app2 - remote app
      1. uses a React context from shared-library
    3. shared-library - module (it’s not an app)
      1. defines a React context
    4. In app1 & app2 webpack.config.js file:
      // app1 & app2 webpack.config.js
      	
      	new ModuleFederationPlugin({
      	...
      	shared: [
      	"react",
      	"react-dom",
      	{
      	"@shared-context/shared-library": {
      	import: "@shared-context/shared-library",
      	requiredVersion:
      	require("../shared-library/package.json").version,
      			},
      	},
      	],
      	}),
      	...
      	})
      
      	
    5. In app1:
      // app1
      						
      		import { NameContext } from "@shared-context/shared-library";
      		const Welcome = React.lazy(() => import("app2/Welcome"));
      		const App = () => (
      			
      				
      					
      				
      			
      		);
      
      	
    6. In app2:
      // app2
      						
      		const Welcome = () => {
      		const name = React.useContext(NameContext);
      		return

      Welcome (from app 2), {name}

      ;
      		  };
      	

      Shares react
  • Sharing data - redux reducers
    1. app1 - host app
      1. creates a store and adds a method injectReducer to the store
      2. async loads RemoteApp and passes the store as prop to that app
    2. app2 - remote app
      1. does not create its own store but uses the store received from app1
      2. injects a reducer into the store received from app1
    3. In app1:
      // app1
      	
      		const RemoteApp = React.lazy(() => import('app2/RemoteApp'));
      		const App = () => (
      			
      				
      					
      				
      			
      		);
      	
  • Sharing routes
    1. app1 - host app
      1. exposes a Navigation component, and its routes (home)
      2. the home route use dynamic import to load Home
      3. uses remote routes
    2. app2 - remote app
      1. exposes its local routes (about)
      2. about route use dynamic import to load About
      3. uses remote routes

both apps use eager: true - the modules are not loaded async