PDF Practical Module Federation 20 Compress (1)
PDF Practical Module Federation 20 Compress (1)
FEDERATION
J AACC K H ERRINGTON & Z AACC K J AACC K S O N
This book is copyrighted © 2020-2021, Zack Jackson and Jack Herrington, all rights
are reserved.
Preface 3
Getting Started 18
Getting Started With Create React App 36
Troubleshooting 188
Reference 194
Glossary 201
Module Federation is an advanced use topic for the Webpack bundler starting with
version 5. In order to
to get the most out of this book you should have a passing
familiarity with Webpack up to and including
i ncluding 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).
plugins). This book also d
deals
eals
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 14 or greater to
run Webpack builds and execute the server code. You should also have git and yarn
installed for build tooling.
SUBSCRIPTION
This book is more than just a book. This original intention of this book was to be a year
long subscription after its initial release. In that time Module Federation went from a
beta feature into full
full blown production, and the book dutifully
dutifully tracked along with tthat
hat
journey. We still honor
honor that subscription. If yo
you
u bought a subscriptio
subscription
n to 1.x you will
get updates to the 2.x version of the book for free.
free.
Micro-Frontends are often the first way that folks encounter Module Federation. So
let’s start off our investigation of Module Federation by first learning about what
Micro-Frontends are, and then how Module Federation can be applied to them, and
more!
WHAT IS A MIC
MICRO-
RO-FRO
FRONTE
NTEND?
ND?
Micro-Frontends (MFEs) are often described as “Micro-services for the frontend”.
Where micro-services architecture
architecture allows individual teams to deploy independ
independent
ent
services that work together to create a complete backend systems architecture, MFEs
allow multiple teams to independently deploy frontend experience code to one or more
applications that provide a complete experience to the frontend consumer.
If that’s a bit too much jargon for, let me provide some examples. The header and
footer of a website are MFEs, as they are usually functionally isolated from the content
that sits between the header and footer. Another example would be a carousel of
products. Where the carousel knows how to manage it’s on view state and make
requests to the backend micro-services to get the list of products to display.
The key attributes of an MFE are that they are self contained and independently
deployable.. They are self-contained because they contain all of the business logic that
deployable
is specific to their function and behavior. That being said MFEs are hosted on a page,
The second key attribute, independent deploy, is achievable in multiple ways. You can
either deploy MFEs as build-time dependencies, or as a runtime dependencies. There
are advantages and disadvantages to both techniques.
- Build time deployment - Build time deployment means that the MFEs are
maintained in separate modules but are still available at build-time and the host
applications need to be re-deployed for a change to go live. Technology options here
include using NPM modules through a private artifact repository, or through Bit.
The big advantage here is that your application deploys as a complete unit. The
disadvantage is that different versions of the same MFE might be deployed on
individual applications at any given point in time.
complete at the end of the build, there are runtime dependencies that need to be
loaded in order for the host application to be complete.
There are strong advantages and disadvantages to each approach. But the key takeaway
here is that MFEs can be either build-time or runtime deployed.
Once they get to that stage a company will often “monolith-bust” by taking features out
of the monolith one-by-one, most often route by route, to create a set of applications
The original application that started out with forty developers became three frontend
application teams with ten developers each, managing the Home, Search and Checkout
experience. Those three teams share a global header footer provided by the five person
“Nav” team. And they are all backed up by a five person Shared Infrastructure team
that manages Design System components, API access libraries, and so on.
The figure below shows two applications, Home and Search. The Home page team
currently has a Product Carousel on their page that we would like to reuse as an MFE
on the Search page.
To share this Product Carousel at build-time we could extract that code into a shared
NPM library, as shown below. Then link it to both applications as a shared dependency.
This is a very reliable way to go, but there are two drawbacks. First, for the Home page
team, fixing issues in the Product Carousel becomes laborious.
laborious. They need to change
projects, make the changes, fix the tests, bump the version and deploy the library. Then
The second drawback is that when the Product Carousel is updated there is no
guarantee that both the Home and Search pages will show the same versions of the
carousel, because either or both, could have deployed with older versions of the library.
The advantage here is that now the code is independently deployable. Once the Product
Carousel team deploys the Home and Search applications will get the new code
immediately.
The Home page application simply exposes the Product Carousel as a runtime
dependency (called a remote) and the Search applications points to that dependency in
its Webpack configuration file. At that point the Search application imports and uses
the Product Carousel as it would any other component.
This Wasn’t
Wasn’t A Problem Before Bundlers
Bundlers gave us the advantage of having a single file to deploy. Or later a set of split
files to deploy that the system would manage as you performed asynchronous imports.
Practical Module Federation Page 12 of 204
But with that advantage came the loss of runtime loadable dependencies, which it
appears, are as important now as they were then. I guess you only realize something’s
value once it’s gone.
- 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.
No code loaders - Micro-FE frameworks (i.e. Single-SPA) 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.
- Applies to any Javascript
Javascript - Where Micro-Frontend frameworks work only 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.
- Universal - Module Federation can be used on any platform that uses the
Javascript runtime. Browser, Node, Electron, 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. AMD, UMD, CommonJS,
SystemJS, window variable and so on.
FRAMEWORK NEUTRALITY
So far we have assumed that both the host application (or shell) and the Micro-
Frontends are using the same view framework (e.g. React, Vue, Svelte, etc.). But, what
if they aren’t, is there a way to manage that? Absolutely. The industry standard solution
solution
to that problem is a system named Single-SPA.
frameworks into a platform agnostic “parcel”. Then host applications can use a Single-
SPA client to embed a parceled component on the page. In this way a Vue application
can host React components or vice versa.
v ersa. Single-SPA manages all of that interaction.
This means that as you are thinking about migrating experiences from a monolith to a
multiple application architecture you can stop thinking about that migration in terms
of complete routes and instead start thinking about in terms of porting portions of a
page.
One thing that Single-SPA does not handle is loading the parceled code into the
application. This can being done as either a build-time dependency, or as a runtime
dependency using Module Federation or SystemJS.
Now that you know more about Micro-Frontends and how runtime and build-time
dependencies interact with Micro-Frontends let’s now rethink our approach to building
applications.
For example you can take a product display element, an “add to cart” element and a
product carousel element and compose them into a complete custom experience for a
new product. And each of those elements can be maintained by a separate team,
deployed as Micro-Frontends (either as build-time or runtime dependencies) and then
composed together in a myriad of ways.
Micro-Frontends and Module Federation fit perfectly into this composable model. And
this model of composability doesn’t need to be constrained just to commerce. You can
apply this to any type of application, and in fact the composability model ends up
blending application types. For
For example, a commerce experience co
could
uld include
elements from a Content Management System (CMS), or vice versa.
The key point here is that you need to look at experiences at a level below the
granularity of a “page” or a “route” and instead into the reusability of elements within
each “page”.
Additionally, Module
Module Federation is very complex,
complex, and should only be attempted by
experienced web developers with a strong background in debugging though issues such
as CORS, library versioning and singletons.
This book will try and steer you around the complex runtime issues associated with
Module Federation. However, we strongly recommend that you take a step-by-step
approach to using Module Federation in your environment. Never continue on to the
next layer of complexity without first ensuring that the code works as-is. And it’s
strongly recommended that at each point where you achieve a working system that you
commit to source code control before moving onto the next change. That will ensure
that you have a safe position to fall back on when you encounter problems.
Please feel free to ask for your money back on this purchase if you feel like Module
Federation is not the right fit for you, your current skill level or skill set.
The easiest way to start learning Module Federation is to try it out. To do that we will
create two applications; host and nav. The home page application will consume and
render a Header React component that is exposed by the nav application.
% mkdir getting-started
% cd getting-started
% mkdir packages
{
"name": "packages",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"scripts": {
"start": "concurrently \"wsrun --parallel start\""
},
"workspaces": [
"packages/*"
],
"devDependencies": {
"concurrently": "^5.2.0",
"wsrun": "^5.2.0"
}
}
% npx create-mf-app
? Pick the name of your app: host
? Project Type: Application
? Port number: 3000
? Framework: react
? Language: javascript
? CSS: Tailwind
Your 'host' project is ready to go.
Next steps:
cd host
npm install
npm start
% cd getting-started
% yarn
% yarn start
That will fire up both applications simultaneously and you’ll get something that looks
like this.
So let’s have a look around at the application to see how this is all put together. The
import "./index.scss";
To get all this running we are using a combination of babel to transpile the code and
Webpack to bundle the
the code and to run the d
development
evelopment server. If we loo
look
k into the
package.json we can see the dependencies and these are described below.
Dependency Description
@babel/runtime Babel runtime functions to support ES6 code.
react The React view library.
react-dom The React DOM library which handles rendering React trees
into DOM elements.
@babel/core The engine of the Babel code transpiling system.
@babel/plugin- Runtime helpers so that we can use ES6 syntax.
transform-runtime
bundled.
css-loader A webpack loader that allows the import of CSS files into
Javascript files.
html-webpa
html-webpack-pl
ck-plugin
ugin A plugin
plugin for
for webpack
webpack that automatica
automatically
lly iinsert
nsertss a sscript
cript tag
that references the bundle into the HTML template.
postcss CSS pre-processor for Tailwind.
postcss-loader Webpack loader to run postcss.
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.57.1 or higher.
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
},
Next we specify and code file extensions that we want to look for if we leave extensions
off the import name.
devServer: {
port: 3000,
historyApiFallback: true,
},
This is where we specify the development server port number, and we tell the dev
server to serve index.html for any unknown routes.
module: {
rules: [
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
This makes sure that we properly process all the JavaScript, TypeScript, CSS or ESM
files that are referenced in the application.
plugins: [
new ModuleFederationPlugin({
name: "host",
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",
}),
],
};
Here we add two plugins, the ModuleFederationPlugin that will share our
consume shared code, and the HtmlWebPackPlugin that will process the
index.html template.
Field Description
applications.
- 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”
- Shared - 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.
CREATING
CREATING THE HEADER
Now that we have our two applications running lets create a Header component in the
nav application in packages/nav/src/Header.jsx. Something like this:
Nothing too interesting outside of the Tailwind classes in the className attribute.
import "./index.scss";
If that works then it’s time to expose that Header component so that it can be
new ModuleFederationPlugin({
name: "nav",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Header": "./src/Header",
},
shared: { ... },
}),
That’s all it takes to expose the Header. We just name it create a key with the name
"./Header" and we the value that is the path to the source file.
Now we are ready to consume the Header component in the host application.
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
"my-nav": "nav@http://localhost:3001/remoteEntry.js",
},
exposes: {},
shared: { ... },
}),
Once we restart the servers we can the access our Header. But how? We’ll we have
defined that there is a new “remote” named my-nav at the name and URL specified in
the value. The name is nav because that’s what is specified in the Webpack
configuration of the nav application. And the remoteEntry.js file is also named in
that configuration file.
Now let’s go to the App.jsx file and consume the Header component.
import "./index.scss";
This loads the Header lazily using an asynchronous import and then renders it within
a Suspense. This allows React to only render the component when the code is
downloaded and ready to go.
Nav
module.exports = {
output: {
publicPath: "http://localhost:3001/",
"http://localhost:3001/",
},
plugins: [
new ModuleFedera
ModuleFederationPlugin({
tionPlugin({
name: "nav",
fileName: "remoteEntry.js",
"remoteEntry.js",
exposes: {
"./Header”: "./src/Header"
"./src/Header"
}
}),
],
};
Home
src/App.jsx
module.exports
module.exports = {
plugins: [
new ModuleFedera
ModuleFederationPlugin({
tionPlugin({
name: "home",
remotes: {
"my-nav": "nav@http://localhost:300
"nav@http://localhost:3001/remoteEntr
1/remoteEntry.js"
y.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
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.
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
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
pulling the dependencies, as on ob
object,
ject, 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
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.
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"],
},
},
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.
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 chapter on Alternative Deployment Options.
WHAT’S NE
NEXT
XT
The create-mf-app command is one way to build applications that use Module
Federation in the next chapter we will show how to use Module Federation in
applications created using Create React App.
Create React App has updated to the latest Webpack which makes it much easier to use
Module Federation. Module Federation support isn’t baked into Create React App but
that doesn’t mean you have to eject to be able to use it. You can use react-app-rewired
or craco, both of which have been standard ways to extend Create React App
applications for several versions now. In the case of craco there is even a Module
Federation plugin that is available to make it even easier and we will use that in this
chapter as well.
In this chapter there are four applications that you can use as starting points.
- Host - This isis a Create React App (CRA) that uses react-app-rewired to modify the
Webpack configuration and it consumes components ffrom rom the other applications.
- Search - This is a CRA application that uses craco to modify the Webpack
configuration and has a function that reads all the files from a given directory and
exposes them. This functionality can be used in any context.
- Checkout - This is a CRA app that uses craco and the craco-module-federation
craco-module-federation
plugin to configure the application to share a component.
% cd packages
% yarn create react-app host
"scripts": {
"start": "PORT=3000
"PORT=3000 react-app-rewired start",
"build":"react-app-rewired
"test": "react-app-rewiredtest",
"react-app-rewired
"react-app-rewired build",
"eject": "react-app-rewired
"react-app-rewired eject"
},
That will not only allow us to override parts of the Create React App configuration but
also allow us to tweak the Webpack configuration. It also sets the port to 3000 so that
its reliably at that port number.
if (!config.plugins) {
config.plugins = [];
}
config.plugins.unshift(
new ModuleFederationPlugin({
name: "home",
...
})
);
return config;
};
The critical pieces here are to set the publicPath appropriately and to add the
ModuleFederationPlugin with your desired settings. We use unshift here to
make sure that the plugin is the first along the line of plugins (which is an extensive
list).
import("./realIndex");
It’s that easy. What this does is give Webpack some “breathing room” to be able to
asynchronously load any dependencies required by Module Federation.
From there we need to alter the package.json to run craco instead of react-
scripts like so:
"scripts": {
"start": "PORT=3001 craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
if (!config.plugins) {
config.plugins = [];
}
config.plugins.unshift(
new ModuleFederationPlugin({
...
exposes: exposeDirectory("./src/federated"),
...
})
);
return config;
},
},
};
where, among other things, you can provide a configure function like we have here.
module.exports = {
plugins: [
{
plugin: require("craco-module-federation"),
},
],
};
This just registers the plugin with craco, so now we have to configure it in
modulefederation.config.js, like so:
module.exports = {
name: "checkout",
...
};
WHAT’S NE
NEXT
XT
Now that you’ve got your first experience with Module Federation it is time to dig iinto
nto
the details of how to share code between applications safely. And that starts with a deep
dive into the world of sharing React components.
Now that we understand where Module Federation fits into the world, and we’ve tried
it out on a very
v ery basic application, let’s increase our depth of knowledge by exploring
how Module Federation works.
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 heaving heavy lifting. And you can use
each of those individually and customize how you see fit so that you have ultimate
control over the output.
- 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
exposes remote modules tthen
hen this is the only plugin you need.
To get a deeper look at what’s built by Webpack a good way to do that is run the build
in development mode and to look at the output of the dist directory, like this:
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
vendors-node_modules_react_index_js.js 73.7 KiB [compared for emit] [id hint: vendors]
Entrypoint main = main.js
Entrypoint nav = remoteEntry.js
- index.html - This is the compiled HTML file that includes the main.js file which
runs the application.
main.js - The compiled code for the main entry point for the application.
-
Practical Module Federation Page 45 of 204
- remoteEntry.js - The manifest Javascript and specialized runtime for the
exported remote modules and any shared packages.
- vendors-node_mo
vendors-node_modules_react_index
dules_react_index_js.js
_js.js - The compiled react package that
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. And often times those two overlap. For example, the same vendor
bundles that are referenced
referenced by the remote m
modules
odules are used by the application itself.
Now that the Javascript is all compiled, let’s discuss how it actually works at runtime.
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.
window.nav.get('Header')
This returns a promise, which when resolved gives you a factory. We can iinvoke
nvoke that
like this:
This will output through console.log the module as defined in the Header
implementation. If Header exports a default the 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.
WHAT’S NE
NEXT
XT
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 deploy
deploy
our federated modules.
Once we know where we are deploying to now we just need to change the webpack
configuration when we are building for production. But how do build for production?
We would do something
something like:
And that will send the production argument to the Webpack configur
configuration.
ation.
Practical Module Federation Page 49 of 204
Now that we know we are in production mode how do we switch the configuration
based on that?
We can turn the webpack.config.js from an object into a function like this:
Then we can use argv.mode to point the publicPath (which is the value that we set to
You can also do this automatically by specifying publicPath as auto, like so:
output: {
publicPath: "auto",
},
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
carousel: `carousel@${
`carousel@${
argv.mode === "production" ?
"https://assets.mycompany.com/carousel" :
"http://localhost:3000"
}/remoteEntry.js`,
},
exposes: {},
...
This code switches the remote based on whether we are building the host application in
either production or development mode.
This does raise an interesting question though about what to do when the code is being
run in development mode. Maybe you want to test your development code against the
production remotes? If so you’ll probably want some additional logic that looks at other
arguments, for example argv.remotes where you can set that to production as
well.
There is a sample project in the production directory of this chapter’s GitHub code that
is set up to demonstrate switching between development and production modes. First
install the dependencies by running yarn in the root directory. To run in development
mode run yarn start in the top level directory. To build for production mode run
WHERE SHO
WHERE SHOULD
ULD FED
FEDERATED
ERATED MODU
MODULES
LES GO?
Federated modules are just JavaScript files, and as such should be deployed to an asset
service like Amazon’s S3 service. Serving the files directly from an Express server, or
any other kind of server is a waste of server processing power and money. If you have
access to a Content Distribution Network (CDN) like Akamai Cloudfront you can use
that to further optimize the delivery of the JavaScript assets.
EXTERNAL REMOTES
It’s not unusual to have three (or more) different environments. For example;
development, test, and production. Where the test environment has a complete copy of
the production environment with test data. In some companies it’s required that
exactly the same built code runs in both test and production. This can be a problem for
Module Federation because the remote URLs are stored in the built bundle. So if you
build for a test deployment
deployment URL you would
would need to rebuild for production.
To get around this you can use the external-remotes-plugin which allows you to
replace portions of the remote URLs at runtime. To see this in action there is
i s an
example project in the chapter’s code on GitHub. In this project there are two Micro-
Frontends; MFE1 and MFE2, but both are built with the same federation name “mfe”
so they can be used interchangeably by just pointing at either the remoteEntry.js
file on port 3000 or 3001 depending on if you want MFE1 or MFE2 respectively.
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
mfe: "mfe@[mfeUrl]/remoteEntry.js
"mfe@[mfeUrl]/remoteEntry.js",
",
},
exposes: {},
The [mfeUrl] in the brackets will be replaced by a value named mfeUrl on the
window object at load time. To
To make use of this feature the main index.js file gets a
feature
little more complicated:
fetch("/public/config.json")
.then((res) => res.json())
.then((config) => {
window.mfeUrl = config.mfeUrl;
import("./App");
});
{
"mfeUrl": "http://localhost:3001"
}
So in this case it sets the mfeUrl on window to be port 3001 on localhost, which is
MFE2. So now when the application loads the MFE if will load MFE2. You can change
this to 3000 and see it load from MFE1 without rebuilding
rebuilding the bundle
bundle..
Of course, in your environment you’ll probably want to get the value for your external
remotes using either a service or by inspecting the current URL.
WHAT’S NE
NEXT
XT
Now that we’ve taken module federation to production, let’s talk more about how to
make shared code and components safer in production.
Let’s visualize the connection between the host app and the nav app we created in the
last chapter. The host app has a runtime connection to the Header.
But any runtime connection can be broken. So let’s try that out by simply starting the
host server without running the nav app. The result is that the page is broken. No
content is rendered at all.
Let’s have a look at how reference the header in the code. We use a React.lazy
import and we’ve wrapped it in a Suspense:
Apparently a suspense
suspense is not enough.
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;
}
}
It’s sad that we can’t use a hook for this, but unfortunately React does not currently
support hooks for error boundaries. The good news is that the page renders and we
w e get
our error message instead.
Shown below is the code for FederatedWrapper that handles both a slow load and an
error during component execution.
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>
);
}
}
WHAT HAPPENS
HAPPENS WHE
WHENN THE
THE APP GOE
GOESS DOWN?
DOWN?
A question we get asked
asked a lot is “what happens when the apps go down”? This is a by-
product of the fact that we most often demonstrate Module Federation by using two
running applications. All of the assets, including the federated modules produced by
the Module Federation plugin are shared by the Express server.
It’s really important to realize that federated modules are just JavaScript files. They
have no connection to the application server. So whether the server is running or not
should not effect their availability. In fact, they should be deployed as you would any
other JavaScript file, to an asset store like Amazon’s S3 service.
This means that we can then create the Header component using this wrapper:
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 APPLICA
APPLICATIONS
TIONS
Another important safety
safety tip is to “bootstr
“bootstrap”
ap” your application. Bootstrapping
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");
And the local App.jsx file would have the application, like so:
This “bootstrapping” gives Webpack the opportunity to process the rest of the imports
before executing the app and will avoid potential race conditions
conditions on importing all the
code.
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
give Webpack any time to
load the required remotes before execution.
Then Webpack would not have the opportunity to load the nav remote before that code
executes, and it would result in a runtime error.
WHAT’S NE
NEXT
XT
In the next chapter we discuss the different state sharing options available to you in
federated React applications.
Once you have a way to share code between applications, whether that be through NPM
modules 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 into how to share state.
new ModuleFederationPlugin({
...
shared: {},
}),
Then restart the applications using yarn start and you’ll notice that it still works.
Which is odd. We can even see the react library being imported along with the header
in the browser’s network tab.
But now let’s break this application by making one small change to the Header
component.
return (
The original Header was stateless, but now lets add some state by using a useState
hook in the Header.
Practical Module Federation Page 65 of 204
Looks good, but here is what we see in the console when it runs:
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. But we didn’t get the error until we tried to use hooks, and that’s because
hooks are internally stored as global state within the react library. So everything was
fine until we used event 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.
Practical Module Federation Page 66 of 204
To fix this we use this configuration when we share react and react-dom.
...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.
USESTATE
The simplest and cleanest way to share state is to use useState. So let’s start with that
and change the host app so that you can add items to a cart and the Header in nav so
This new version of the App component has its own state that contains a cart count.
Practical Module Federation Page 68 of 204
After pressing the “Buy me!” button five times the result in the browser
browser looks like the
figure below:
And 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 host application.
<button onClick={onClear}>Clear</button>
</header>
);
It’s not surprising this worked. This is the simplest and most straightforward way to
share state in React. But it does provide a solid baseline to compare with some other
approaches.
Practical Module Federation Page 71 of 204
USE-CONTEXT
Obviously prop-drilling is only going to get you so far, and it’s certainly no surprise that
it works. But what about using a React context? That works as well and it provides an
excellent setup for looking at other state managers like Redux, Mobx, Zustand, etc.
since they use context as well.
So let’s start with the use-state example code that we have to start and migrate that
to a use-context example that uses React context to share state.
This is a pretty standard, if simplistic, context setup. We have two exported values,
CountProvider which is a component that you use at the top of your tree to share the
state down the tree. And useCount which is a custom hook that gives you the count
and the count setter.
Practical Module Federation Page 72 of 204
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
"my-nav": "nav@http://localhost:3001/remoteEntry.js",
host: "host@http://localhost:3000/remoteEntry.js",
},
exposes: {
"./store": "./src/store",
},
shared: { ... },
}),
Now we alter the packages/host/src/App.jsx file to both provide and use the
context.
import "./index.scss";
state setter. And we can also remove the drilled properties from the header.
Practical Module Federation Page 74 of
o f 204
Now over in the nav project we need to update the Webpack configuration, located in
pacakges/nav/webpack.config.js, to add the host application as a remote:
new ModuleFederationPlugin({
name: "nav",
filename: "remoteEntry.js",
remotes: {
host: "host@http://localhost:3000/remoteEntry.js",
},
exposes: {
"./Header": "./src/Header",
},
shared: { ... },
}),
Now that we have access to the host as a remote we can update the Header
component.
Practical Module Federation Page 75 of 204
This works, but now the Header embedded in the App component of the nav
application no longer works because we don’t have the context.
Practical Module Federation Page 76 of 204
<CountProvider>
<div className="mt-10 text-3xl mx-auto max-w-6xl">
<Header />
<div className="mt-10">Nav project</div>
</div>
</CountProvider>
);
Now that we know we can share context this way we can start looking at using more
complex state managers. But before we get into that, lets talk about some alternatives
when it comes to sharing the store module because the current model we will using
works fine for a single
single application, but probably won’t scale up cleanly to mul
multiple
tiple host
applications.
Practical Module Federation Page 77 of
of 204
SHARING STORES
As we saw in the use-context example the host application exposed a store, and the
nav application consumed it.
So why is that important? In the case of our use-context implementation you have
to share the same instance of the module because it’s the instance that holds the global
context for the state.
For simplicity sake we will be using having the host application provide the store in the
different scenarios that follow. But you should pick the state sharing setup that is most
appropriate to your architecture.
REDUX
If you’ve done any React 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 the Getting Started chapter. From there we add the Redux
Toolkit @reduxjs/toolkit and react-redux to host and nav applications.
% cd packages/host
% yarn add @reduxjs/toolkit react-redux
% cd ../nav
% yarn add @reduxjs/toolkit react-redux
name: "counter",
initialState: {
count: 0,
},
reducers: {
increment: (state) => {
state.count += 1;
},
clear: (state) => {
state.count = 0;
},
},
});
This very simple Redux store has an initial state with a count of zero. And there are
two actions, increment, will add one to the count, and clear will set the count to
zero.
The next thing we want to do is to share the store externally (more about this later).
Practical Module Federation Page 80 of 204
new ModuleFederationPlugin({
name: "host",
...
exposes: {
"./store": "./src/store",
},
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
w hether it will work from the host application
of the consumers. The big question is whether
Before we get there we need to adjust the App React component to use this store.
return (
<div className="mt-10 text-3xl mx-auto max-w-6xl">
<React.Suspense fallback={<div />}>
<Header />
</React.Suspense>
<div className="mt-10">Hi there, I'm some cool product.</div>
<button
className="px-5 py-2 bg-green-500 text-white rounded-xl"
onClick={() => dispatch(increment())}
>
Buy me!
</button>
<div>Cart count is {count}</div>
</div>
);
};
Modern Redux uses the useSelector hook to get the state values we are interested
in, and the useDispatch hook to get a dispatch function that we can use to dispatch
actions.
Practical Module Federation Page 82 of 204
The only thing left to do is update the Header so that it connects to the store properly.
That starts with allowing the nav app to see the store by adjusting its Webpack
configuration.
new ModuleFederationPlugin({
name: "nav",
filename: "remoteEntry.js",
remotes: {
host: "host@http://localhost:3000/remoteEntry.js",
},
exposes: {
"./Header": "./src/Header",
},
Now we can access the store in the host application. This means that at this point
host depends on nav for the Header component and nav depends on host for the
Redux store. That’s fine. That’s a bi-directional connection and Module Federation
supports that just fine.
Practical Module Federation Page 83 of 204
So let’s head over to the nav application and alter the App.jsx file to look like this:
return (
<header className="text-5xl font-bold p-5 bg-blue-500 text-white flex">
<div className="flex-grow">Header - Cart count is {count}</div>
<button onClick={() => dispatch(clear())}>Clear</button>
</header>
);
};
ZUSTAND
Zustand is a popular alternative Redux that has a similar level of structure to a Redux
store but has far less boilerplate. Let’s try it out by starting with our use-context
% cd packages/host
% yarn add zustand
In this case we don’t need to add zustand to the nav app because as long as we have
the custom hook that Zustand creates we are good to go.
I’m joking, right? This is a store? Not joking, no. And yes, this iiss a Zustand store. You
have the values at the top, in this case count, then the methods you want. And the
methods just call set to set the state. How cool is that! No wonder folks love Zustand.
Practical Module Federation Page 85 of 204
Now the next thing we need to do is use that over in the packages/host/src/
App.jsx file:
import "./index.scss";
MOBX
Mobx is another popular state manager for React applications. It works an observables
model. To try out Mobx in Module Federation we first need to install it in both the
host and nav applications.
% cd packages/host
% 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 a new packages/
host/store.js file:
...
Then over in the Header implementation in the nav application we also wrap that in
an observer.
Once again, we display the data simply by showing the value. And we reset the value
just by setting it to zero.
zero. And this all work
workss seamlessly across these components
brought together through Module Federation.
Federation.
The reason all this works is because we are sharing the same instance of the observable
from the store exposed by the host application.
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
Practical Module Federation Page 90 of 204
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. So
when an atom changes state then only
only those components that are using it will re-
render.
Let’s try it out and you can see more of what I mean. The first step is to install recoil
in both the host and nav applications.
% cd packages/host
% yarn add recoil
% cd ../nav
% yarn add recoil
With that done we can launch the servers with yarn start. We then create a new file
in the src directory of the home app called atoms.js with these contents.
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 atoms value changes
then any component that subscribes to it will be updated. You can have as many atoms
as you like. And you can use another mechanism called a “selector” to create composite
To use the cartCount atom we return to the App.jsx file and import both recoil and
the atoms:
ReactDOM.render(
<RecoilRoot>
<App />
</RecoilRoot>,
document.getElementById("app")
);
Practical Module Federation Page 92 of 204
Notice that the Header now no longer has any properties. So 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 new need to alter the webpack.config.js to add
host as a remote.
new ModuleFederationPlugin({
...
remotes: {
Host: "host@http://localhost:3000/remoteEntry.js",
},
...
}),
This is creating a bi-directional linkage between host and nav so that we can share the
same instance of the atoms. Just as we have shared the same instance of the store in
previous examples.
Practical Module Federation Page 94 of 204
To finish the example we use the useRecoilState hook to both get and set the value
of the cartCount atom:
</header>
);
};
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.
Let’s try it out and you can see it in action. The first step is to install rxjs in the host
application.
% cd packages/host
% yarn add rxjs
The next thing to do is to import Subject from rxjs and to create a Subject that will
hold the cart count.
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 96 of 204
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:
useEffect(() => {
const sub = subject.subscribe(valueSet);
return () => {
sub.unsubscribe();
};
}, [subject]);
return [
value,
{
increment: () => count.next(value + 1),
clear: () => count.next(0),
},
];
};
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.
So, now that we have our hook we can use it in the App component.
We use the useCount 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 clear.
Practical Module Federation Page 98 of 204
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:
And we reuse the same useCount code from host, which is critical because we have to
share the same instance of the subject to get the state sharing to work.
To set that up we first create a new file in host named analytics.js with these
contents:
exposes: {
"./analytics": "./src/analytics",
},
That allows us to import it using the name host, like this in App.jsx.
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
i t imported we can attach a console function to it using subscribe.
analyticsBus.subscribe((evt) => {
console.log(`analytics: ${JSON.stringify(evt)}`);
});
Practical Module Federation Page 100 of 204
Next we can start to instrument our code, like the increment and clear function
from useSubject which now sends out analytics messages:
return [
value,
{
increment: () => {
analyticsBus.next({ type: "addToCart", value: value + 1 });
count.next(value + 1);
},
clear: () => {
analyticsBus.next({ type: "onClear" });
count.next(0);
},
},
];
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.
Practical Module Federation Page 101 of 204
HOW TO CHOOSE
The choice of a state manager is highly dependent on the type of application you are
building. But here are
are some recommendations.
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.
There are many state managers in the React ecosystem and this chapter has only
covered a few. If you are 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.
Practical Module Federation Page 102 of 204
WHAT’S NE
NEXT
XT
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
1.8
RESILIENT FUNCTION AND STATIC DATA SHARING
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:
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.
Practical Module Federation Page 105 of 204
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.
But that single value could be any primitive, a number, a boolean, whatever.
React.useEffect(() => {
import("logic/singleValue")
.then(({ default: value }) => singleValueSet(value))
.catch((err) => console.error(`Error getting single value: ${err}`));
}, []);
This code creates some state to hold the value using a useState hook. It then 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.
Practical Module Federation Page 106 of 204
So what we are seeing 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.
Arrays and objects work exactly the same as the single value case and you can
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:
time. The simplest alternative is to write a small wrapper function like this:
Here is the interesting thing about promises, they will resolve immediately if the
promise has already been resolved. So 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 functor that takes an import
with a function as the default
default and returns a fu
function
nction that you can call in the same
same way
as sendAnalytics above.
.catch((err) =>
console.log(`Error sending value: ${JSON.stringify(args)}`)
);
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.
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. But
until it resolves it maintains a queue of messages that it then sends to the function
using the forEach.
Practical Module Federation Page 109 of 204
CLASSES
The last thing to try out is
i s a non-React class. The test class iin
n the logic application is
defined this way.
class MyClass {
constructor(value) {
this.value = value;
}
logString() {
console.log(`Logging ${this.value}`);
}
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.
This code starts the import and gets a promise back. It then 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.
Exciting stuff! But it does show that you can safely consume and create objects from
classes exported from remote modules.
Practical Module Federation Page 111 of
of 204
The fact that Module Federation can share any type of Javascript code opens up a
world of possibilities for your projects.
projects. Here are some ideas for
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 NE
NEXT
XT
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 112 of 204
1.9
DYNAMICALLY
DYNAMICALLY LOADING
LOADI NG FEDERA
F EDERATED
TED MODULES
So far the relationship between our host applications and our remotes has been fixed.
For example the host 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.
- widget - This is a single React component that is shared as a remote module in the
scope of widget and under the name Widget.
host-wp5 - This is a host application, running on Webpack 5, that dynamically
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: {...},
}),
The important elements here are that we export the Widget component and that the
name of this application is widget. It’s also good so see that we are running and
exporting this remote module off of port 8080.
output: {
publicPath: "http://localhost:8080/",
},
devServer: {
port: 8080,
},
The React widget itself is super simple as shown in the code below.
You can spruce it up if you like. But 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 host-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.
configuration.
Practical Module Federation Page 115 of 204
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,
});
if (!props.system) {
return <h2>Not system specified</h2>;
}
if (!ready) {
return <h2>Loading dynamic script: {props.system.url}</h2>;
}
if (failed) {
return <h2>Failed to load dynamic script: {props.system.url}</h2>;
}
return (
<React.Suspense fallback="Loading System">
<Component />
</React.Suspense>
);
}
Practical Module Federation Page 116 of 204
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).
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.
Practical Module Federation Page 117 of 204
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
code from anywhere is astonishing.
However, what happens if you aren’t on Webpack 5. Can you still do this?
Practical Module Federation Page 118 of 204
Looking at the host-wp4 directory and the package.json within that directory you
can see that the Webpack version
v ersion is currently 4.
"devDependencies": {
...
"webpack": "^4.43.0",
...
},
And 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 119 of 204
...
window[scope].override(
Object.assign(
{
react: () => Promise.resolve().then(() => () => require("react")),
},
global.__webpack_require__ ? global.__webpack_require__.o : {}
)
);
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 120 of 204
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 as are described in detail in the React State Sharing chapter. 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.
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 NE
NEXT
XT
In the next chapter we dig into how to share packages properly across federated
modules.
Practical Module Federation Page 121 of 204
1.10
SHARED LIBRARIES AND VERSIONING
If you take a look at any Javascript file in your application it’s probably going to start
lot, of import statements, many using node modules or
with at least a few, if not a lot,
packages. That’s just the nature of the Javascript ecosystem. But 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.
- 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. Which we call a “singleton”.
This chapter walks through the different ways to configure the shared key with
recommendations on when to use each of the different options.
Practical Module Federation Page 122 of 204
SPECIFYING VERSIONS
There are three ways to specify shared versions. The first is the array syntax we’ve been
using thus far with the names of each of the libraries to share. The second is an object
syntax with the name of the library as the key, and the version (using semantic
versioning format) as the value.
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
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 form where
you hint Webpack in exactly how to
to manage the shared depend
dependency.
ency.
Practical Module Federation Page 123 of 204
FALLBACK
Webpack has a fallback behavior
behavior if the package provided as shared doesn’t match the
requirements specified in the ModuleFederationPlugin or SharePlugin
configuration.
mismatch.
This fallback feature enables versioning deployments at scale because not all
applications will be required to push new versions
v ersions at the same. time.
WEB
WEBPACK
PACK SHARI
SH ARING
NG HIN
HINTS
TS
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 124 of 204
"shared": {
"react": {
singleton: true
},
"react-dom": "^16.12.0"
}
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 125 of 204
import
"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 126 of 204
shareScope
This is the scope that will be used to store the shared package. Here iiss 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 truthy then only
one instance of this package should be running at any time. Here is an example:
"shared": {
"react": {
singleton: true
}
}
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
"shared": {
"react": {
version: "^16.12.0"
}
}
This configuration tells Webpack that react should be used at the specified semantic
version.
Practical Module Federation Page 128 of 204
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.
WHAT’S NE
NEXT
XT
Before we wrap on the basics of Module Federation lets take a look at how we can use
Module Federation to power experiences using frameworks other than React.
Practical Module Federation Page 129 of 204
1.11
NON-REACT VIEW FRAMEWORKS
Module Federation is strongly associated with React and Micro-Frontends, but it’s just
a mechanism for sharing JavaScript code, any type of JavaScript code including code
for other frameworks.
In the example code associated with this chapter there are example projects in Vue and
Solid-JS. Both were generated using npx create-mf-app which gives you an option
to generate applications using most of the frameworks listed above.
<template>
new ModuleFederationPlugin({
name: "remote",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Widget": "./src/Widget.vue",
},
shared: require("./package.json").dependencies,
}),
This exports the component and creates the necessary remoteEntry.js file.
Practical Module Federation Page 131 of 204
We then reference that remote in the remote key within the host Webpack
configuration.
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
remote: "remote@http://localhost:3001/remoteEntry.js",
},
exposes: {},
shared: require("./package.json").dependencies,
}),
You can name the remote application whatever you want of course, and have as many
of them as you want.
Practical Module Federation Page 132 of 204
Finally we import the Widget into the host application and use it.
<template>
<div class="mt-10 text-3xl mx-auto max-w-6xl">
<div>Name: host</div>
<Widget />
</div>
</template>
<script>
import Widget from "remote/Widget";
export default {
components: {
Widget,
}
}
</script>
It’s just that easy! I have to say this is just as easy, or maybe even easier because of the
elegant simplicity of the Vue developer experience, when compared to sharing React
components between applications.
Looks a lot like React, right? We then expose Widget using the remote application’s
Webpack configuration like
like so:
new ModuleFederationPlugin({
name: "remote",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Widget": "./src/Widget",
},
shared: {...},
}),
new ModuleFederationPlugin({
name: "host",
filename: "remoteEntry.js",
remotes: {
remote: "remote@http://localhost:3001/remoteEntry.js",
},
exposes: {},
shared: {...},
}),
<div>Name: host</div>
<Widget />
</div>
);
WHAT’S NE
NEXT
XT
We’ve learned in this chapter that it’s just as easy to feder
federate
ate code within non-React
frameworks, in this case Vue and Solid-JS, as it to federate code between React
applications.
We strongly encourage
encourage you to try this o
out yourself. The create-mf-app
ut for yourself.
application builder is a fantastic way to just try out new frameworks, even if you aren’t
playing around with Module Federation in the process.
Now that we have an understanding of the basics of Module Federation lets get on with
RT TWO
PAART
PRACTICAL USE CASES
Practical Module Federation Page 137 of 204
2.1
HE ADER/FOOTER
RESILIENT HEADER/FOOTER
Code for this chapter is available on GitHub. There is also a Blue Collar Coder video
associated with this practical use case.
Ideally we want a shared Header and Footer system with these attributes:
- Otherwise
Live sharing - We want all the micro-sites to update the header at the same time.
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.
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.
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
wi ll be whatever
last time the Home page was built.
version was imported the last
Now that we know what we are building let’s take a look at the code for this chapter on
Github.
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.
New Header
</div>
);
In the video associated with this code we use the background color to differentiate
build of the nav project and the current live Module Federation
between an older NPM build
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
between the home and nav
workspaces project. So it’s managing the NPM linking between
projects.
Practical Module Federation Page 141 of 204
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": "^17.0.2",
"react-dom": "^17.0.2"
}
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 142 of 204
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:
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 144 of 204
2.2
A /B TESTS
A/
Code for this chapter is available on GitHub. 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 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
customer
- indicating which choice the customer choose, tabulates the results and chooses a
Results Manager - This is the system that receives the analytics messages
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 146 of 204
The FrameA and FrameB components only vary by the contents of the initial <div>
tag and the styling around the image.
We also load the VariantChooser from the ab-manager remote and invoke it like
this:
<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".
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"],
};
);
};
export default VariantChooser;
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-
Practical Module Federation Page 149 of 204
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
underscore instead.
We also expose the VariantChooser and variants using the exposes key.
Practical Module Federation Page 150 of 204
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
}),
Specifying host, which is the current project, as a remote also allows us to bring in the
Frame components using standard Module Federation imports:
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 151 of 204
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
code in order to specify
specify a winning variant.
Practical Module Federation Page 152 of 204
2.3
LIVE PREVIEW BETWEEN APPLICATIONS
Code for this chapter is available on GitHub. There is also a Blue Collar Coder video
associated with this practical use case.
In large enterprise settings its not 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.
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 153 of 204
In our example system the CMS editor looks like the site pictured below:
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 154 of 204
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.
new ModuleFederationPlugin({
name: "cms",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./EmbedPage": "./src/EmbedPage",
"./EmbedEditor": "./src/EmbedEditor",
},
shared: { ... }, // Shared React dependencies
}),
This EmbedPage component uses the excellent react-query library to connect to the
CMS service defined in the Webpack configuration using an absolute URL.
underlying Page component is a straightforward content formatter:
And the underlying
</div>
);
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.
Practical Module Federation Page 157 of 204
THE HOME
HO ME APPLICATION
APPLICATION
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.
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.
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
i n 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.
Resilient Header Footer chapter in the Use Cases part. 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 159 of 204
PAART
RT THREE
ADVANCE
ADVAN CEDD MODU
MODULE
LE FEDE
FEDERA
RATI
TION
ON
Practical Module Federation Page 160 of 204
3.1
FULLY FEDERATED SITES
Code for this chapter is available on GitHub. There is also a Blue Collar Coder video
associated with this practical use case.
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 d
different
ifferent applications. But
another downside is that you lose the ability to create a Single Page Application easily
in this methodology. It also make 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 161 of 204
But 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; home, search and profile. Could we bi-directionally share between
them as in the figure below.
Search
Home Profile
So if you are working on the search application, then you federate in the home and
profile applications. And 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 162 of 204
AN EX
EXAMP
AMPLE
LE APP
APPLICATION
LICATION
Setting up an entire fully federated application would be too much code to work
through in the context of a book. So we’ve opted to provide the book code on Github
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 this is that, given what we’ve learned thus far
through this book, this is not anything we haven’t already seen. This is
i s just the
culmination of our existing work in understanding Module Federation.
new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
remotes: {
search: "search",
profile: "profile",
},
exposes: { "./Home": "./src/Home" },
shared: { ... }, // Shared dependencies
}),
Practical Module Federation Page 164 of 204
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 React component code for the application we use the react-router-dom
components to select between the different routes for display:
<Routes>
<Route
path="/"
element={
<React.Suspense fallback={<div>Loading home</div>}>
<Home />
</React.Suspense>
}
/>
<Route
path="/search"
element={
<React.Suspense fallback={<div>Loading search</div>}>
<Search />
</React.Suspense>
}
/>
The Home component we use as-is, but for the lazily loaded Search and Profile
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.
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 inin 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
huge performance win for the cu
customer.
stomer. There are great
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
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 tthat
hat when Checkout update
updatess the
look or the functionality of the “Add To Cart” button the Home and Search sites will
update immediately.
Practical Module Federation Page 169 of 204
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
content from all of the applications
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 170 of 204
The next thing to move is the Redux store into the Checkout application (because it
only stores the cart state).
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.
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
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 NE
NEXT
XT
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 173 of 204
Practical Module Federation Page 174 of 204
3.2
ALT ER N ATI
AL ATIVVE DEPLOY
OYMM ENT OPT IONS
Thus far we’ve been deploying federated modules from applications for consumption
by other applications, but there are alternatives to that
that model.
MICRO-FRONTEND MODEL
In the Micro-Frontend 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.
Practical Module Federation Page 175 of 204
Let’s start by looking at the widget directory within the micro-fe directory of the
this chapter. In that directory we find the webpack.config.js file that
book code for this
just packages up the Widget using standard a standard ModuleFederationPlugin
configuration.
module.exports = {
output: {
publicPath: "http://localhost:3002/",
},
...
plugins: [
new ModuleFederationPlugin({
name: "widget",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Widget": "./src/Widget",
},
shared: { ... }, // Shared React dependencies
}),
],
};
And 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.
Practical Module Federation Page 176 of 204
"scripts": {
"scripts":
"build":
"build": "webpack --mode production"
production",
"start":
"start": "cd dist && PORT=3002 npx servor"
servor"
},
To run this widget server first run yarn build then yarn start.
out run yarn start in the home directory that is a peer to the widget
And to test it out
directory.
Practical Module Federation Page 177 of 204
...
The import thing to get from this exercise is that you don’t necessarily have to have the
federated module code deployed with the application. Which leads us to a second
possible build and deployment option; the sidecar.
Practical Module Federation Page 178 of 204
This project is, as you might guess from the name, a React application that is packaged
using Webpack 4. But 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
in a
directory called wp5-sidecar (though you can call it whatever you want.)
Practical Module Federation Page 179 of 204
module.exports = {
module: {
rules: [
{
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.
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. But 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 181 of 204
3.3
MEDUSA
out you will need to use Docker to run the dashboard as shown in the shell command
below:
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
first install the plugin in your project:
Then configure the plugin to point at the dashboard server.
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.
3.4
FREQUENTLY ASKED QUESTIONS
FREQUENTLY
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.
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
http://localhost:8080/remoteEntry.js in the getting started
projects.
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.
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.
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.
module.
- When contextually
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 187 of 204
3.5
TROUBLESHOOTING
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"],
},
If you do not want to set dependencies as eager then you can take advantage of
module: {
rules: [
{
test: /bootstrap\.js$/,
loader: "bundle-loader",
options: {
lazy: true,
},
},
]
}
//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"));
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.
Methods mentioned above work, but can have some limits or drawbacks.
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.
Practical Module Federation Page 192 of 204
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.
APPE
AP PEND
NDIX
IX A
REFERENCE
The sections below cover the different plugins that make up the Module Federation
ecosystem within Webpack 5 and their various options.
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 var type and match the name with the module name.
Examples
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
Filename (optional)
The file name for the remote entry. This is optional and defaults to
remoteEntry.js.
Remotes (optional)
An object that specifies the remotes
remotes this application consumes.
consumes.
Examples
{ remotes: {
nav: "nav",
} }
Specifies that the incoming nav federated module is a known remote and that it
can be imported using the name nav.
{ remotes: {
nav: "checkoutNav",
} }
{ remotes: {
nav:{
external: "nav@http://localhost:3001/remoteEntry.js"
}
}
This automatically fetches the remote named nav from the specified URL
without the requirement to
to add a script tag to the
the host page.
remoteType (optional)
Specifies Webpack library type for the remotes.
Examples
{ remotes: {
nav: "nav",
}, remoteType: "var" }
Exposes (optional)
The list of internal modules to exposed as federated modules.
Examples
exposes: {
"./Header": "./src/Header",
},
exposes:
Header:{{
import: “./src/Header”,
// optional metadata
}
},
Exposes the source file ./src/Header as the named export Header with the
additional metadata provided in the object.
Practical Module Federation Page 197 of 204
Shared (optional)
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.
Examples
{ shared: ["lodash"], }
shared: {
"react": "^16.12.0"
},
{ shared: require(“./package.json").dependencies },
{ shared: {
react: {
singleton: true
}
}
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",
}
},
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 199 of 204
ShareScope (optional)
The scope name to use for the shares..
Examples
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.
AP
APPE
PEND
NDIX
IX B
GLOSSARY
Below is a glossary of the different terms used in this book and what they mean.
Term Description
Exposed A given module should
should be exported by the applicat
application
ion for
federation.
REVISION HISTORY
v1.1.0 - 6/18/2020:
6/18/2020: Updated to bet
beta
a 18
v2.0.0 - 12/26/2021:
12/26/2021: Massive content over
overhaul
haul and upgrade
- Updated all the code for all chapters
- Completely rewrote the introductory
i ntroductory chapter
-
Practical Module Federation Page 203 of 204
Jack Herrington is a Principal Full Stack Zack Jackson is the Principal Javascript
Software Engineer. He has written seven Architect at Lululemon
Lululemon and the creator of
books on a variety of topics
topics from Module Federation. Zack strives to prove
Podcasting to PHP and now to Module the impossible to be possible. He has
Federation. He is also the host of the Blue designed distributed application
Collar Coder channel on YouTube which architecture for over 10 years and has
covers mainly frontend topics from architected stacks consisting over 150+
beginner to advanced architecture
architecture levels. independent Micro-frontends. Before
He lives in Portland, Oregon with his wife Module Federation, he co-authored the
and his daughter who is attending Oregon code- split SSR technology that’s still used
State University. today by all React tools which offer the
capability.