0% found this document useful (0 votes)
283 views

Practical Module Federation

Uploaded by

Atishay.Jain
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
283 views

Practical Module Federation

Uploaded by

Atishay.Jain
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 167

PRACTICAL MODULE

FEDERATION
J ACK H ERRINGTON & Z ACK J ACKSON

Book Version 1.4.0


Covers Webpack 5.0.0
PROLOGUE
We’ve all remarked at some point how rapidly the world of Javascript changes, or how
frequently a new framework arises. But rarely does a completely new architectural
paradigm arise. Module Federation is a paradigm shift in how we organize and build
our applications. This book, Practical Module Federation, seeks to give you not just a
solid understanding of what Module Federation is and how to configure, build, and
deploy it, but to do so in the context of the practical applications you will be building
with it.

This book is copyrighted © 2020, Zack Jackson and Jack Herrington, all rights are
reserved.

Practical Module Federation Page 1 of 166


CONTENTS

Preface 3
Introducing Module Federation 6
Getting Started 15
How Module Federation Works 35
Resilient Sharing of React Components 40
React State Sharing Options 49
Resilient Function and Data Sharing 75
Dynamically Loading Federated Modules 84
Shared Libraries And Versioning 93
Resilient Header/Footer 102
A/B Tests 110
Live Preview Between Applications 118
Fully Federated Sites 126
Alternative Deployment Options 139
Module Federation Dashboard 145
Frequently Asked Questions 148
Troubleshooting 151
Reference 157
Glossary 164
About the Authors 166

Practical Module Federation Page 2 of 166


PREFACE

Module Federation is an advanced use topic for the Webpack bundler starting with
version 5. In order to get the most out of this book you should have a passing
familiarity with Webpack up to and including version 4. You should know what it’s
useful for (bundling Javascript), how it’s configured (through configuration files
written in Javascript), and how it’s extended (through plugins). This book also deals
extensively with the React view framework and with web development technologies in
general.

SETUP
You will want to have node running on your local machine at version 12 or greater to
run Webpack builds and execute the server code. You should also have git and yarn
installed for build tooling.

HOW TO READ THIS BOOK


The first three chapters of this book provide a step-by-step walkthrough of the
architectural role of Module Federation, how to use it in a simple case, and then a walk-
through of the mechanics of how it works internally. The chapters that follow on from
that weave through a number of related topics to give you details and options on how to
get the most out of Module Federation in practical real world projects at scale. It’s
recommended that for those chapters, you have the code downloaded and experiment
with the projects as you run them.

Practical Module Federation Page 3 of 166


SUBSCRIPTION
This book is more than just a book. It’s a year long subscription to practical topics
around Module Federation and will be updated regularly as we learn more about this
exciting topic, and make changes to our “best practices”. You can be part of that as well
by engaging with the Webpack community. As the tools and technology progresses, we
will keep updating this book, pushing new versions and sending you notifications.

Practical Module Federation Page 4 of 166


PART ONE
GETTING TO KNOW MODULE FEDERATION

Practical Module Federation Page 5 of 166


1.
INTRODUCING MODULE FEDERATION

Module Federation gives us a new method of sharing code between applications. To


understand it, we need to be able to compare it with the mechanisms we have had
access to thus far. To do that, we need to envision a fairly common scenario. You have
an enterprise system where you have two applications; the first is an internally facing
content management system (CMS) that allows employees to format product
information for display on the website. The second application is the externally facing
website that displays that product information to the customer. The internal CMS
system has a “preview” functionality that ideally matches exactly what the customer
would see on the website. These two applications are managed by two separate teams.
The ask from management is that both of these applications share the same rendering
code so that the preview functionality in the CMS application is always in sync with the
rendering code on the customer website.

With this in mind, let's think about the options we have available to us today to share
that rendering code.

NODE MODULES
By far, the most common route to sharing code in the Javascript ecosystem is via an
NPM (Node Package Manager) module. In this model, either the CMS system or the
external site would extract the rendering code into a new project (and potentially a new

Practical Module Federation Page 6 of 166


repository). Both of the applications would then remove any rendering code they have
and replace it with the components in the NPM module.

The simple illustration below shows the connection between these two applications and
the NPM module that holds the rendering components.

CMS Application External Website

Rendering
Components

The advantage of this approach is that it’s highly controlled and versioned. To update
the rendering components, the code needs to be modified, approved, and published to
NPM. Both applications then need to bump their dependency versions and their own
versions, then re-test and re-deploy.

Another advantage of this approach is that both of the applications that consume this
library are in turn versioned and deployed as complete units, probably as a docker
image. It’s not possible that the rendering components would change without a version
bump and a re-deploy.

The primary disadvantage here is that it’s a slow process. Without automation, it’s up
to both teams to inform each other that a new version is out and request an update. It’s
also a big cognitive shift for the engineers on the project when they need to make a
change to the rendering components. Often times, it will mean that the engineer will
need to change both the host application and the rendering components in order to
close a ticket.

Practical Module Federation Page 7 of 166


Depending on the size of the host application and the rendering components, it can be
frustrating waiting on builds, tests, and deployment. Back to back, where the host
application only needed to bump a version. During urgent production failures, hotfixes
and mitigation can be challenging, which could impact revenue.

Another approach that has emerged in the last few years is to use a Micro-frontend
approach.

MICRO-FRONTENDS
In the Micro-frontend (Micro-FE) model, the rendering code is again extracted into a
new project but the code is consumed either on the client or on the server at runtime.
This is illustrated below as a dotted line

CMS Application External Website

Rendering Micro-FE

To get this going, the Rendering Micro-FE project packages its source code as a
runtime package. The popular Micro-FE SingleSPA calls these packages “parcels”. That
code is then deployed out to a static hosting service like Amazon’s S3. The CMS
application and the external site then download this code at runtime and use it to
render the content.

The advantage of this approach is that the Rendering Micro-FE code can be updated
without either of the consuming applications redeploying. In addition, both

Practical Module Federation Page 8 of 166


applications are always in sync in terms of how they render the code since they are
always downloading the latest version.

The downsides are that just like in the NPM model, the rendering code needs to be
extracted from the original application in order for this to work. It also needs to be
packaged in a way where it works with the Micro-FE framework, which might require
substantial modification. In the end, it might not end up looking like a run of the mill
React component.

Another downside is that an update to the rendering code might end up breaking either
of these applications. Therefore, additional monitoring will need to be added to make
this work.

Performance and code duplication is challenging to manage. As either consumer may


already have a dependency the Micro-FE uses. This can drastically degrade load and
execution times, as there is a lot more code that needs to be processed over and over.
As each application has start up overhead, the boot time can put pressure on a
machine's ability to achieve first meaningful paint (an important metric of page speed).

One workaround to alleviate code duplication is to externalize commonly used


packages, having it loaded independently by a consuming application. However, this
can become a very manual process and can be risky as there is a highly-centralized
reliance on that common code always being there. This can defeat the purpose of
Micro-FE's principal of decentralization. Upgrading to newer released copies is
challenging as all Micro-FEs need to be compatible and deployed at the exact same
time.

Module Federation presents us with a new option that makes it far more appealing
than either of these two approaches.
Practical Module Federation Page 9 of 166
MODULE FEDERATION
With Module Federation either the CMS application or the Website, simply “exposes”
its rendering code using the Webpack 5 ModuleFederationPlugin. And the other
application then consumes that code at runtime, again using the plugin. This is shown
in the illustration below:

CMS Application External Website

Rendering
Components

In this new architecture, the CMS application specifies the external website as
“remote”. It then imports the components just as it would any other React component,
and invokes them just as it would if it had the code internally. However, it does this at
runtime so that when the external website deploys a new version, the CMS application
consumes that new code on the next refresh. Distributed code is deployed
independently without any required coordination with other codebases that consume
it. The consuming code doesn’t have to change to use the newly deployed distributed
code.

There are several advantages to this approach.

- Code remains in-place - For one of the applications, the rendering code remains
in-place and is not modified.
- No framework - As long as both applications are using the same view framework,
then they can both use the same code.

Practical Module Federation Page 10 of 166


- No code loaders - Micro-FE frameworks are often coupled with code loaders, like
SystemJS, that work in parallel with the babel and Webpack imports that engineers
are used to. Importing a federated module works just like any normal import. It
just happens to be remote. Unlike other approaches, Module Federation does not
require any alterations to existing codebases. There isn’t the learning curve that
would come with a Micro-FE framework.
- Applies to any Javascript - Where Micro-FE frameworks work on UI
components, Module Federation can be used for any type of Javascript; UI
components, business logic, i18n strings, etc. Any Javascript can be shared.
- Applies beyond Javascript - While many frameworks focus heavily on the
Javascript aspects, Module Federation will work with files that Webpack is currently
able to process today such as images, JSON, and CSS. If you can require it, it can
be federated.
- Universal - Module Federation can be used on any platform that uses the
Javascript runtime such as Browser, Node, Electron, or Web Worker. It also does
not require a specific module type. Many frameworks require use of SystemJD or
UMD. Module Federation will work with any type currently available, including
AMD, UMD, CommonJS, SystemJS, window variable and so on.

Just as with any architecture, there are some disadvantages and primary among those
is the run-time nature of the linkage. Just as in the Micro-FE example, the application
is not complete without the rendering code, which is loaded at runtime. If there are
issues with loading that code, then that can result in outages. In addition, bugs in the
shared code code easily result in either or both of the consuming applications being
taken down.

Practical Module Federation Page 11 of 166


You might be looking at the diagrams for both the Module Federation and the Micro-
FEs and wondering what the differences are. It can be confusing, so let’s examine in
some more detail.

THE RELATIONSHIP BETWEEN MODULE FEDERATION AND MICRO-


FRONTEND FRAMEWORKS
Module Federation and Micro-Frontend frameworks cover a lot of the same ground
and are actually complementary. The primary purpose of Module Federation is to share
code between applications. The primary purpose of Micro-FE frameworks is to share
UI between applications. A subtle, but important, difference.

A Micro-FE framework has two jobs; first, load the UI code onto the page and second,
integrate it in a way that’s platform-agnostic. Meaning that a Micro-FE framework
should be able to put React UI components on a Vue page, or Svelte components in a
React page. That’s a nifty trick and frameworks like SingleSPA and OpenComponents
do a great job at that.

Module Federation, on the other hand, has one job--get Javascript from one
application into another. This means that it can be used to handle the code loading for
a Micro-FE framework. In fact, the SingleSPA documentation has instructions on how
to do just that very thing. What Module Federation can do that a Micro-FE framework
cannot is load non-UI code onto the page, for example, business logic or state
management code. A simple way to think about Module Federation is; it makes
multiple independent applications work together to look and feel like a monolith, at
runtime.

Practical Module Federation Page 12 of 166


Before Module Federation, Micro FE frameworks would use custom loaders or
excellent off-the-shelf loaders, like SystemJS. Now that we have Module Federation,
the Micro-FE frameworks are starting to adopt that instead of these other loaders.

Whether or not to use a Micro-FE framework comes down to one question; do you
want to use different view frameworks (e.g. React, Vue, Svelte, vanilla JS, etc.) on the
same page together? If so, then you’ll want to use a Micro-FE framework and use
Module Federation underneath that to get the code onto the page. If you are using the
same view framework in all your applications, then you should be able to get by using
Module Federation as your “Micro-FE framework” because you don’t need any
additional code to, for example, use external React components in your current React
application. SingleSPA also offers the ability to route between Micro-FEs, or mount
different frameworks on a single page via an opinionated and organized convention.
You would need to use one of the many great routing solutions (e.g. react-router) to get
the same features in a Module Federation system.

WITH GREAT POWER COMES GREAT RESPONSIBILITY


Modern Javascript applications are complex. At the time of this writing, an un-
modified create-react-app application had over a thousand NPM modules, comprised
of over 38 thousand files--just to put up a web application with a spinning React logo.
Most large scale applications have dozens or hundreds of in-house modules and many
more additional open source modules. Module Federation layers on top of that
complexity an entirely new mechanism for sharing code at runtime between
applications.

Practical Module Federation Page 13 of 166


Doubtless, there is a lot of value in runtime sharing of Javascript code between
applications. However, it comes with a non-trivial complexity cost, even with Webpack
5 doing a lot of heavy lifting to make it all feel seamless.

Managing that complexity means using the right tool for the right job. If the code
doesn’t need to be runtime shared, it shouldn’t be. An application that is entirely self-
contained and deployed as a docker image is always going to be less complex and most
likely more reliable, than one that includes runtime dependencies. Code that doesn’t
need to be runtime shared should not be until there is a requirement for it.

WHAT’S NEXT
By now, you should have a decent understanding of what Module Federation is and
how it changes the landscape of code sharing in Javascript. The chapters of the book
that follow walk through setting up Module Federation and sharing different types of
code with practical examples that you can follow on your existing projects.

Practical Module Federation Page 14 of 166


2.
GETTING STARTED

Code for this chapter is available on GitHub.

The easiest way to start learning Module Federation is to try it out. To do that, we will
create two applications; home and nav. The home page application will consume and
render a Header React component that is exposed by the nav application.

Let’s start by using a pre-built boilerplate application to create the home app. I have
one specifically built for this purpose named wp5-starter-react.

First, let’s get the project directories set up. Using the Terminal, follow these steps:

% mkdir -p wp5_test/packages
% cd wp5_test/packages
% npx degit [email protected]:jherr/wp5-starter-react.git#main home

This will create a new directory called wp5_test and within it packages/home that
contains our first Webpack 5 application.

This application is pretty much the simplest possible React app that I could come up
with and it is bundled using Webpack 5.

Practical Module Federation Page 15 of 166


To run it, we first run yarn to install the modules and yarn start to start it up.

% cd home
% yarn
% yarn start

This will automatically fire up a browser which will display this simple page.

So, let’s have a look around at the application to see how this is all put together. The
first place to look is in src/App.jsx where we have the React application itself.

Practical Module Federation Page 16 of 166


import React from "react";
import ReactDOM from "react-dom";

import "./index.css";

const App = () => <div>Hi there, I'm React from React.</div>;

ReactDOM.render(<App />, document.getElementById("app"));

In this simple React application, we create new component called App and render it on
the page.

This React application is then rendered into the HTML page contained in src/
index.html.

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

