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!

Webpack 5 Module Federation A-Z: React Foodie App (Part II) – TypeScript + Bit πŸ‘‘
Webpack 5 Module Federation part 2

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:

GitHub - KrzysztofLen/react-foodie-app at mf/part2
New version of React hotel app with new approach and tech stack - GitHub - KrzysztofLen/react-foodie-app at 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"
    ]
}
Basic TypeScript config in tsconfig.json file

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",
    ]
}
Footer localy tsconfig.json file which extends root config
πŸ’‘
In the include section, in addition to the 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>
        );
    }
}
ErrorBoundary in TypeScript

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:

TypeScript error 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;
}
remoteEntry.d.ts file

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:

Webpack 5 Module Federation A-Z: React Foodie App (Part I)
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!

Thanks for reading β™₯️β™₯️

If this article was helpful, please leave a comment or πŸ‘