Webpack 5 Module Federation A-Z: React Foodie App (Part II) β TypeScript + Bit π
Learn how to use Webpack Module Federation in real React application. Find out Micro-Frontends approach, TypeScript, AWS, Redux and many more in this blog series!

Intro
Hey folks! π
Welcome to the Webpack Module Federation A-Z blog series!
This is Part II of series about Micro-Frontend with Webpack Module Federation. Today we will introduce TypeScript and Bit to our React Foodie Application. This app was builded and setup in Part I of this series and will continue to be expanded with new features so if you missed it please check it out at THIS link.
TypeScript
Nowadays, working with TypeScript is almost standard. So you are probably wondering how to combine this technology with the Module Federation. Fortunately it is not a difficult task. However, there are some quirks that it's good to know how to deal with.
If you want to see the final result of these changes, you can find the source code below and just remember to change branch to mf/part2
:
Preconfig
Before TypeScript will work in our project, we need to configure it first. For this we need to create a tsconfig.json
file and install some dependencies.
And here are two possible ways.
We can create a tsconfig.json
file for each Micro-Frontend application with different options, so each could potentially have its own rules and approaches.
The second option is to put one tsconfig.json
file in the root of monorepo and then every tsconfig.json
file inside the Micro-Frontend will extend options from the file in the root.
We will choose this approach.
So, in the root of the application at the same level as we have the main package.json
file, create a tsconfig.json
file with the following options:
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"outDir": "./dist",
"baseUrl": "./",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitAny": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": false,
"noEmit": false,
"jsx": "react-jsx",
},
"exclude": [
"node_modules",
"./applications/**/node_modules"
]
}
And that's it for now. We don't need to do anything else now, so let's move on.
Step 1βTypeScript
We will start our migration to TypeScript from the footer
app, because it is a fairly simple micro-application without many dependencies and logic.
First we need to install the needed dependencies: ts-loader and typescript as a dev dependency. Next, we need to create a tsconfig.json
file that will extend the configuration from the main configuration file:
{
"extends": "./../../tsconfig.json",
"include": [
"./src",
"remoteEntry.d.ts"
],
"exclude": [
"./node_modules",
]
}
src
folder, add the remoteEntry.d.ts
file. We will create it later.Okay, we've set up TypeScript so let's move on to changing our JS files to TS. First, let's change the boostrap.jsx
file extension to bootstrap.tsx
. As you may have noticed, there aren't any types to add, so we don't have any compiler errors, so this file is perfectly TypeScript compatible.
Let's do a little more complicated stuff and start migrating the ErrorBoundary.jsx
file. Below you can see what the TypeScript-friendly ErrorBoundary.tsx
looks like:
import React, { ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends React.Component<Props, State> {
public state: State = {
hasError: false,
};
public static getDerivedStateFromError(_: Error): State {
return { hasError: true };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Uncaught error:', error, errorInfo);
}
public render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return (
<React.Suspense fallback={<div>Loading footer...</div>}>
{this.props.children}
</React.Suspense>
);
}
}
The last file left in the footer application is Footer.jsx
itself. This is where things get complicated. After changing this file to Footer.tsx
, you should get the following errors from lazy-loaded components:

Cannot find module ... or its corresponding type declarations.
basically means that there is no type declaration available for these modules.
So how to fix that? π€
Step 2 - create declarations
The above errors can be solved in several ways that can be found on the internet, but in my opinion the easiest and cleanest is to declare the modules in d.ts
files. In the footer
, the remoteEntry.d.ts
file (which we put in tsconfig.json
earlier) will look like this:
///<reference types="react" />
declare module 'navigation/Logo' {
const LogoLazy: React.ComponentType;
export default LogoLazy;
}
declare module 'navigation/Search' {
const SearchLazy: React.ComponentType;
export default SearchLazy;
}
For each lazy component we create a simple module type declaration with an appropriate name.
Step 3- Bit
The next big thing we'll add to our React Foodie application is the component tool. There are some popular solutions on the market like Storybook, but today we're going to use something else... Bit.
For those who don't know what Bit is:
Bit is an open-source toolchain for component-driven development.
You can read about Bit and micro-fronted on their post at THIS link to find out how it works, even on their website. Β
In the concept, Bit focuses on building anything from blocks and therefore works great with a micro-frontend approach and a Module Federation.
In our app, we're going to use something called Bit Harmony, which is
Bit is an open-source toolchain for composing modern applications from independent components. In the Bit world, we build components not apps.
To start working with it, you need to create a workspace for storing components in the cloud. I encourage you to create your own workspace and follow the official documentation as there are many other great features. Otherwise, have a look at how it is implemented in our application.
Step 4- NPM fallback
But what does Bit have to do with Module Federation?
In our application, we will use it in two ways. The first will be simple to use as standalone components installed as dependencies as after upload to Bit Harmony it loads components in several package managers such as NPM or Yarn simultaneously.
The second will be used as an NPM fallback.
Normal use of Bit components as dependencies can be seen e.g. in a list
micro-application in GridItem.jsx
, where we import a Snackbar
:
import { Snackbar } from '@krzysztoflen/react-foodie-app.snackbar';
And then an example of using the component:
return (
<div className="col-12 md:col-4">
<Snackbar
isOpen={isOpen}
onClose={handleClose}
variant={variant}
message={message}
style={{ boxShadow: 'none' }}
/>
It's pretty simple and obvious to those who use any UI component management tool.
But now let's use some components as a NPM fallback solution.
What is it you ask? π§
In the ErrorBoundary
component you can see that we have a check if some lazy loading component crashes and it will just display the text in the h1
tag. This approach increases the availability of the application as the user can still use the rest of the application even if one component fails.
That approach is called fault tolerance and you can read more about in the context of React HERE.
We can now extend our ErrorBoundary
to make it our application even more flexible and failed proof.
In my Bit workspace I have set up Footer
and Navigation
components that can replace the original ones if they crash.
Let's start importing it in App.tsx
in the app
micro-application:
import { Footer as NPMFooter } from '@krzysztoflen/react-foodie-app.footer';
import { FallbackNavigation as NPMFallbackNavigation } from '@krzysztoflen/react-foodie-app.fallback-navigation';
Next, we need to adjust the ErrorBoudary
itself a bit, which will now accept the following props:
<ErrorBoundary
loadingError="Loading fallback navigation"
loading="Loading navigation"
errorFallback={
<NPMFallbackNavigation
pages={[
{
label: 'List',
url: '/',
},
{
label: 'Cookbook',
url: '/cookbook',
},
{
label: 'Shopping List',
url: '/shopping-list',
},
]}
/>
}
localErrorFallback={<FallbackNavigation />}>
<NavigationLazy items={routes} />
</ErrorBoundary>
As you can see in props
, we have now passed two error handling attributes: errorFallback
and localErrorFallback
. The first loads our component from the NPM registry if the wrapped component crashes, otherwise it will use our local component passed in localErrorFallback
! π
We can do this with any element in our application and store components in the bit workspace!
Summary:
You did it! π
In this part, you can see the power of Module Federation supported by TypeScript and Bit. You already know how to deal with TypeScript errors related to lazy load components and how to prepare your application to be fault tolerance with Bit and NPM fallback approach.
This was the part II of the series and we will continue to expand this application in the future with other tools such as Redux and finally deploy our micro-frontend applications on a cloud host with a Github actions Continous Deployment approach.
So stay tuned!
If you missed a part I, you can check it out at the link below:

Thanks for reading β₯οΈβ₯οΈ
If this article was helpful, please leave a comment or π