Again, not much to see, yet. To get all this running, we are using a combination of
babel to transpile the code, and Webpack to bundle the code and to run the
development server. If we look into the package.json, we can see the dependencies
and these are described below.

Dependency Description
react This is the React view library.

Practical Module Federation Page 17 of 166


Dependency Description
react-dom This is the React DOM library which handles rendering React
trees into DOM elements.
@babel/core This is the engine of the Babel code transpiling system.
@babel/preset-react These are convenient Babel presets for React.
@babel/preset-env This is a present to bring import environment variables into
the Babel transpiling process.
babel-loader This Webpack loader runs Babel on Javascript code before it’s
bundled.
css-loader This is a Webpack loader that allows the import of CSS files
into Javascript files.
html-webpack-plugin This is a plugin for Webpack that automatically inserts a
script tag that references the bundle into the HTML template.
style-loader This is a plugin that works in conjunction with CSS loader to
import CSS into Webpack bundles.
webpack This is the Webpack engine.
webpack-cli This is the Webpack command line interface.
webpack-dev-server This is the Webpack development server we use to run the
application.

So far, there is really nothing new to see here. These are all standard dependencies that
should be familiar to anyone who has written webpacked React applications. The
important element here is that all of these dependencies are at the latest version, and in
particular, that Webpack has a version of 5.0.0-beta.18 or higher.

Practical Module Federation Page 18 of 166


The fun begins in the webpack.config.js file, shown below:

const HtmlWebPackPlugin = require("html-webpack-plugin");


const ModuleFederationPlugin = require(“webpack/lib/container/
ModuleFederationPlugin”);

const deps = require("./package.json").dependencies;


module.exports = {
module: {
rules: [
… // Standard Webpack Rules for CSS and JS/JSX,
],
},
plugins: [
new ModuleFederationPlugin({
name: "starter",
filename: "remoteEntry.js",
remotes: {},
exposes: {},
shared: {
...deps,
react: { singleton: true, requiredVersion: deps.react, },
"react-dom": { singleton: true,
requiredVersion: deps["react-dom"],
},
}
}),
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
};

At the top of the file, we bring in the HTMLWebPackPlugin which uses the HTML
template in src/index.html to create the web page that hosts the React application.

Practical Module Federation Page 19 of 166


Below that is the import for the ModuleFederationPlugin, which is then used in
the plugins section.

The rules section in modules tells Webpack how to identify and interpret the
different types of source files based on the extension.

Now, we get to the interesting part where we set up our ModuleFederationPlugin.


We will get into a lot more detail as we go but the fields that configure the Module
Federation plugin are described in the table below.

Field Description
name This is the name of the application. Never have conflicting
names, always make sure every federated app has a unique
name.
filename This is the filename to use for the remote entry file. The
remote entry file is a manifest of all of the exposed modules
and the shared libraries.
remotes These are the remotes this application will consume.
exposes These are files this application will expose as remotes to other
applications.
shared These are libraries the application will share with other
applications.

Practical Module Federation Page 20 of 166


The critical concepts here are remotes, exposes, and shared.

- Remotes - These are the names of the other federated module applications that this
application will consume code from. For example, if this home application consumes
code from an application called nav that will have the header, then nav is a “remote”
and should be listed here.
- Exposes - These are the files that this application will export as remotes to other
applications.
- Shared - This is a list of all the libraries that this application will share with other
applications in support of files listed in the exposes section. For example, if you
export a React component, you will want to list react in the shared section because
it’s required to run your code.

Now that we understand the project and how it works, it’s time to start making some
modifications.

CREATING THE HOME APP


First, let’s modify that package.json to rename this application home. The result will
look like this:

{
"name": "home",
...

Practical Module Federation Page 21 of 166


Next, we change the Module Federation plugin configuration in webpack.config.js
to also indicate that our name is home.

new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
...
}),

Now, we are ready to consume some federated modules and put them on the page, but
in order to do that, we need to have some. So, let’s create a new project called nav that
will have a Header component we can consume.

CREATING THE NAV APP


To create the nav app, let’s go back into the Terminal and follow much the same
process as we did before:

% cd wp5_test/packages
% npx degit [email protected]:jherr/wp5-starter-react.git#main nav

From here, we need to modify package.json and webpack.config.json to


change the name to nav just as we did in the home project.

Our next step is to modify index.js to export a new Header component.

Practical Module Federation Page 22 of 166


import React from "react";

const Header = () => (


<header style={{ fontSize: "xx-large" }}>I'm the header!</header>
);

export default Header;

If you like, you can remove the index.css and App.jsx files as well, we don’t need
those.

The next thing we need to do is modify webpack.config.js to expose this new


Header component.

new ModuleFederationPlugin({
name: "nav",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Header": "./src/index",
},
shared: { ... }, // Shared React dependencies
}),

The exposes object has pairs of keys and values for all of the different Javascript files
this application exposes to other applications. In this case, we are saying that ./src/
index.js is exposed as Header to any consuming application.

We are adding react to the list of shared modules because our component relies on
React. If you add more dependencies to your application that are used by the Javascript

Practical Module Federation Page 23 of 166


files that you expose, then you will want to add those to shared as well. You should not
add development dependencies (devDepedencies) or share node.js server side only
dependencies within a build designated for the client (like express).

The final thing we need to do is specify the port number for our development server.
We do that by altering the the port numbers in webpack.config.js:

module.exports = {
output: {
publicPath: "http://localhost:3001/",
},

...

devServer: {
port: 3001,
},

...
};

We need to do this because when we reference this nav application in our home
application, we need the location to be a known value.

We also need to modify the webpack.config.js to let Webpack know the base
location for the server code and pages from the nav app. This is an important step,
because this will be used to asynchronously load code at runtime. If the origin is
different from the consumer, the remote is unable to know where it should download
its own code from.

Practical Module Federation Page 24 of 166


The only thing we have to do now is fire up this nav application and make some
modifications to our home application to consume it. To do that we first run yarn to
install all the node modules and then yarn start the application.

MODIFYING THE HOME PAGE


Now that we have our nav application up and running, it’s time to import that Header
component into our home application.

The first step is to modify the webpack.config.js to tell the


ModuleFederationPlugin that we will be consuming components from the nav
application.

plugins: [
new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
remotes: {
nav: "nav@http://localhost:3001/remoteEntry.js",
},
exposes: {},
shared: { ... }, // Shared React dependencies
}),

To do that, we add a nav key/value pair to the remotes key in the Module Federation
options. The key is what you would like to call this remote internally. The value is the
name of the remote specified in the remotes Webpack configuration. For simplicity,
let’s use nav for both of these.

Practical Module Federation Page 25 of 166


We’ve also added react to the list of shared modules. This is critical because it tells
Webpack that the host application, home, wants to share its React instance with any
incoming federated modules. Which, in this case, is in the incoming Header. If we
didn’t do this, two bad things would happen.

- The incoming Header component would download its own shared version of
react. Since React is very large, that would significantly add to the weight of the
page.
- React, and many other libraries, have internal singletons for storing state. These are
used by hooks and other elements of the React architecture. If you have two
different copies running on the same page, you will get warnings and errors.

When in doubt, always share all of your non-development dependencies.

Practical Module Federation Page 26 of 166


The next step is to go to the App.jsx file in the src directory and alter it to read:

import React from "react";


import ReactDOM from "react-dom";

const Header = React.lazy(() => import("nav/Header"));

import "./index.css";

const App = () => (


<div>
<React.Suspense fallback={<div />}>
<Header />
</React.Suspense>
<div>Hi there, I'm React from React.</div>
</div>
);

ReactDOM.render(<App />, document.getElementById("app"));

At the top of the file, we import Header using a React lazy import. In the body of the
component, we reference that header within a React.Suspense object. We also
specify that an empty <div> tag should be used until the content loads.

With that all set up, we can run yarn start in the home directory within a new
terminal session (both servers need to be running). The result should look something
like what is shown in the figure below.

Practical Module Federation Page 27 of 166


Some congratulations are in order. You’ve just successfully federated your first two
applications and shared a React component between the two! If you are stuck, be sure
to look at the book code available on Github. Clone the code and run it for yourself. If it
works, then look for differences between the book code and your own version.

MAKING A WORKSPACE
Now that we have our example up and running, we should spend a second to make it
easier on ourselves by creating a workspace in the wp5_test directory. First, stop both
of the development servers and remove the node_modules directories and
yarn.lock files.

Practical Module Federation Page 28 of 166


Next initialize a package.json in the wp5_test directory using the following
commands:

% cd wp5_test
% yarn init -y

After that, install the concurrently and wsrun packages in that package.json
using the yarn add command.

% yarn add concurrently wsrun -D

To the package.json file, add the following keys and values:

"private": true,
"scripts": {
"start": "concurrently \"wsrun --parallel start\""
},
"workspaces": [
"packages/*"
],

The private key and workspaces keys tell yarn that we are creating a monorepo
project that hosts multiple packages within a single repository. Those packages are
contained in a packages subdirectory we are going to create.

The start script uses wsrun (workspaces run) and concurrently to execute both of
the servers simultaneously.

Practical Module Federation Page 29 of 166


With that, we’ve got it all set up and we can first install all the node modules with yarn
and the run both of the servers simultaneously using yarn start.

GETTING NAMING RIGHT


It’s important to know what parts of the web pack configuration of one application
need to match in the corresponding parts of the consuming applications web pack
config. In the figure below we show the key parts of the Nav applications web pack
config and how they map into the Home application’s web pack config.

Nav
module.exports = {
output: {
publicPath: "http://localhost:3001/",
},
plugins: [
new ModuleFederationPlugin({
name: "nav",
fileName: "remoteEntry.js",
exposes: {
"./Header”: "./src/Header"
}
}),
],
};

Home

src/App.jsx

const Header = React.lazy(() => import("my-nav/Header"));

module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "home",
remotes: {
"my-nav": "nav@http://localhost:3001/remoteEntry.js"
}
}),
],
};

The publicPath is the base of the remote URL which ends with the fileName. And
then before that is the remote name defined using name in the Module Federation

Practical Module Federation Page 30 of 166


Plugin configuration. You can also change the name of the import by changing the key
to whatever you like, for example in this case my-nav.

PROPERLY SHARING REACT


Throughout this book we will define the shared key in the
ModuleFederationPlugin configuration object like this:

shared: { ... }, // Shared React dependencies

This is just shorthand for this full configuration:

const deps = require("./package.json").dependencies;

...

shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},

Practical Module Federation Page 31 of 166


It’s nothing sinister, this configuration is long and it would have taken up a lot of space
on the page. But it is worth talking about why this shared configuration is set up this
way. What we are doing is pulling the dependencies, as on object, from the
package.json file. We are then setting the shared object to match the object from
package.json, but we are overriding react and react-dom to set them to
singleton mode and pin the to their specific current version.

They are marked as singletons because those two libraries; react and react-dom
have internal state and you can’t have multiple copies of them on the same page. That
qualifies them a “singletons” and telling Webpack this ensures that it will never load
more than one copy of react, or react-dom on the page at one time.

The value of the spread of the deps object (i.e. ...deps) is that all of the other
runtime dependencies for the application are added to the list of shared libraries
automatically. So if you bring in antd, or lodash, those will be automatically shared.

Practical Module Federation Page 32 of 166


It’s worth noting that react and react-dom aren’t the only singletons out there. For
example, most CSS-in-JS libraries are also singletons. And if you find that you are
seeing troubles sharing something @emotion/core then you should consider treating
that exactly the same way as react, like so:

shared: {
...
"@emotion/core": {
singleton: true,
requiredVersion: deps["@emotion/core"],
},
},

Any time you have globally shared state within a module, which is very easy to do, then
your library becomes a singleton.
The different modes for the shared key are explained in more in the appendices at the
end of the book. But if you are using React then we strongly recommend adopting this
standard pattern.

MIGRATING EXISTING APPLICATIONS


What do you do if you have an existing Webpack 4 application and you want to expose
parts of it to other applications, or use Module Federation to consume parts from other
applications? The most direct route is to upgrade from Webpack 4 to Webpack 5 and
then add the ModuleFederationPlugin and customize it using the techniques we’ve
discussed so far. Thankfully, the Webpack documentation has excellent upgrading
documentation you can follow.

Practical Module Federation Page 33 of 166


If you don’t want to upgrade, you might consider creating a sidecar Webpack 5 project
in a subdirectory of the application. This sidecar application would include Webpack 5
along with any dependencies from the original project. The webpack.config.js file
for the sidecar would then use the ModuleFederationPlugin to expose the files
from the host application using relative references to the source. This sidecar technique
is described and demonstrated in the chapter on Alternative Deployment Options.

WHAT’S NEXT
Now that you’ve got your first experience with Module Federation, it is time to dig into
the details of how to share code between applications safely. That starts with a deep
dive into the world of sharing React components.

Practical Module Federation Page 34 of 166


3.
HOW MODULE FEDERATION WORKS

Now that we understand where Module Federation fits into the world, and we’ve tried
it out on a very basic application, let’s increase our depth of knowledge by exploring
how Module Federation works.

Let’s first look at how Module Federation works at build time.

BUILD TIME
The ModuleFederationPlugin we’ve been using thus far is really just syntactic
sugar over three interrelated plugins--it serves as a convenience plugin that passes
options to the separated parts that perform the heavy lifting. You can use each of those
individually and customize how you see fit so that you have ultimate control over the
output.

These three plugins are:

- ContainerPlugin - This plugin manages the export the remote modules that you
define in the exposes key of the configuration. It also creates the remote entry file
that acts as a manifest for the application, which is called the “scope” internally. If
your application only exposes remote modules, then this is the only plugin you need.
This is what a remote uses.

Practical Module Federation Page 35 of 166


- ContainerReferencePlugin - This plugin manages the remotes in the
configuration. This is what a host uses.
- SharePlugin - This plugin manages the shared portion of the configuration. It
handles all of the versioning requirements for the shared packages. Shared is also
referred to as “overrides” both internally and externally. This is used by both remote
and host.

To get a deeper look at what’s built by Webpack, a good way to do that is to run the
build in development mode and to look at the output of the dist directory, like this:

webpack —mode development

In an example project that exposes one remote module, Header, the example output is
shown below:

Asset Size
index.html 171 bytes [compared for emit]
main.js 2.93 KiB [emitted] [name: main]
remoteEntry.js 12.1 KiB [emitted] [name: nav]
src_header_js.js 2.3 KiB [emitted]
vendors-node_modules_react_index_js.js 73.7 KiB [compared for emit] [id hint: vendors]
Entrypoint main = main.js
Entrypoint nav = remoteEntry.js

Each of these files and its role is individually described below.

- index.html - This is the compiled HTML file that includes the main.js file which
runs the application.
- main.js - This is the compiled code for the main entry point for the application.

Practical Module Federation Page 36 of 166


- remoteEntry.js - This is the manifest Javascript and specialized runtime for
the exported remote modules and any shared packages.

- src_header_js.js - This is the compiled Javascript for the Header component.


This is referenced in the remoteEntry.js file.

- vendors-node_modules_react_index_js.js - This is the compiled React


package that supports the Header component which is shared.

It’s important to understand that these files are a fusion of the Javascript bundles
required to run the application itself, as well as the Javascript bundles required for the
remote modules. Often times those two overlap. For example, the same vendor bundles
that are referenced by the remote modules are used by the application itself.

Now that the Javascript is all compiled, let’s discuss how it actually works at runtime.

MODULE FEDERATION AT RUNTIME


When the remoteEntry.js is loaded by the browser, it registers a global variable
with the name specified in the name key in the ModuleFederationPlugin
configuration. This variable has two things in it, a get function that returns any of the
remote modules and an override function that manages all of the shared packages.

For example, you have an application with the name of nav that exposes a runtime
module named Header. After loading the code, you would be able to open up the
console and inspect window.nav and see that it has two functions, get and
override.

Practical Module Federation Page 37 of 166


With the get function we can get the Header, like so:

window.nav.get('Header')

This returns a promise, which when resolved gives you a factory. We can invoke that
like this:

window.nav.get('Header').then(factory => console.log(factory()));

This will output through console.log the module as defined in the Header
implementation. If Header exports a default, the value returned from factory()
will be an object with a key named default. If the module has other named exports,
those will also be attached to the object.

A side effect of running the factory is also to load any shared packages required by the
remote module. Webpack is good about only loading the shared packages required to
support each module. For example, if component A imports react and lodash, but
component B only imports react, running the factory on B would only bring in
react.

In addition, Module Federation is smart enough to resolve shared packages between


remotes. For example, a host application named home consumes two remotes: nav and
search. Both nav and search require lodash, but home does not. Whichever
remote is loaded first will load its version of lodash, and if the other remote module is
loaded, it will use the already loaded lodash as long as its version parameters are
satisfied.

Practical Module Federation Page 38 of 166


Important Note: Circular imports and nested remotes are supported. For example,
App A imports a module from App B, App B imports an exported module from App C,
which imports an expose module from App A. This is pictured in the diagram below.

App A App B App C

Webpack will resolve these circular imports correctly and efficiently.

WHAT’S NEXT
Now that we’ve dug more deeply into Module Federation and how it works, we will
jump back into the world of practical implementation and cover how to safely export
and consume React components in a federated context.

Practical Module Federation Page 39 of 166


4.
RESILIENT SHARING OF REACT COMPONENTS

Code for this chapter is available on GitHub.

Let’s visualize the connection between the home app and the nav app we created in the
last chapter. The home app has a runtime connection to the Header.

Home Application Nav Application

Header

However, any runtime connection can be broken. Let’s try that out by simply starting
the home server without running the nav app. The result is that the page is broken. No
content is rendered at all. In the console we see that nav is undefined:

Uncaught ReferenceError: nav is not defined


while loading "Header" from webpack/container/reference/nav

Practical Module Federation Page 40 of 166


This is clearly not a great customer experience. Let’s have a look at how use the
Header component in the code:

<React.Suspense fallback={<div />}>


<Header />
</React.Suspense>

We use a React.lazy import and we’ve wrapped it in a Suspense. Apparently, a


suspense is not enough. What we need is an error boundary to make this system more
resilient. To create an “error boundary” we follow the documentation on the React site
by adding a component to home application’s App.jsx file.

Practical Module Federation Page 41 of 166


class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// logErrorToMyService(error, errorInfo);
}

render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}

Practical Module Federation Page 42 of 166


Then we wrap the Suspense in this new ErrorBoundary component.

const App = () => (


<div>
<ErrorBoundary>
<React.Suspense fallback={<div />}>
<Header />
</React.Suspense>
</ErrorBoundary>
<div>Hi there, I'm React from React.</div>
</div>
);

It’s unfortunate that we can’t use a hook for this, but React does not currently support
hooks for error boundaries. The good news is that the page renders and we get our
error message instead.

This is certainly an improvement. However, we could do better, and it starts by looking


at the ErrorBoundary code. The important method is

Practical Module Federation Page 43 of 166


getDerivedStateFromError which is what creates the boundary. Let’s rewrite this
ErrorBoundary component into a FederatedWrapper component that handles
both the delayed execution case covered by React.Suspense and the error case
handled by getDerivedStateFromError.

Shown below is the code for FederatedWrapper that handles both a slow load and an
error during component execution.

class FederatedWrapper extends React.Component {


...// Same as in the ErrorBoundary class

render() {
if (this.state.hasError) {
return this.props.error || <div>Something went wrong.</div>;
}

return (
<React.Suspense fallback={this.props.delayed || <div />}>
{this.props.children}
</React.Suspense>
);
}
}

To use this wrapper component, all you need to do is wrap the Header component in
the FederatedWrapper with messaging alternatives for the error and delay
properties.

Practical Module Federation Page 44 of 166


<FederatedWrapper
error={<div>Temporary Header</div>}
delayed={<div>Loading header...</div>}
>
<Header />
</FederatedWrapper>

Now this works and is resilient to multiple types of errors.

HIGH ORDER ERROR HANDLING


Building on the FederatedWrapper, we can create a high order component (HOC) that
takes a lazy component and returns a component with the same API.

const wrapComponent = (Component) => ({ error, delayed, ...props }) => (


<FederatedWrapper error={error} delayed={delayed}>
<Component {...props} />
</FederatedWrapper>
);

This new wrapComponent function takes a React component as an argument and


returns a new component that takes the properties for error and delayed and sends
any additional properties onto the wrapped component.

This means that we can then create the Header component using this wrapper:

const Header = wrapComponent(React.lazy(() => import("nav/Header")));

Practical Module Federation Page 45 of 166


And we can use it in our application just as if we imported it directly.

const App = () => (


<div>
<Header />
<div>Hi there, I'm React from React.</div>
</div>
);

This creates an API for our React components, that we import via Module Federation,
that’s both easy to use and resilient to errors.

BOOTSTRAPPING APPLICATIONS
Another important safety tip is to “bootstrap” your application. Bootstrapping means
that your main entry point should be to a file whose job is to asynchronously load the
main application. For example, you would have an index.js file, which is the main
entry point for Webpack, and its contents would be:

import("./App");

The local App.jsx file would have the application, like so:

import ReactDOM from “react-dom";


ReactDOM.render(<div>Hello world</div>, document.getElementById("app"));

Practical Module Federation Page 46 of 166


This “bootstrapping” gives Webpack the opportunity to process the rest of the imports
before executing the app and will avoid potential race conditions on importing all the
code.

A non-bootstrapped version of this application would have an index.js file with the
contents:

import ReactDOM from "react-dom";


ReactDOM.render(<div>Hello world</div>, document.getElementById("app"));

And since index.js is the main entry point of the application (which is the default for
Webpack) then this code will execute immediately and not give Webpack any time to
load the required remotes before execution.

If this non-bootstrapped index.js were to contain a remote component, like so:

import ReactDOM from "react-dom";


import Header from "nav/Header";
ReactDOM.render(<Header />, document.getElementById("app"));

Then Webpack would not have the opportunity to load the nav remote before that code
executes, and it would result in a runtime error.

All of the examples in this book are bootstrapped.

Practical Module Federation Page 47 of 166


WHAT’S NEXT
In the next chapter, we discuss the different state sharing options available to you in
federated React applications.

Practical Module Federation Page 48 of 166


5.
REACT STATE SHARING OPTIONS

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries.

Once you have a way to share code between applications, whether that be through NPM
modules, Micro-FE frameworks, or through Module Federation, the inevitable next
conversation that arises is how to share state between the host and the remote
modules, or even between remotes. This chapter explores some options on how to
share state.

GETTING THE SETUP RIGHT


Let’s continue using the two example applications, home and nav, that we’ve been
using thus far. Now, let’s experiment a little by removing the contents of the shared
array in the ModuleFederationPlugin configuration in webpack.config.js. So
that it would look like this:

new ModuleFederationPlugin({
...
shared: [],
}),

Practical Module Federation Page 49 of 166


Then restart the applications using yarn start and you’ll notice that it still works.
This is odd. We can even see the react library being imported along with the header in
the browser’s network tab.

Now, let’s break this application by making one small change to the header.

import React from "react";

const Header = () => {


const [stateIDontUse] = React.useState(null);
return <header style={{ fontSize: "xx-large " }}>I'm the header!</
header>;
};

export default Header;

The original Header was stateless, but just by adding a useState hook the Header
now throws an error and we can see this message in the console logs:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the
body of a function component. This could happen for one of the following
reasons:
1. You might have mismatching versions of React and the renderer (such as
React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

This error is telling us what we already know; there are two react library instances on
the page. However, we didn’t get the error until we tried to use hooks. That’s because

Practical Module Federation Page 50 of 166


hooks are internally stored as global state within the react library. So, everything was
fine until we used even the most basic hook, and boom, we got an error.

It’s important to understand this behavior, and the importance of sharing all of your
runtime dependencies for this reason: any state sharing library is likely to have global
state or use features of the framework that require global state. You always need to
share these libraries.

To fix this we use this configuration when we share react and react-dom.

const deps = require("./package.json").dependencies;

...

shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},

This defines react and react-dom as singletons which tells Webpack to make sure
that there is never more than one copy of react or react-dom on the page at a time.

With some understanding of how these libraries can go right and wrong, it’s time to dig
into some specific state sharing options.

Practical Module Federation Page 51 of 166


USESTATE
The simplest and cleanest way to share state is to use useState. Let’s start with that and
change the home app so that you can add items to a cart and the Header in nav so that
it can display the count of items in the cart.

To do that, let’s first add the react library back into shared in both projects and then
re-launch if you haven’t done that already. Then let’s alter the App.jsx in home to add
some new functionality.

const App = () => {


const [itemCount, itemCountSet] = React.useState(0);
const onAddToCart = () => {
itemCountSet(itemCount + 1);
};
return (
<div>
<Header />
<div>Hi there, I'm some cool product.</div>
<button onClick={onAddToCart}>Buy me!</button>
<div>Cart count is {itemCount}</div>
</div>
);
};

This new version of the App component has its own state that contains a cart count.
After pressing the “Buy me!” button five times, the result in the browser looks like the
figure below:

Practical Module Federation Page 52 of 166


From here, we can send this state onto the Header by adding a property:

<Header count={itemCount} />

And we can change the Header to display that value.

const Header = ({ count }) => {


const onClear = () => {};
return (
<header style={{ fontSize: "xx-large " }}>
<span>Header - Cart count is {count}</span>
<button onClick={onClear}>Clear</button>
</header>
);
};

The result is that we now have a Header from the nav application that tracks with the
cart count state that’s stored in the home application.

Practical Module Federation Page 53 of 166


To make it bi-directional, let’s pass through an onClear callback to enable the cart
clearing button in the Header. So in the home application we change the Header
component reference to:

<Header count={itemCount} onClear={() => itemCountSet(0)} />

And the Header implementation changes to:

const Header = ({ count, onClear }) => (


<header style={{ fontSize: "xx-large " }}>
<span>Header - Cart count is {count}</span>
<button onClick={onClear}>Clear</button>
</header>
);

Practical Module Federation Page 54 of 166


That completes the round circuit where have state store in the application that’s read
and updated from a React component in a Federated Module.

It’s not surprising this worked. This is the simplest and most straightforward way to
share state in React. However, it does provide a solid baseline to compare with some
other approaches.

REDUX
If you’ve used the React framework, you’ve heard of Redux. It’s easily the most
commonly used state manager. To see how Redux works in the Module Federation
context, we start with simple project we finished in chapter four. From there, we add
redux and react-redux to home and nav applications.

% cd packages/home
% yarn add redux react-redux
% cd ../nav
% yarn add redux react-redux

From there, we go into the App.jsx file and implement a new Redux store.

Practical Module Federation Page 55 of 166


import { createStore } from "redux";

function cartReducer(state = { count: 0 }, action) {


switch (action.type) {
case "INCREMENT":
return {
...state,
count: state.count + 1,
};
case "RESET":
return {
...state,
count: 0,
};
default:
return state;
}
}

const store = createStore(cartReducer);

This very simple Redux store has an initial state with a count of zero. There are two
actions: INCREMENT will add one to the count and RESET will set the count to zero.

Our next step is to import and add a Provider to the application.

import { Provider, connect } from "react-redux";

Then we change the ReactDOM.render invocation to wrap our App in a Provider with
the store.

Practical Module Federation Page 56 of 166


ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("app")
);

This is a critical step because this Provider is how we are going to get the state to any
of the consumers. The big question is whether it will work from the home application
through Module Federation into the nav app.

Before we get there, we need to adjust the App React component to use this store.

const App = connect((state) => state)(({ count, dispatch }) => {


const onAddToCart = () => {
dispatch({
type: "INCREMENT",
});
};
return (
<div>
<Header />
<div>Hi there, I'm some cool product.</div>
<button onClick={onAddToCart}>Buy me!</button>
<div>Cart count is {count}</div>
</div>
);
});

The connect function, provided by React-redux, is a high order function that takes the
App component and subscribes it to the store. It extracts fields from the state and

Practical Module Federation Page 57 of 166


sends them as props to the component as well as providing a dispatch function to
send actions to the store.

The component now looks at count, which connect takes from the store and sends to
the component as a property. The event handler uses the dispatch function to send the
INCREMENT action back the store.

The only thing left to do is update the Header so that it connects to the store properly.
Let’s head over to the nav application and alter the App.jsx file to look like this:

import React from "react";


import { connect } from "react-redux";

const Header = ({ count, dispatch }) => (


<header style={{ fontSize: "xx-large " }}>
<span>Header - Cart count is {count}</span>
<button onClick={() => dispatch({ type: "RESET" })}>Clear</button>
</header>
);

export default connect((state) => state)(Header);

It works just fine. It turns out that you can access your Redux store seamlessly in React
components integrated using Module Federation.

Practical Module Federation Page 58 of 166


MOBX
Mobx is another popular state manager for React applications. It works on an
observables model. To try out Mobx in Module Federation, we first need to install it in
both the home and nav applications.

% cd packages/home
% yarn add mobx mobx-react
% cd ../nav
% yarn add mobx mobx-react

Then we create the store, which is as easy as adding this code to the App.jsx file:

import { observable } from "mobx";

const store = observable({


count: 0,
});

We then pass the store to the App component by re-writing the ReactDOM.render
invocation.

ReactDOM.render(<App store={cartStore} />, document.getElementById("app"));

Practical Module Federation Page 59 of 166


Then to finish the implementation of the App, we wrap it an observer.

import { observer } from "mobx-react";

...

const App = observer(({ store }) => {


return (
<div>
<Header store={store} />
<div>Hi there, I'm some cool product.</div>
<button onClick={() => (store.count = store.count + 1)}>
Buy me!
</button>
<div>Cart count is {store.count}</div>
</div>
);
});

We’ve removed the state from App and now we just use the count as a regular
Javascript object. To set it, all we need to do is just set its value and the observable
handles all the rest. We also need to send the store to Header as a property.

Practical Module Federation Page 60 of 166


Then over in the Header implementation in the nav application, we also wrap that in
an observer.

import React from "react";


import { observer } from "mobx-react";

const Header = observer(({ store }) => (


<header style={{ fontSize: "xx-large " }}>
<span>Header - Cart count is {store.count}</span>
<button onClick={() => (store.count = 0)}>Clear</button>
</header>
));

export default Header;

Once again, we display the data simply by showing the value. We reset the value just by
setting it to zero. This all works seamlessly across these components brought together
through Module Federation.

RECOIL
Recoil is a novel state management system from FaceBook that is designed to extend
the existing useState and useContext mechanisms by providing state that is
“orthogonal to the rendering tree”. Meaning that useState and useContext relate
directly to where they are used in the React tree. When useState or useContext
change anything below where they are defined in the React tree, will be re-rendered.
Recoil, on the other hand, stores its state as “atoms” that are held apart from the tree.
When an atom changes state then only those components that are using it will re-
render.

Practical Module Federation Page 61 of 166


Let’s try it out and you can see it in action. The first step is to install recoil in both
the home and nav applications.

% cd packages/home
% yarn add [email protected]
% cd ../nav
% yarn add [email protected]

At the time of this writing, only version 0.0.7 of recoil was compatible with this
approach. With that done, we can launch the servers with yarn start. Then we
create a new file in the src directory of the home app called atoms.js with these
contents.

import { atom } from "recoil";

export const cartCount = atom({


key: "cartCount",
default: 0,
});

This creates a new “atom” that holds the cart count value. In the recoil model, state is
stored as atoms and components subscribe to those atoms. If an atom's value changes
then any component that subscribes to it will be updated. You can have as many atoms
as you like. You can use another mechanism called a “selector” to create composite
atoms from a collection of other atoms.

Practical Module Federation Page 62 of 166


To use the cartCount atom, we return to the App.jsx file and import both recoil and
the atoms:

import { useRecoilState, RecoilRoot } from "recoil";


import { cartCount } from "./atoms";

Then we wrap the App component in a RecoilRoot as part of our


ReactDOM.render.

ReactDOM.render(
<RecoilRoot>
<App />
</RecoilRoot>,
document.getElementById("app")
);

Practical Module Federation Page 63 of 166


Next, we change useState to useRecoilState and point it at the cartCount atom.

const App = () => {


const [itemCount, itemCountSet] = useRecoilState(cartCount);
const onAddToCart = () => {
itemCountSet(itemCount + 1);
};
return (
<div>
<Header />
<div>Hi there, I'm some cool product.</div>
<button onClick={onAddToCart}>Buy me!</button>
<div>Cart count is {itemCount}</div>
</div>
);
};

One of the great things about Recoil is how it’s almost a plug and play replacement for
useState. What do you do if more than one component need the state? Make an atom
and point both components at it by just replacing useState with useRecoilState.
It’s very clean.

Practical Module Federation Page 64 of 166


Notice that the Header now no longer has any properties. To make that work, we first
need to export the atoms from home by altering the webpack.config.js.

new ModuleFederationPlugin({
...
exposes: {
"./atoms": "./src/atoms",
},
...
}),

Then over in the nav application, we need to alter the webpack.config.js to add
home as a remote.

new ModuleFederationPlugin({
...
remotes: {
home: "home@http://localhost:8080/remoteEntry.js",
},
...
}),

This creates a bi-directional linkage between home and nav where the home
application consumes the Header from nav and the nav application consumes
atoms from home. And it all works!

Practical Module Federation Page 65 of 166


To finish the example, we use the useRecoilState hook to both get and set the value
of the cartCount atom:

import React from "react";


import { useRecoilState } from "recoil";
import { cartCount } from "home/atoms";

export default () => {


const [count, setCount] = useRecoilState(cartCount);
return (
<header style={{ fontSize: "xx-large " }}>
<span>Header - Cart count is {count}</span>
<button onClick={() => setCount(0)}>Clear</button>
</header>
);
};

It’s as easy as that. When the user clicks on the button, the atom is updated and all the
consumers of the atom are updated automatically.

RXJS
RxJS is a very popular event bus library. It’s not often used as a state mechanism, but
there is no reason why it can’t be done.

Practical Module Federation Page 66 of 166


Let’s try it out and you can see it in action. The first step is to install rxjs in the home
application.

% cd packages/home
% yarn add rxjs

And then we need to update the webpack.config.js to share it.

new ModuleFederationPlugin({
...
shared: { ... }, // Shared React dependencies including rxjs,
}),

Though this is optional in this case because home will be creating RxJS subjects which
are self contained observable objects that don’t require additional methods from the
library to use them.

The next thing to do is to import Subject from rxjs and to create a Subject that will
hold the cart count.

import { Subject } from “rxjs";

const count = new Subject(0);

Subjects in RxJS are event busses. And in this case we are starting the topic off with the
value of zero. You can post a new value by using the next method on the object. And
you can subscribe to the bus by calling to subscribe method.

Practical Module Federation Page 67 of 166


To integrate it with the App component in the App.jsx file in home we will first create
a new custom React hook called useSubject. It’s defined like this:

const useSubject = (subject, initialValue) => {


const [value, valueSet] = React.useState(initialValue);
React.useEffect(() => {
const sub = subject.subscribe(valueSet);
return () => {
sub.unsubscribe();
};
}, [subject]);
return value;
};

This hook takes the subject and its initial value and uses a standard useState to hook to
track its state. It then subscribes to that subject and updates the state any time it
changes.

Passing in the intialValue is a hack because we didn’t use a BehaviorSubject


that can return the most recently posted value at any time. There are several different
types of subjects each with different semantics. I chose Subject because it’s the simplest
type and I wanted to show that it works with the simplest possible contract.

Practical Module Federation Page 68 of 166


So, now that we have our hook we can use it in the App component.

const App = () => {


const itemCount = useSubject(count, 0);

const onAddToCart = () => count.next(itemCount + 1);

return (
<div>
<React.Suspense fallback={<div />}>
<Header count={count} onClear={() => count.next(0)} />
</React.Suspense>
<div>Hi there, I'm some cool product.</div>
<button onClick={onAddToCart}>Buy me!</button>
<div>Cart count is {itemCount}</div>
</div>
);
};

We use the useSubject hook we just created to get the current value of items in the
cart. And then we use the next method on the count subject to increase its value, or set
it to zero in the case of the onClear.

Now we could just send itemCount to the Header, but instead let’s send the subject
so that it too can subscribe to it. To do that we change Header.js in the nav project.
The result is shown in the code fragment below:

Practical Module Federation Page 69 of 166


const Header = ({ count, onClear }) => {
const itemCount = useSubject(count, 0);
return (
<header style={{ fontSize: "xx-large " }}>
<span>Header - Cart count is {itemCount}</span>
<button onClick={onClear}>
Clear
</button>
</header>
);
};

And we reuse the same useSubject code from home, which we could share via NPM
module or via Module Federation.

Using RxJS as an Analytics Bus


Another, probably more appropriate use of RxJS would be for something like an
analytics bus. Where any component could post events to the bus that would then be
sent to an analytics service.

To set that up we first create a new file in home named analytics.js with these contents:

import { Subject } from "rxjs";

const analyticsBus = new Subject();

export default analyticsBus;

Practical Module Federation Page 70 of 166


The next step is to expose that file (module) in the webpack.config.js:

exposes: {
"./analytics": "./src/analytics",
},

That allows us to import it using the name home, like this in App.jsx.

import analyticsBus from "home/analytics";

It’s important that we import it by referring to home to ensure that both the App
component and the Header component get exactly the same analytics subject.

Now that we have it imported we can attach a console function to it using subscribe.

analyticsBus.subscribe((evt) => {
console.log(`analytics: ${JSON.stringify(evt)}`);
});

Practical Module Federation Page 71 of 166


Next we can start to instrument our code, like the addToCart handler:

const onAddToCart = () => {


const value = itemCount + 1;
analyticsBus.next({ type: "addToCart", value });
count.next(value);
};

And we can also add it over in the Header:

<button
onClick={() => {
analyticsBus.next({ type: "onClear" });
onClear();
}}
>
Clear
</button>

Using RxJS for an analytics event bus in a way that’s more in-line with its original
purpose. It also opens up the opportunity to put loggers, filters, mutations and much
more into the bus to work the analytics events in whatever way you please. RxJS
provides a lot of tooling for that.

HOW TO CHOOSE
The choice of a state manager is highly dependent on the type of application you're
building. Here are some recommendations:

Practical Module Federation Page 72 of 166


For applications that will be using Module Federation to bring in code to extend the
existing interface, like a customer extensible dashboard, then using a conventional
useState approach with property drilling and well-defined callbacks is
recommended. This provides the safest and most well-defined interface without
additional package requirements.

For an internal dashboard, where the modules will pick and choose between a variety
of data, then Recoil seems like an interesting solution because it would limit the re-
renders to just those components subscribed to the specific atom that is being updated.

An e-Commerce application, or any application that has very limited data sharing
requirements (user identity, cart contents, etc.) between federated modules using
either a few Recoil atoms or a small Redux store would work well.

There are many state managers in the React ecosystem and this chapter has only
covered a few. If you're starting with a new project, you should develop a set of
requirements and then evaluate and run proof of concepts of several solutions so you
can evaluate how well they match your requirements and how the developer experience
feels.

One of the great parts about Module Federation is that it’s not boxing you into any
particular state management solutions. The industry standard solutions that work in
your non-federated React application work just as well in a federated application.

Practical Module Federation Page 73 of 166


WHAT’S NEXT
Module Federation is about sharing more than components and state--any type of
Javascript can be shared. In the next chapter, we cover multiple techniques to add
resilience to any type of function or data that’s shared by Module Federation.

Practical Module Federation Page 74 of 166


6.
RESILIENT FUNCTION AND DATA SHARING

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries.

One of the most unique and important aspects of Module Federation is that you can
share anything that can be represented as a Javascript module. This includes:

- Primitives - Numbers, strings, Dates, etc.


- Arrays - Standard Javascript arrays
- Objects - From regular objects to JSON that’s transpiled by babel
- Functions - And not just React component functions
- Classes - Which are really just functions, but need to be invoked with the new
operator

To emphasize this point, we’ll walk through some examples and show how they are
exported and consumed, and also show different ways to manage them safely.

Practical Module Federation Page 75 of 166


THE LOGIC PROJECT
To experiment with different types of modules to federate, we’ve created an application
named logic that exposes modules with several different types of signatures. The
webpack.config.js exposes all of these relevant files.

new ModuleFederationPlugin({
name: "logic",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./analyticsFunc": "./src/analyticsFunc",
"./arrayValue": "./src/arrayValue",
"./classExport": "./src/classExport",
"./objectValue": "./src/objectValue",
"./singleValue": "./src/singleValue",
},
shared: [],
}),

This is then consumed by a home project that works with the different types of data.

PRIMITIVE VALUES
Let’s start with the simplest case, the single value. Looking at the singleValue.js
file we see it implemented as just a default return of a string.

export default "single value";

Practical Module Federation Page 76 of 166


That single value could be any primitive, a number, a boolean, whatever.

In the home application, we then consume it using an asynchronous import within a


useEffect hook that runs only once.

const SingleValue = () => {


const [singleValue, singleValueSet] = React.useState(null);

React.useEffect(() => {
import("logic/singleValue")
.then(({ default: value }) => singleValueSet(value))
.catch((err) => console.error(`Error getting single value: ${err}`));
}, []);

return <div>Single value: {singleValue}.</div>;


};

This code creates some state to hold the value using a useState hook. Then it uses the
useEffect hook to start the import. The import can either return successfully, in
which case we get back an object with the default key that we map to value and
then use to set the singleValue. In case of an error, we send that error to the console.

At the end, we render the result into a <div> tag in our application. The result looks
like the screenshot below.
What this shows is that importing values, of any type, just requires an asynchronous
import to bring in safely. You can use synchronous imports in some cases, but to build
a resilient code base you should import the code asynchronously and handle the case
when you are unable to get the code you need.

Practical Module Federation Page 77 of 166


Arrays and objects work exactly the same as the single value case and you can have a
look at the book code to see this for yourself.

FUNCTIONS
It starts to get more interesting as we look at functions. Let’s take an example of an
analytics sending function. The example code defines it like this:

export default (msg) => console.log(`Analytics msg: ${msg}`);

This is exported as nav/analyticsFunc. We want to be able to run it at any time.


The simplest alternative is to write a small wrapper function like this:

Practical Module Federation Page 78 of 166


const analyticsFunc = import("logic/analyticsFunc");
const sendAnalytics = (msg) => {
analyticsFunc
.then(({ default: analyticsFunc }) => analyticsFunc(msg))
.catch((err) => console.log(`Error sending analytics value: ${msg}`));
};

Here is the interesting thing about promises, they will resolve immediately if the
promise has already been resolved. In this case, you can send as many calls as you like
to this function and once it resolves the first time it will unlock the rest and send
everything out.

If you want something more generic, you can create a function that takes an import
with a function as the default and returns a function that you can call in the same way
as sendAnalytics above.

const createAsyncFunc = (promise) => (...args) =>


promise
.then(({ default: func }) => func(...args))
.catch((err) =>
console.log(`Error sending analytics value: ${JSON.stringify(args)}
`)
);

const sendAnalytics = createAsyncFunc(import("logic/analyticsFunc"));

This high order createAsyncFunction takes an asynchronous import promise and


returns a function that takes whatever arguments you have and sends them to the
function once it has resolved.

Practical Module Federation Page 79 of 166


If you want to queue up messages to the function until it resolves, we can create a
different high order function that maintains a queue of messages to go to the function
before it resolves.

const queuedFunction = (funcPromise) => {


let queueFunc = null;
const queue = [];
let pending = false;

return (msg) => {


if (queueFunc) {
queueFunc(msg);
} else {
queue.push(msg);

if (!pending) {
pending = true;
funcPromise
.then((func) => {
queueFunc = func;
queue.forEach(queueFunc);
queue = [];
})
.catch((err) => console.log(`Error getting queued function`));
}
}
};
};

This function takes an asynchronous import promise, just like the one above.
However, until it resolves, it maintains a queue of messages that it then sends to the
function using the forEach.

Practical Module Federation Page 80 of 166


To create the sendAnalytics function using this high order queueFunction
function, you call it this way:

const sendAnalytics = queuedFunction(


import("logic/analyticsFunc").then(({ default: func }) => func)
);

CLASSES
The last thing to try out is a non-React class. The test class in the logic application is
defined this way.

class MyClass {
constructor(value) {
this.value = value;
}

logString() {
console.log(`Logging ${this.value}`);
}
}

export default MyClass;

Practical Module Federation Page 81 of 166


To bring this into the home app, we create a function called newClassObject that
takes arguments and sends them to the constructor once we have it.

const classExport = import("logic/classExport");


const newClassObject = (...args) =>
classExport
.then(({ default: classRef }) => {
return new classRef(...args);
})
.catch((err) => console.log(`Error getting class: ${err}`));

This code starts the import and gets a promise back. Then it creates a function that
uses that promise and either throws an error if it catches or, if it works, returns a new
object with the values it’s given as arguments.

You invoke it and monitor it as a promise, as in the code below.

newClassObject("initial value").then((theObject) => {


theObject.logString();
});

And the result is that you see in the console is:

Logging initial value

Exciting stuff! This shows that you can safely consume and create objects from classes
exported from remote modules.

Practical Module Federation Page 82 of 166


The fact that Module Federation can share any type of Javascript code opens up a
world of possibilities for your projects. Here are some ideas for you:

- A/B testing code - Code for different test variants that you can load on the fly
using asynchronous imports
- Feature flags - Javascript objects containing feature flags or settings
- i18n strings - The localization strings for different languages, lazily loaded
- Persistent state management - Javascript code to manage and store persistent
state in local storage on the client

WHAT’S NEXT
Now that we know how Module Federation works and how to export and import any
kind of code safely, the next chapter will cover how to load Federated Modules
dynamically.

Practical Module Federation Page 83 of 166


7.
DYNAMICALLY LOADING FEDERATED MODULES

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries.

So far, the relationship between our host applications and our remotes has been fixed.
For example, the home page includes a reference to the nav app where it gets the
remote Header module. But you don’t have to do this. You can dynamically load the
remoteEntry.js onto the page and then execute your own version of import to run
the code.

The chapter 7 book code has three directories;

- widget - This is a single React component that is shared as a remote module in the
scope of widget and under the name Widget.
- home-wp5 - This is a host application, running on Webpack 5, that dynamically
loads and runs widget.
- home-wp4 - This is a host application, running on Webpack 4, that dynamically
loads and runs widget.

Practical Module Federation Page 84 of 166


We’ll start with having a look at the widget directory and the webpack.config.js
within that directory.

new ModuleFederationPlugin({
name: "widget",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Widget": "./src/Widget",
},
shared: { ... }, // Shared React dependencies
}),

The important elements here are that we export the Widget component and that the
name of this application is widget. It’s also good to see that we are running and
exporting this remote module off of port 8082.

output: {
publicPath: "http://localhost:8082/",
},
devServer: {
port: 8082,
},

The React widget itself is super simple as shown in the code below.

import React from "react";

export default () => <div>I'm a cool widget</div>;

Practical Module Federation Page 85 of 166


You can spruce it up, if you like. For now, it’s time to run yarn start in the widget
directory and get the Webpack development server up and running.

Over in the home-wp5 directory, we have the host for the Widget remote module. If we
have a look at its webpack.config.js, we can see it only uses
ModuleFederationPlugin to share react, other than that, this is just a standard
Webpack 5 configuration.

Practical Module Federation Page 86 of 166


The real action happens over in App.jsx where we define a new React component
called System that loads the remoteEntry.js script, gets the code in the module and
then renders it.

function System(props) {
const { ready, failed } = useDynamicScript({
url: props.system && props.system.url,
});

...

const Component = React.lazy(() =>


new Promise((moduleResolve) => {
const react = require("react");
const legacyShareScope = {
react: {
[react.version]: {
get: () =>
new Promise((reactResolve) => {
reactResolve(() => react);
}),
loaded: true,
from: "webpack4",
},
},
};
new Promise((contaierResolve) => {
contaierResolve(window[scope].init(legacyShareScope));
}).then(() => {
window[scope].get(module).then((factory) => {
moduleResolve(factory());
});
});
})
);

Practical Module Federation Page 87 of 166


return (
<React.Suspense fallback="Loading System">
<Component />
</React.Suspense>
);
}

The useDynamicScript hook is a custom React hook that, given a URL, in this case
for the remoteEntry.js, adds a script tag to the page and then sets ready to true
when the script is loaded (or failed to true if that failed).

After that, we use React.lazy and React.Suspense as we have in many other


examples in this book. However, where we would normally use an asynchronous
import() statement, we are instead using loadComponent.

This function is doing what import does. It’s using the scope (widget) and module
(Widget) to get the module factory from the globally mounted object. Then it invokes
that factory and returns the module. This is just what React.lazy wants to see.

The reason that react is called out in particular is that React is a singleton that requires
that there is only a single instance on the page at a time. So this code initializes the
scope with the host pages React library instance so that the widget module avoids
loading its’ own version.

The result when we run yarn start in the home-wp5 directory (while the server is also
running in widget) looks like this.

Practical Module Federation Page 88 of 166


It might not look like much, but when combined with all of the potential types of code
and visual components that we can use with Module Federation, the potential for what
we can do if we load code from anywhere is astonishing.

However, what happens if you aren’t on Webpack 5. Can you still do this?

CONSUMING FEDERATED MODULES FROM WEBPACK 4


It’s great that we can consume federated modules on Webpack 5, but Webpack is
complex and sometimes migration can be an involved process. Can we use federated
modules today even in Webpack 4? Absolutely!

Practical Module Federation Page 89 of 166


Looking at the home-wp4 directory and the package.json within that directory you
can see that the Webpack version is currently 4.

"devDependencies": {
...
"webpack": "^4.43.0",
...
},

The webpack.config.js file for this project has no mention of Module Federation as
it was not available in version 4.

In the src/App.jsx file, we can see the implementation for the host React
component. It has the same useDynamicScript hook that we used in the Webpack 5
version just to get the code onto the page.

Practical Module Federation Page 90 of 166


The difference is in the DynamicWidget component that uses useDynamicScript to
get the code and then uses the factory functionality to give React.lazy the Widget
module to run.

const DynamicWidget = ({ url, scope, module, ...props }) => {


const { ready, failed } = useDynamicScript(url);

...

window[scope].override(
Object.assign(
{
react: () => Promise.resolve().then(() => () => require("react")),
},
global.__webpack_require__ ? global.__webpack_require__.o : {}
)
);

const Component = React.lazy(() =>


window[scope].get(module).then((factory) => {
const Module = factory();
return Module;
})
);

return (
<React.Suspense fallback="Loading System">
<Component {...props} />
</React.Suspense>
);
};

The React.lazy and React.Suspense are basically the same as in the Webpack 5
example. The big difference is that we are manually setting the overrides for the

Practical Module Federation Page 91 of 166


scope. We are telling, with widget remote module, that we already have react and that
it should use our version instead of downloading its own version. This part is critical
because otherwise there would be two copies of React on the page and we would have
issues like those described in detail at the start of chapter 5. All of your shared packages
need to be listed in this override call.

Once that’s all set up, we can bring in the Widget from the widget application just
like any other React component.

const App = () => (


<div>
<DynamicWidget
url={"http://localhost:8082/remoteEntry.js"}
scope={"widget"}
module={"Widget"}
/>
<div>Hi there, I'm React from React in Webpack 4.</div>
</div>
);

It’s a testament to the stability of the Webpack architecture that we can use cutting
edge features from one version reliably in the previous version.

WHAT’S NEXT
In the next chapter, we dig into how to share packages properly across federated
modules.

Practical Module Federation Page 92 of 166


8.
SHARED LIBRARIES AND VERSIONING

If you take a look at any Javascript file in your application, it’s probably going to start
with at least a few, if not a lot, of import statements, many using node modules or
packages. That’s just the nature of the Javascript ecosystem. It does make for a
challenge when we share code because the piece of code that brings in those imports
needs those packages, and needs them at the right versions.

This is why getting the shared key in the ModuleFederationPlugin right is so


critical.

- Duplication - Without sharing the packages, you get duplication and this will slow
down the experience.
- Version mismatch - If we don’t get the sharing setup correctly, then different
remote modules could end up consuming incorrect versions, which could lead to
crashes.
- Singletons and internal state - Many libraries, particularly frontend libraries,
have internal state that is required in order to run properly. That means there can
only be one instance of that library running at a time. We call this a “singleton”.

You can list the libraries to share between host and remote as easily as this:

shared: ["lodash"],

Practical Module Federation Page 93 of 166


This is fine for proof of concepts and prototypes, or even in production if you have a
mono-repo with managed versioning across all the applications. However, this would
not scale to any large production context, because some libraries, like react and
react-dom have special requirements.

This chapter walks through the different ways to configure the shared key with
recommendations on when to use each of the different options.

SPECIFYING VERSIONS
There are three ways to specify shared versions. The first way is the array syntax we’ve
been using thus far with the names of each of the libraries to share. The second way is
an object syntax with the name of the library as the key, and the version (using
semantic versioning format) as the value. It looks like this:

"shared": {
"react": "^16.12.0",
"react-dom": "^16.12.0"
}

If that looks familiar, that’s because it looks exactly the same as it does in
package.json and it works exactly the same way. In fact, because
webpack.config.js is just a Javascript script you could do:

"shared": require('./package.json').dependencies

Practical Module Federation Page 94 of 166


That is perfectly valid and potentially even preferable since it automatically shares out
any runtime dependencies with their versions.

However, you can be even more precise about the control by using the third way, where
you hint Webpack in exactly how to manage the shared dependency.

FALLBACK
Webpack has a fallback behavior if the package provided as shared doesn’t match the
requirements specified in the ModuleFederationPlugin or SharePlugin
configuration.

Here is an example: both applications A and B consume lodash but at different


versions. Both have copies of loads available to them as potential fallbacks. A loads first
and brings in its own version (1.0) of lodash. B is starting to load and looking for
lodash but requires a later version (2.0). With the right version hinting to Webpack,
the B remote module will “fall back” on its version of lodash because of that version
mismatch.

This fallback feature enables versioning deployments at scale because not all
applications will be required to push new versions at the same time.

WEBPACK SHARING HINTS


There is another variant of the object syntax where the key is the name of the package
and the value is another object containing different hints to modify the behavior of
Webpack’s SharePlugin.

Practical Module Federation Page 95 of 166


For example, this shared configuration:

const deps = require("./package.json").dependencies;

shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},

This will inform Webpack that react and react-dom should be treated as a
singleton, meaning that under no loading circumstances should there ever be more
than one copy of react running at a time. And they also have strict version
requirements.

The sections that follow detail the different hinting options you can send to Webpack’s
SharePlugin through ModuleFederationPlugin, or directly, if you prefer that
level of control.

Practical Module Federation Page 96 of 166


import
This is the import name of the package. Here is an example:

"shared": {
"react": {
import: "react"
}
}

This will import the react package from the module reference "react".

shareKey
This is the name that will be used to identify the package within the scope. Here is an
example:

"shared": {
"react": {
shareKey: "react"
}
}

This will share the package the "default" scope under the key "react".

Practical Module Federation Page 97 of 166


shareScope
This is the scope that will be used to store the shared package. Here is an example:

"shared": {
"react": {
shareScope: "default"
}
}

This will share the package the "default" scope under the key "react".

singleton

Toggles singleton status for this shared package. If singleton is true, then only
one instance of this package should be running at any time. Here is an example:

"shared": {
"react": {
singleton: true
}
}

This enables the management of the react package as a singleton.

Practical Module Federation Page 98 of 166


strictVersion

The strictVersion flag tells Webpack to use the fallback if there is no version
match. If the singleton flag is enabled and there is no fallback, then Webpack will throw
an error. Here is an example:

"shared": {
"react": {
singleton: true,
strictVersion: true,
version: "^16.12.0"
}
}

This configuration tells Webpack that react is a singleton and it must be in the specified
semantic version of "^16.12.0", and if nothing is available then fallback or throw.

version
The version of the package. Here is an example:

"shared": {
"react": {
version: "^16.12.0"
}
}

This configuration tells Webpack that react should be used at the specified semantic
version.

Practical Module Federation Page 99 of 166


requiredVersion
The version of the package that is required, otherwise Webpack should use the fallback.
Here is an example:

"shared": {
"react": {
requiredVersion: "^16.12.0"
}
}

This configuration tells Webpack that react must be used at the specified semantic
version.

As you can see, a bunch of thought has gone into making a reliable versioning
mechanism for shared packages in Module Federation.

WHAT’S NEXT
Now that we’ve got a good sense of different ways to load shared packages and run code
from remote modules using Module Federation, let’s look at the exciting architectural
pattern of Fully Federated Applications that is unique to the Module Federation
ecosystem.

Practical Module Federation Page 100 of 166


PART TWO
PRACTICAL USE CASES

Practical Module Federation Page 101 of 166


9.
RESILIENT HEADER/FOOTER

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries. There is also a Blue Collar Coder video associated with this practical use
case.

A very common architectural problem occurs in the move from a monolithic


architecture where we have one big app with one huge frontend codebase, to a micro-
site architecture where we have a bunch of smaller NodeJS applications. We gain the
ability to release quickly and independently, but we lose code sharing. NPM is great but
the process of getting a new NPM module rolled out and consumed is a huge source of
friction. But some things absolutely must be shared, and chief among those is the
shared Header and Footer.

Ideally we want a shared Header and Footer system with these attributes:

- Live sharing - We want all the micro-sites to update the header at the same time.
Otherwise we get an inconsistent user experience, or worse.
- Sharing without requiring version bumps - The ideal system would not
require the micro-sites to re-deploy with every update.
- Safety and resilience - The header should be absolutely safe and never take down
the applications that consume it.

The great news is that Module Federation allows us to come up with a sharing
architecture that fills all of these requirements.

Practical Module Federation Page 102 of 166


In this example we will build a React Header project that exports both an NPM
module and a Module Federation build. We then have a Home page application that
consumes both of these builds.

Home
Nav
HeaderWrapper

Header Module Federation Header


Build

FallbackHeader

NPM Build

The frontend code then uses the Module Federation build first, then if an error occurs,
it falls back onto the NPM code which it loads lazily. The Header from Module
Federation is exactly the same as the Header from NPM, which is aliased to
FallbackHeader in the HeaderWrapper.

The only difference is that the Header from Module Federation and the one from NPM
is that the Module Federation version is guaranteed to be up to date with the most
recently released version (i.e. live sharing). Where the NPM version will be whatever
version was imported the last time the Home page was built.

A fun way to think about this approach is like pitons in rock climbing. Module
Federation is the climbing up the rock, but the NPM build is like the piton that keeps
your application from falling too far if your grip slips.

Now that we know what we are building let’s take a look at the example code for this
chapter on Github.

Practical Module Federation Page 103 of 166


The first place to look is in the package.json of the nav project located in
packages/nav directory. This is the standard package.json we’ve used throughout
this book, with small modifications in the package scripts and the addition of a main
key.

"scripts": {
"build": "npm run build:mf && npm run build:npm",
"build:mf": "webpack --mode production",
"build:npm": "babel src --out-dir build”,
...
},
"main": "./build/index.js",

Now the build creates both a dist directory that has the contents of the Module
Federation build, but also a build directory that has the NPM build. The main key
then points the NPM consumer at the babel transpiled index.js file.

The index.js file looks like this:

import Header from "./Header";

export { Header };

Practical Module Federation Page 104 of 166


And the Header implementation file is a very simple React component.

import React from "react";

const Header = () => (


<div
style={{
padding: "1em",
background: "green",
color: "white",
fontSize: "xx-large",
}}
>
HEADER!
</div>
);

export default Header;

In the video associated with this code we use the background color to differentiate
between an older NPM build of the nav project and the current live Module Federation
code.

Now that we understand the nav project and the Header component we can look to
the consuming home application located in packages/home. The first thing to
understand is that the overall project here, located in the root package.json, is a
workspaces project. So it’s managing the NPM linking between the home and nav
projects.

Practical Module Federation Page 105 of 166


You can see this in the package.json of the home app which references the nav at
version 1.0.0.

"dependencies": {
"nav": "1.0.0",
"react": "^16.13.0",
"react-dom": "^16.13.0"
}

To import the Module Federation version of the nav project the webpack.config.js in
the home project has this ModuleFederationPlugin configuration.

new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
remotes: {
"mf-nav": "nav@http://localhost:8081/remoteEntry.js",
},
exposes: {},
shared: { ... }, // Shared React dependencies
}),

The interesting part here is that we are importing the nav remote but we are aliasing it
to mf-nav within this project. That way in the code we can actively decide whether we
are referencing the NPM version of the Header or the Module Federation version of
the Header.

Practical Module Federation Page 106 of 166


In fact you can see that right away in the src/App.jsx file where we lazily import
both versions of the Header, like so:

const FallbackHeader = React.lazy(() => import("nav/build/Header"));


const Header = React.lazy(() => import("mf-nav/Header"));

We lazily import both versions, but for different reasons. The FallbackHeader,
which is sourced from the NPM library, is lazily loaded because we don’t want to bring
in the NPM header unless the Module Federation version fails. The Module Federation
Header is lazily loaded because a failure to do so would bring down the application if
the import failed.

Practical Module Federation Page 107 of 166


The HeaderWrapper is a React class because a React “Error Boundary” cannot be a
functional components. It has two jobs, first is to catch any errors, which is handled by
this code:

class HeaderWrapper extends React.Component {


constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidCatch() {}

...
}

Practical Module Federation Page 108 of 166


This render function renders the two different versions based on whether or not there
has been an error.

render() {
if (this.state.hasError) {
return (
<React.Suspense fallback={<div>Loading fallback header</div>}>
<FallbackHeader />
</React.Suspense>
);
}

return (
<React.Suspense fallback={<div>Header loading</div>}>
<Header />
</React.Suspense>
);
}

In the case of an error we lazily render the FallbackHeader, and otherwise we render
the Module Federation Header (which may cause an error and trigger the rendering of
the FallbackHeader).

To try this all out first run yarn and yarn build at the top level of the project. From
there you can run yarn start in each of the packages directories; home and nav. To
check resilience simply stop the yarn start process on the nav project to see the
home application fall back onto the NPM version.

Module Federation also provides additional levels of fallbacks for the Module
Federation code by offering an array of externals. If the first one fails the second will be
tried and so on.

Practical Module Federation Page 109 of 166


10.
A/B TESTS

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries. There is also a Blue Collar Coder video associated with this practical use
case.

There are plenty of places to start on your journey towards implementing Module
Federation across your org, and one of those is using it to implement A/B tests, and a
shared A/B test manager.

A/B tests are a way of seeing true customer preferences through data. You take two
variations on the same piece of interface. In the case of the example code we are
comparing two different image borders:

Variant A has a blue border, and variation B has a green dashed border. In production
this choice would be linked to some meaningful action, for example, clicking an
“Adoption” button to start the adoption process.

Practical Module Federation Page 110 of 166


There are three parts to this system:

- A/B Test Code - The code that renders the different variations
- Test Manager - Some code, or a service, that picks which option the customer will
be presented to the customer
- Results Manager - This is the system that receives the analytics messages
indicating which choice the customer choose, tabulates the results and chooses a
“winner” after a statistically significant number of results have been gathered.

In this example we will be focused on the first two of these, the A/B test code and the
test manager. In reality however, the test and results manager roles are most often
filled by third party systems as services.

Practical Module Federation Page 111 of 166


We start off the example by looking at the packages/home directory which contains a
React application based on the starter kit. Within that project in the src directory are
the FrameA and FrameB components, both of which look like this:

import React from "react";

export default ({ src, style, ...props }) => (


<>
<div>Variant A:</div>
<img
src={src}
style={{
...style,
padding: "1em",
border: "5px solid blue",
}}
{...props}
></img>
</>
);

The FrameA and FrameB components only vary by the contents of the initial <div>
tag and the styling around the image.

In the host App.jsx component we first lazily load these components.

const FrameA = React.lazy(() => import("host/FrameA"));


const FrameB = React.lazy(() => import("host/FrameB"));

Practical Module Federation Page 112 of 166


We lazily load them because we don’t want to load the variation unless it’s going to be
displayed. This isn’t a huge concern in this case since the components are pretty small.
But in real world experiments the code sizes can be larger.

We also load the VariantChooser from the ab-manager remote and invoke it like this:

import VariantChooser from “ab-manager/VariantChooser";

<VariantChooser
test="test1"
variations={{
a: FrameA,
b: FrameB,
}}
src="https://placedog.net/640/480?id=53"
style={{ width: 640, height: 480 }}
/>

It’s the job of the VariantChooser to roll the dice to see which variation we get, then
to render the correct variation from the variations property. While also sending along
the additional props (i.e. src and style). The test property defines the name of the
test we are running, and what the possible values are. In this case the name is test1
and the possible values are "a" and "b".

Which brings us to the other project in this monorepo and that’s the ab-manager
project which holds the list of available tests, and the VariantChooser implementation.

Practical Module Federation Page 113 of 166


In the variants.js file within packages/ab-manager we see the list of all of the
possible tests and their corresponding variations:

export default {
test1: ["a", "b"],
};

We then import that set of tests into the VariantChooser implementation:

import React from "react";


import variants from "ab-manager/variants";

const chooseVariant = (test) =>


variants[test][Math.floor(Math.random() * variants[test].length)];

const VariantChooser = ({ test, variant, variations, ...props }) => {


const [testVariant] = React.useState(variant || chooseVariant(test));
const Component = variations[testVariant];
return (
<React.Suspense fallback={<div>Loading variant</div>}>
<Component {...props} />
</React.Suspense>
);
};

export default VariantChooser;

At the top we bring in the variants. We then define a chooseVariant helper


function which just picks a random item from the test supplied. The component uses
that to choose the variant to hold it in the state. It then renders the variant within a
suspense.

Practical Module Federation Page 114 of 166


The import of the variants is interesting. We could have just imported the variants from
'./variants', since it’s located in the same directory. But by using the 'ab-
manager/variants' version we are forcing the import to go through Module
Federation. This ensures that variants.js gets split out from the
VariantChooser.jsx code. And that makes variants.js independently
deployable. So it would be possible to push a new version of the ab-manager with only
the tests changed.

To get all this working we set up Module Federation this way in the ab-manager:

new ModuleFederationPlugin({
name: "ab_mgr",
filename: "remoteEntry.js",
remotes: {
"ab-manager": "ab_mgr@http://localhost:8080/remoteEntry.js",
},
exposes: {
"./VariantChooser": "./src/VariantChooser",
"./variants": "./src/variants",
},
shared: { ... }, // Shared React dependencies
}),

It’s interesting to notice that the federated modules package is defined as ab_mgr but
we then alias the remote to ab-manager. This is because the name "ab_mgr" is
actually used as Javascript variable and a minus is not valid within a Javascript
variable. So we use the underscore instead.

We also expose the VariantChooser and variants using the exposes key.

Practical Module Federation Page 115 of 166


If we look at the webpack.config.js file in the packages/home directory where
the home application is located we see the import of the ab-manager:

new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
"ab-manager": "ab_mgr@http://localhost:8081/remoteEntry.js",
"host": "host@http://localhost:8080/remoteEntry.js",
},
exposes: {
"./FrameA": "./src/FrameA",
"./FrameB": "./src/FrameB",
},
shared: { ... }, // Shared React dependencies
}),

As well as the exposing of both of the Frame variations.

Specifying host, which is the current project, as a remote also allows us to bring in the
Frame components using standard Module Federation imports:

const FrameA = React.lazy(() => import("host/FrameA"));


const FrameB = React.lazy(() => import("host/FrameB"));

To run this example run yarn and then yarn start in the root directory. That will launch
both the home and ab-manager applications. You should then have a look at the
network tab to see that the Webpack is only bringing in the required variation.

Practical Module Federation Page 116 of 166


In the Blue Collar Coder video that accompanies this chapter we also change the values
in the variants.js file to demonstrate that you can push just the list of variations
without having to update the code in order to specify a winning variant.

Practical Module Federation Page 117 of 166


11.
LIVE PREVIEW BETWEEN APPLICATIONS

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries. There is also a Blue Collar Coder video associated with this practical use
case.

In large enterprise settings it’s no uncommon at all that you will get a division between
the customer facing portion of the site and the administration facing side of the site.
And while that’s maybe fine from an organizational standpoint, it’s not great for code
sharing, particular code that really should be shared. A decent example of that is when
you have the customer facing portion of the site rendering some data that is
administered by the admin team.

When the administration pages want to preview the data as it would appear to the
customer they can either iframe in the production site and patch the data through in a
“preview mode”, or they can just maintain a second copy of the rendering code. But
Module Federation provides a “third way” by making it easy for the customer facing
team to share rendering code with the administration team, or vice versa. And to do
that sharing live so that absolute fidelity is preserved between the customer facing site
and the administration site. A win for everyone!

Practical Module Federation Page 118 of 166


In our example system the CMS editor looks like the site pictured below:

And the corresponding page on the customer facing site looks like this:

Buddy is such a cute dog! Honestly, if you are looking for good placeholder images you
should check out placedog.net.

Practical Module Federation Page 119 of 166


There are two applications in the example code for this; cms-admin and home. In this
case the cms-admin application has both a shared content editor component, named
EmbedPage, and a shared content viewer component, called EmbedEditor.

The CMS application does not use the EmbedPage and EmbedEditor components
directly. Those are both wrapper components that connect to the REST API endpoint
using CORS so that the requests can go to a different port.

THE CMS APPLICATION


Both the cms-admin and home applications are derived from the wp5-starter-
react project. But the cms-admin project significantly alters the
webpack.config.js to extend the dev-server with a REST API to support reading
and writing JSON payloads that represent the different “pages” in the Content
Management System (CMS).

The ModuleFederationPlugin configuration should be familiar by now though;

new ModuleFederationPlugin({
name: "cms",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./EmbedPage": "./src/EmbedPage",
"./EmbedEditor": "./src/EmbedEditor",
},
shared: { ... }, // Shared React dependencies
}),

Practical Module Federation Page 120 of 166


We can take a quick look at EmbedPage to see how it retrieves content from the server
and then sends it on to the Page component for display.

import React from "react";


import { useQuery } from "react-query";

import Page from "./Page";


import { fetchPage } from "./api";

const EmbedPage = ({ page }) => {


const { data } = useQuery(
["getPage", { page }],
fetchPage("http://localhost:8080")
);
return data ? <Page {...data} /> : null;
};

export default EmbedPage;

This EmbedPage component uses the excellent react-query library to connect to the
CMS service defined in the Webpack configuration using an absolute URL.

Practical Module Federation Page 121 of 166


And the underlying Page component is a straightforward content formatter:

import React from "react";


import { Grid } from "semantic-ui-react";

const Page = ({ title, text, img1, img2, img3 }) => (


<>
<h1 style={{ borderBottom: "5px solid black" }}>{title}</h1>
<p>{text}</p>
<Grid columns={3}>
<Grid.Row>
{img1 && (
<Grid.Column>
<img src={img1} style={{ width: "100%" }} />
</Grid.Column>
)}
...
</>
);

You could also share out Page from cms-admin for clients specifically interested in
rendering without the data fetching. In the case of this example the EmbedPage makes
it easier on the client with a simple contract; given a page name the component will
make the necessary requests to show the page.

The rest of the cms-admin applications follows this pattern with an Editor
component wrapped by an EmbedEditor that also connects to the service.

This then leads us to the home application that consumes these components.

THE HOME APPLICATION


Practical Module Federation Page 122 of 166
The home page starts up its use of these components by configuring just the remotes
section of the ModuleFederationPlugin in the Webpack configuration.

new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
remotes: {
cms: "cms@http://localhost:8080/remoteEntry.js",
},
exposes: {},
shared: { ... }, // Shared React dependencies
}),

In the App.jsx for home we then bring in the components using React.lazy imports
and wrap them in local React.Suspense wrappers that provide some nice feedback as
the component loads.

const EmbedPage = React.lazy(() => import("cms/EmbedPage"));


const Page = () => {
const { page } = useParams();
return (
<React.Suspense fallback={<div>Loading</div>}>
<EmbedPage page={page} />
</React.Suspense>
);
};

There is another similar wrapper for EmbedEditor and both of these are used in the
main page layout that uses tabs to swap between the static and editable views of the
page content.

Practical Module Federation Page 123 of 166


This live preview example isn’t exploring any new technical boundaries in our
understanding of Module Federation. It’s a fairly simple system, and that’s the point.
There is a huge business value in the reuse and synchronization of code between these
two systems, and potentially even more value as the number of clients expands. And it
doesn’t take any huge technical leaps to get there given the functionality provided by
Module Federation.

An ideal extension to this example would be to use the resiliency pattern as shown in
chapter 9. In this pattern we export the components using both federated modules and
NPM, so that should the live sharing federated modules fail, you have a recent version
as a fallback that you can render.

Practical Module Federation Page 124 of 166


PART THREE
ADVANCED MODULE FEDERATION

Practical Module Federation Page 125 of 166


12.
FULLY FEDERATED SITES

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries.

Most large sites run on what’s labeled the “micro site” architecture where a single
domain (e.g. amazon.com) isn’t served by a single application, but instead different
routes will go to different applications. All of these applications work together through
shared session state or shared backend APIs to create what appears to be a single
coherent site for the customer.

The advantage of the micro site architecture is that each of these applications can
deploy independently because the site as a whole doesn’t need to get deployed every
time one route changes. One downside, which we’ve been addressing throughout this
book, is the complexity of sharing code between these different applications. Another
downside is that you lose the ability to create a Single Page Application easily in this
methodology. It also makes it difficult to test an end-to-end flow because you’d have to
inject the experience you are testing into the middle of the entire production end-to-
end flow.

Practical Module Federation Page 126 of 166


What if you could share whole routes between the different applications? So that each
application would be able to render all the others? For example, if we had three
applications called home, search, and profile, could we bi-directionally share
between them as in the figure below?

Search

Home Profile

If you're working on the search application, then you federate in the home and profile
applications. As you navigate around the interface, you are still technically running in
the search app, but you get the entire site experience.

This is what we call a Fully Federated Site and it has some big advantages.

- It can be deployed as the site itself, giving your customers a Single Page Application
experience but still allowing the individual teams to deploy independently.
- It provides a better developer experience where the contributor can see how their
feature works through the entire experience.
- It means that you have the ability to completely end-to-end test your entire site even
across multiple projects.

Practical Module Federation Page 127 of 166


AN EXAMPLE APPLICATION
Setting up an entire fully federated application would be too much code to work
through in the context of a book. Therefore, we’ve opted to provide the book code on
Github and look at key sections of the code to demonstrate how it works.

As with our other example applications, if you run yarn start at the top of the chapter 9
directory you will launch all of the servers. The home page runs on port 3001, profile
on 3002, and search on 3003. For simplicity, we’ve put the port numbers in the same
order as the alphabetical order of the project names.

If you navigate to http://localhost:3001/, you’ll see the home application hosted


experience.

Practical Module Federation Page 128 of 166


In this version, the Home tab is the only one with code actually in the application. The
Search and Profile tabs are running on code provided, through Module Federation, by
the profile and search applications on ports 3002 and 3003 respectively.

To get a quick taste of the power of a fully federated site, simply make a change to
packages/home/src/Home.jsx and refresh your browser on http://
localhost:3001/. Of course you would expect to see a change there, but when you
navigate to http://localhost:3002/, the profile application, and http://
localhost:3003/, the search application, you will see the changes as well.

The really impressive part about all of this is that, given what we’ve learned thus far
through this book, this is not anything we haven’t already seen. This is just the
culmination of our existing work in understanding Module Federation.

Let’s look at the webpack.config.js in the packages/home directory. In there, the


ModuleFederationPlugin is configured like so:

new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
remotes: {
home: "home@http://localhost:3001/remoteEntry.js",
profile: "profile@http://localhost:3002/remoteEntry.js",
search: "search@http://localhost:3003/remoteEntry.js",
},
exposes: { "./Home": "./src/Home" },
shared: { ... }, // Shared React dependencies
}),

Practical Module Federation Page 129 of 166


The home application specifies the search and profile applications as remotes and
exposes its own Home page React component. It also shares out it’s dependencies on
react, react-dom, and the antd component library.

In the App.jsx file, we import the different components using a combination of a


straight import for the home page and lazily loaded components for search and
profile.

import Home from "./Home";


const Profile = React.lazy(() => import("profile/Profile"));
const Search = React.lazy(() => import("search/Search"));

In the React component code for the application, we use the react-router-dom
components to select between the different routes for display:

<Route exact path="/">


<Home />
</Route>
<Route path="/search">
<React.Suspense fallback={<div>Loading search</div>}>
<Search />
</React.Suspense>
</Route>
<Route path="/profile">
<React.Suspense fallback={<div>Loading profile</div>}>
<Profile />
</React.Suspense>
</Route>

Practical Module Federation Page 130 of 166


The Home component we use as-is, but for the lazily loaded Search and Profile pages we
use a suspense.

If you want some more fun, just check out the network tab to see just how little extra
code is loaded as you navigate between the tabs.

This is certainly not the only possible way that you could construct a fully federated
site. You can imagine a very small booter application that only hosts the content frame
and manages navigation between the routes that are supplied via federation. By
default, the application would bring in the routes from production, but by changing a
flag or setting an environment variable an engineer could switch one of the routes to
their own localhost running copy of the part of the interface they are working with.

Practical Module Federation Page 131 of 166


LARGER SCALE EXAMPLE
Let’s look at a much larger example system. In the sample code is an entire three
application micro-site with associated node modules and micro-services located in the
large-example directory. Shown below is a block diagram for that system.

We have the applications at the top, Home, Search and Checkout, and they connect to
some React components shared using NPM (AddToCart and Frame). And they also
have a shared codebase for the redux store which holds the cart and some shared API
wrappers in the logic NPM module. And all of this sits on top of two micro-services.

And this is the kind of thing we see a lot of nowadays; the "micro-site" architecture. The
most common architecture before that was the "monolith", a single, usually Java
application, where all the frontend code was in one application.

Practical Module Federation Page 132 of 166


The advantage of the micro-site architecture is that each application can deploy
independently. But there are some disadvantages:

- Code sharing - Code sharing is really difficult - Having shared code in NPM
modules creates friction to sharing code since it has to be extracted from the original
application, which means jumping from repo to repo, doing version bumping, etc.
- Single Page Applications (SPA) - SPAs are difficult to pull off with micro-sites,
but being on a SPA is a huge performance win for the customer. There are great
options like SingleSPA, but those have a learning curve.
- End-to-end testing - E2E testing is very difficult. How do you know when you
release a new version of checkout it's going to be compatible with the other micro-
sites and not break existing flows? That's what end-to-end testing is for, but staging
the entire site using existing tools is very tough.

We are going to step-by-step improve this site using Module Federation until we end
up with a system that runs as a Single Page Application, has easy code sharing, and is
E2E testable. In the accompanying video you can follow along with the step by step
code changes. In this book we will cover the high level architectural moves.

Practical Module Federation Page 133 of 166


Relocating the UI Components

First step is to move the AddToCart React component from components NPM library
to the Checkout application.

This is the natural place for that code because it's functionality that fits within the
purview of the Checkout team. Then we change the Home and Search applications to
remote in the Checkout application and import AddToCart from there.

This means that not only are we reducing the number of NPM libraries we need to
version by one, but we also get live code sharing so that when Checkout updates the
look or the functionality of the “Add To Cart” button the Home and Search sites will
update immediately.

Practical Module Federation Page 134 of 166


Moving The Frame (Header)
Our next stop on the train towards full site federation is to move the header (or Frame)
component over into the Home application and then to expose it out to all the
applications.

Right now Frame just has anchor tag links to the other applications. In a later step,
because all of the content from all of the applications is exposed as remotes, we will
later Frame to give us full SPA functionality. But for now this is a huge win because the
Home page team can update the Frame header and all the applications will see those
changes immediately after Home page deploys.

Practical Module Federation Page 135 of 166


Moving The Business Logic
The next thing to move is the Redux store into the Checkout application (because it
only stores the cart state).

All of the applications now use the remote of Checkout to get the store redux code. This
is nothing new, but when we move to a Single Page Application at the end, because the
user never navigates away from the first page we will be able to retain the store in
memory through the entire customer journey.

Practical Module Federation Page 136 of 166


And then there is one more move to split the business logic code between both the
Checkout and Search applications.

Checkout manages the Cart Logic because it relates to their role in the system. And the
Search team manages the product related API wrappers which are also shared out to
any other applications that need product data or search capabilities.

Moving To A Single Page Application (SPA)


The final step is to move to a full SPA by exposing all the body content components for
each of the applications And then linking those to the already shared Frame component
using a SPA router such as react-router-dom.

Because any site can vend the content from any other site a user can start on the search
page application and never actually leave the original search page as they go perhaps to
Practical Module Federation Page 137 of 166
the home page, then back to search, and then into checkout. And that means your can
use an End-to-End (E2E) testing system to test the entirety of the site from initial
customer visit all the way through ordering an item, with your local code changes run
within that test.

This new system is conceptually easier to understand as there are less moving parts and
shared components and business logic are sharing out of the applications where they
are most ideally located. But, it can be hard to understand how dramatic of a change
full site federation is without trying it yourself. I strongly encourage you to watch the
associated video and to walk through the example before trying to migrate your site to a
fully federated model.

WHAT’S NEXT
It’s not just about sharing modules out of running applications with Module
Federation. There are other ways to share code as well. In the next chapter, we cover
these other methods in detail.

Practical Module Federation Page 138 of 166


13.
ALTERNATIVE DEPLOYMENT OPTIONS

Code for this chapter is available on GitHub. This chapter has sub-directories for each of the different
state management libraries.

Thus far, we’ve been deploying federated modules from applications for consumption
by other applications, but there are alternatives to that model. In this chapter, we will
cover two such models starting with an approach similar to the Micro-Frontend
architecture we covered in the first chapter of this book.

MICRO-FRONTEND MODEL
In the Micro-FE model, we use Webpack to create a dist directory that we then
statically deploy to a static hosting service. Amazon’s S3 service would be a good
example target for this approach.

Applications would then consume the remoteEntry.js just as they would from a
remote application, but in this case there is no running application, just the federated
modules.

Let’s start by looking at the widget directory within the micro-fe directory of the
book code for chapter 10. In that directory, we find the webpack.config.js file that just
packages up the Widget using a standard ModuleFederationPlugin configuration.

Practical Module Federation Page 139 of 166


module.exports = {
output: {
publicPath: "http://localhost:3002/",
},

...

plugins: [
new ModuleFederationPlugin({
name: "widget",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Widget": "./src/Widget",
},
shared: { ... }, // Shared React dependencies
}),
],
};

In the package.json, we have a build script runs the webpack build and a start
script that runs a static server that imitates something like S3.

"scripts": {
"build": "webpack --mode production",
"start": "cd dist && PORT=3002 npx servor"
},

To run this widget server, first run yarn build then yarn start.

To test it out, run yarn start in the home directory that is a peer to the widget
directory.

Practical Module Federation Page 140 of 166


The home application itself should be very familiar by now. The
ModuleFederationPlugin in the webpack.config.js sets up widget as a remote.
The index.html file includes a script tag to the remoteEntry.js file on our static
server. The App.jsx file lazily imports the Widget component and hosts it within a
Suspense as shown in the code below:

import React from "react";

...

const Widget = React.lazy(() => import("widget/Widget"));

const App = () => (


<div>
<React.Suspense fallback={<div>Loading</div>}>
<Widget />
</React.Suspense>
<div>Hi there, I'm React from React.</div>
</div>
);

The important thing to get from this exercise is that you don’t necessarily have to have
the federated module code deployed with the application. This leads us to a second
possible build and deployment option--the sidecar.

SIDECAR DEPLOYMENTS FOR WEBPACK 4


So, you have a Webpack 4 application and it’s going to take some time to port to
Webpack 5, but you want to export some of its modules today. What to do? Well, you

Practical Module Federation Page 141 of 166


could do a sidecar deployment as shown in the sidecar/wp4-project directory of
the chapter 10 book code.

This project is, as you might guess from the name, a React application that is packaged
using Webpack 4. However, we want to expose the src/Carousel.jsx file as a
federated module. How do we do that? We create a Webpack 5 project within this
project in a directory called wp5-sidecar (though you can call it whatever you want.)

Practical Module Federation Page 142 of 166


In the sidecar’s webpack.config.js, there are two items worth noting:

module.exports = {
module: {
rules: [
... // Rules for CSS and pjs
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-react"],
},
},
},
],
},

plugins: [
new ModuleFederationPlugin({
name: "widget",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Carousel": "../src/Carousel",
},
shared: { ... }, // Shared React dependencies
}),
],
};

The first thing to notice is that we are setting up the babel options in the Webpack
configuration because babel will not find them with the relative pathing that we use to
get to the Carousel module.

Practical Module Federation Page 143 of 166


Other than that, it’s a simple yarn build to create a dist directory that could be
deployed to a static hosting service along with the Webpack 4 bundled code.

It’s true that the Webpack 4 code won’t know about the federated module Javascript or
use it. However, this is a method for exporting code from Webpack 4 applications today
if there is going to be a lengthy migration process to Webpack 5.

Practical Module Federation Page 144 of 166


14
MODULE FEDERATION DASHBOARD

With Module Federation making it very easy to share code and components between
applications it became readily apparent that we needed a way to visualize everything
being shared between applications. So we created the Module Federation Dashboard
and it’s available on Docker today. Here are some screenshots from a populated
dashboard.

The dashboard is currently in alpha release status as we are rapidly iterating on


features, adding new functionality, and changing the data model. If you want to try it

Practical Module Federation Page 145 of 166


out you will need to use Docker to run the dashboard as shown in the shell command
below:

> mkdir dashboard-data


> docker run -p 3000:3000 \
--mount type=bind,source="$(pwd)"/dashboard-data,target=/data \
-t scriptedalchemy/mf-dashboard:latest

This will create a local directory named dashboard-data that will hold the data files
so that there is no data loss when the Docker image is shutdown.

Once this is running you can go to http://localhost:3000/ to see the dashboard. When
no data is loaded you will get a home page that explains the dashboard, what it’s useful
for and how to integrate with it.

To send data to the Dashboard we’ve developed a plugin that integrates right into your
Webpack configuration. To get started first install the plugin in your project:

yarn add @module-federation/dashboard-plugin -D

Then configure the plugin to point at the dashboard server.

const DashboardPlugin = require(“@module-federation/dashboard-plugin");


...
new DashboardPlugin({
dashboardURL: "http://localhost:3000/api/update",
}),

Practical Module Federation Page 146 of 166


The next time you start or build your application the dashboard plugin will
automatically update the dashboard with the latest information from the build.

There is also additional metadata that you can add to the DashboardPlugin
configuration that is documented on the NPM page for the plugin.

We are really excited about the potential for the Dashboard. It’s a level of detail and
insight that isn’t available from NPM. As we continue to add new features to enhance
the experience and stabilize the application

Practical Module Federation Page 147 of 166


15
FREQUENTLY ASKED QUESTIONS

It’s hard to cover every potential use case for a technology as fundamental as Module
Foundation. In this chapter, we cover a variety of related topics and questions that
often arise as we roll this out in projects and organizations.

What is the remoteEntry.js file?

The remoteEntry.js file contains references to the exposed modules of an


application. It doesn’t contain the code for those modules, but it knows where to go get
the code. That’s important because it allows those modules to be lazily loaded via
import() if that is how you choose to load your code.

When you have one of the examples in this book up and running it’s worth having a
look at the remoteEntry.js file to see what is in it so that you can get a better
understanding how Module Federation works at the code level. By default the URL for
remoteEntry.js is http://localhost:8080/remoteEntry.js in the starter kit project.

You can name the remoteEntry.js file whatever you choose. The one truly
important detail is that the publicPath defined in the webpack.config.js
matches where the code is deployed. Because the remoteEntry.js contains
references to the code, and not the code itself, Webpack uses the publicPath to find
the modules and load them asynchronously. So that publicPath must match the
deployment location for the system to work.
Practical Module Federation Page 148 of 166
Can I use Module Federation with SingleSPA?

Absolutely! In fact Module Federation is one of the two code loading styles that is
covered on the SingleSPA documentation website. The advantage of using SingleSPA in
tandem with Module Federation is that SingleSPA extends the capabilities of the
solution by allowing you to host view code from multiple view frameworks (e.g. React
can run on a Vue page, or vice versa.) And Module Federation allows you to share other
types of code as well (i.e. business logic, etc.)

What about A/B Testing?

A/B test code is an ideal use for Module Federation. You can expose both of the
variants and use asynchronous imports (or React.lazy) to import which ever variant
your A/B provider tells you to. In this solution you will ever only import one variation
of the code.

When should I still use NPM?

This question is really about the architecture of your solution and as such you should
create your own criteria for that decision. That being said, here is some high level
guidance on when to choose NPM over Module Federation.

- Externally shared code - If you are writing a wrapper for your API then
developers will expect to see that on NPM.
- Slow moving business logic - If it’s not going to change that often, and the
upgrade procedures are very specific, then it’s probably best controlled and
versioned as an NPM module.

Practical Module Federation Page 149 of 166


- When contextually appropriate - If similar functionality is already packaged as
NPM and that functionality is not migrating to Module Federation, then you should
consider sticking with NPM.

Module Federation is a new architectural paradigm and it will take time for the
industry to adapt. It’s worth spending the time before rolling out Module Federation at
scale to develop guidance on how to decide on packaging code as NPM packages or as
federated modules.

It’s worth noting that any code shared with NPM can always be imported an either
shared using the shared module system or exports to other applications using exposes.

Practical Module Federation Page 150 of 166


16
TROUBLESHOOTING

We see some common errors in Module Federation. So here’s what’s going on if you see
one of them.

UNCAUGHT ERROR: SHARED MODULE IS NOT AVAILABLE FOR


EAGER CONSUMPTION
This is an easy one. You are eagerly executing an app which is operating in
omnidirectional mode. Meaning that application A is importing from B which is in turn
importing from A. You have a few options to choose from.

You can set the dependency as eager inside the advanced API of Module Federation,
which doesn’t put the modules in an async chunk, but provides them synchronously.
This allows us to use these shared modules in the initial chunk. But be careful as all
provided and fallback modules will always be downloaded.

Practical Module Federation Page 151 of 166


It’s wise to provide it only at one point of your app, e. g. the shell.

shared: {
...deps,
react: {
eager: true,
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
eager: true,
singleton: true,
requiredVersion: deps["react-dom"],
},

Where we define deps as:

const deps = require("./package.json").dependencies;

Practical Module Federation Page 152 of 166


If you do not want to set dependencies as eager then you can take advantage
of bundle-loader in your webpack configuration. To do that add this loader:

module: {
rules: [
{
test: /bootstrap\.js$/,
loader: "bundle-loader",
options: {
lazy: true,
},
},
]
}

Then change your entry point to look like this.

//index.js
import bootstrap from "./bootstrap";
bootstrap();

Create a bootstrap.js file, and move the contents of your entry point code into that
file

//bootstrap.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));

Practical Module Federation Page 153 of 166


What this does is create an opportunity for Webpack to coordinate with other remotes
and decide who will vend what, before beginning to execute the application. This
approach will increase Round-Trip Time (RTT) as Webpack is unable to async load
everything in one roundtrip.

The recommended solution to eager imports

Methods mentioned above work, but can have some limits or drawbacks.

With Webpack we strongly recommend a dynamic import of a bootstrap file. Doing so


will not create any additional Round Trips, it’s also more performant in general as
initialization code is split out of a larger chunk.

import('./bootstrap')

UNCAUGHT ERROR: MODULE “./BUTTON” DOES NOT EXIST IN


CONTAINER.
It likely does not say button, but the error message will look similar.
This issue is typically seen if you are upgrading from beta.16 to beta.18
Within ModuleFederationPlugin.

Practical Module Federation Page 154 of 166


Change the exposes from:

exposes: {
"Button": "./src/Button",
},

To this:

exposes: {
"./Button": "./src/Button",
},

The change is done so that we can follow the ESM syntax inside Node 14

UNCAUGHT TYPEERROR: FN IS NOT A FUNCTION


You likely are missing the remoteEntry.js, make sure its added in your page
template. If you have the remote entry loaded for the remote you are trying to
consume, but still, see this error. Add the host’s remoteEntry.js file to the HTML as
well.

Depending on how your application exposes, shares, and is architected. An


omnidirectional host can leverage its own remote when coordination is taking place.
This has no performance drawbacks. Webpack is just leveraging itself in an
omnidirectional manner. Since its both a host and remote at the same time. It may use
its own remote. This might seem strange, but this architecture is extraordinarily

Practical Module Federation Page 155 of 166


powerful, with omnidirectional hosts allowing dead simple ways to introduce A/B tests
without ever writing a feature flag or needing to remove one.

Practical Module Federation Page 156 of 166


APPENDIX A
REFERENCE

This is a reference for all the options associated with the four different plugins that
make up the Module Federation system.

ModuleFederationPlugin
Name
This is the name of the module being exported.

Library (optional)
This specifies how the module is to be formatted for use in the browser context.
You should use the assign type and match the name with the module name.

Examples

{ library: { type: "assign", name: "app1" } }

This sets the Webpack library target type to var under the name app1. There are
many library target types, we recommend var as it will be assigned to a global
variable exposed under that name and be easily referenced.

Practical Module Federation Page 157 of 166


Filename (optional)

The file name for the remote entry. The recommended value for this key is
“remoteEntry.js”. This key is only required if the application is exposing
modules.

Remotes (optional)
An object that specifies the remotes this application consumes. Remotes is only
required if you are going to be consuming remote modules.

Examples

{ remotes: {
nav: "nav@http://localhost:3001/remoteEntry.js",
} }

This specifies that the incoming nav federated module is a known remote and
that it can be imported using the name nav. And that the remote entry is at the
specified URL.

Practical Module Federation Page 158 of 166


{ remotes: {
"@checkout/nav": "nav@http://localhost:3001/remoteEntry.js",
} }

This specifies that the incoming nav federated module is a known remote and
that it can be imported using the name checkoutNav.

remoteType (optional)

This specifies Webpack library type for the remotes.

Examples

{ remotes: {
nav: "nav",
}, remoteType: “var" }

This specifies that the remotes are packaged as vars.

Practical Module Federation Page 159 of 166


Exposes (optional)
This is the list of internal modules to expose as federated modules. This key is
only required if the application is exposing modules.
Examples

exposes: {
"./Header": "./src/Header",
},

This exposes the source file ./src/Header as the named export Header.

exposes: {
Header: {
import: “./src/Header”,
// optional metadata
}
},

This exposes the source file ./src/Header as the named export Header with
the additional metadata provided in the object.

Practical Module Federation Page 160 of 166


Shared (optional)
This is the list of packages that this application will share with other applications
that consume its exposed modules, and share with federated modules it
consumes as remotes. This key is only required if the application is sharing
libraries.

Examples

{ shared: ["react", "redux"], }

This shares react and redux without version information.

shared: {
"react": "^16.12.0"
},

This shares react with the NPM semver mechanics starting at 16.12.

{ shared: require(“./package.json").dependencies },

This shares all the packages listed in the package.json dependencies.

Practical Module Federation Page 161 of 166


{ shared: {
react: {
singleton: true
}
}
}

This shares react but marks it as a singleton meaning that only one copy can be in
use in the application at one time.

{shared: {
react: {
version: "^16.12.0",
import: "react",
shareKey: "react",
shareScope: "default",
singleton: "true",
}
},

This shares react at 16.12 as a singleton with the import name as react and in the
default scope under the name react.

Practical Module Federation Page 162 of 166


ShareScope (optional)
This is the scope name to use for the shares.

Examples

{ shared: ["react", “redux"], shareScope: “default" }

This sets the name of the scope to use for the shared packages to “default”.

ContainerPlugin
This is the plugin that exposes modules for Module Federation. It accepts the name,
library, filename, and exposes keywords with the same options as
ModuleFederationPlugin.

ContainerReferencePlugin
This is the plugin manages remotes for Module Federation. It accepts the remoteType
and remotes keywords with the same options as ModuleFederationPlugin.

SharePlugin
This is the plugin manages shares for Module Federation. It accepts the shares and
sharesScope keywords with the same options as ModuleFederationPlugin.

Practical Module Federation Page 163 of 166


APPENDIX B
GLOSSARY

Below is a glossary of the different terms used in this book and what they mean.

Term Description
Exposed A module that is exported from an application is “exposed”
Host The currently running application that is hosting federated
remote modules
Module The smallest unit of shareable code. In the Webpack system, a
module can be a single file.
Override/Shared The Webpack internal name for a shared package
Package An NPM package
Remote A reference to an external federated module
Remote Module Any file exported through Module Federation
Scope A scope is synonymous with an application. Every application
is exported as one named scope.
Shared A module that is shared from an application to a remote
module.
Singleton A constraint that only a single version of this module or
package should be in use within that application at any given
time

Practical Module Federation Page 164 of 166


REVISION HISTORY
v1.1.0 - 6/18/2020: Updated to beta 18
- Updated references from beta 17 to beta 18
- Fixed small inconsistencies in the code examples
v1.2.0 - 6/26/2020: Content Upgrade
- Split into three parts
- Added chapter 9: Header, chapter 10: A/B tests and chapter 11: Live Previews
- Substantive content added to the Full Site Federation chapter
v1.3.0 - 7/23/2020: Content Upgrade
- Updated all the code to v22 of Webpack 5
- Modified all the examples to use the new remote syntax
- Numerous context fixes and enhancements
v1.4.0 - 10/11/2020: Content Upgrade
- Updated all the code to the Webpack 5.0.0 release
- Updated the code samples on GitHub
- Updated the dynamic loading code

Practical Module Federation Page 165 of 166


ABOUT THE AUTHORS

Jack Herrington is a Principal Full Zack Jackson is the Principal


Stack Software Engineer. He has Javascript Architect at Lululemon and
written seven books on a variety of the creator of Module Federation. Zack
topics from Podcasting to PHP and strives to prove the impossible to be
now to Module Federation. He is also possible. He has designed distributed
the host of the Blue Collar Coder application architecture for over 10
channel on YouTube which covers years and has architected stacks
mainly frontend topics from beginner consisting over 150+ independent
to advanced architecture levels. He Micro-frontends. Before Module
lives in Portland, Oregon with his wife Federation, he co-authored the code-
and his daughter who is attending split SSR technology that’s still used
Oregon State University. today by all React tools which offer the
capability.

Practical Module Federation Page 166 of 166

You might also like