Micro Frontends with Vite and Bit
Tags: javascript, webdev, typescript, react
Published: 2024-03-13 11:35:17


In this tutorial, you'll learn the steps to create a micro frontend application leveraging the capabilities of [Vite](https://vitejs.dev/), [Bit](https://bit.dev),  and the modern browser.

Our Micro Frontend applications (MFEs) and their shared dependencies will be bundled into ES Modules.

**In development**, our MFEs and shared dependencies will be loaded from our local workspace. This allows us to develop and test the MFEs locally without having to deploy them.

**In production**, our MFEs will be loaded (in runtime) from their remote entry point according to the [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) in the host app's `index.html`. For example:

```html
<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18.2.0",
    "@learnbit/react-es-mfe.my-mfe": "https://bit-react-esm-mfe.netlify.app/v0.0.1.js"
  }
}
</script>
```

Our micro frontend solution will consist of three [Bit components](https://bit.dev/reference/components/the-bit-component):

1. **[A micro frontend](#creating-the-micro-frontend)**: An app bundled as an ESM library that can be imported and rendered by the host app (at runtime).

2. **[A remote module list](#creating-the-remote-modules-mapping)**: A list of shared modules and micro frontends consumed at runtime by our host app and micro frontend.

3. **[A host app](#creating-the-host-app)**: A container application that imports and renders the micro frontends.


![The dependency graph of our micro frontends solution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8gjjnwpfg3y5yes36pp.png)

> _[The dependency graph of our solution](https://bit.cloud/learnbit/react-es-mfe/my-host-app/~graph). The host application and the micro frontend both share the list of remote modules (to be excluded from their bundles and imported dynamically)._

In this project, we'll use Bit to manage different parts of our solution as Bit components. We'll [share the components](https://bit.dev/docs/getting-started/collaborate/exporting-components) on [bit.cloud](https://bit.cloud) and deploy the apps (also Bit components) to their respective hosting services.


![A component-based solution for Micro Frontends with Vite](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kyq8blvdwhm1z9cevap.png)

Explore this project by:

- Reviewing the Bit components [in their remote scope on bit.cloud](https://bit.cloud/learnbit/react-es-mfe) 


- Forking (copying) the entire solution to your [Bit workspace](https://bit.dev/docs/getting-started/installing-bit/start-bit-workspace) by running `bit scope fork learnbit.react-es-mfe YOUR_ACCOUNT_AND_SCOPE_NAME` (use your [bit.cloud](https://bit.cloud) account and scope name)

- Visiting the [deployed application](https://bit-react-esm-host-app.netlify.app/)

---

## Creating the micro frontend

To get started with our project, we'll create a new [Bit workspace](https://bit.dev/docs/getting-started/installing-bit/start-bit-workspace) (replace `my-account.my-scope` with your own [bit.cloud](https://bit.cloud) account and scope name):

```bash
bit new basic my-workspace --default-scope my-account.my-scope
cd my-workspace
```

Since our solution is entirely component-based, our host app and micro frontend will also be maintained as independent Bit components.

To create a new React Vite app, we'll use Bit's out-of-the-box component template for [React Vite apps](https://bit.dev/docs/apps/create-react-apps):

```bash
bit create react-app my-mfe
```
> _[See the micro frontend component in its scope](https://bit.cloud/learnbit/react-es-mfe/my-mfe/~code/vite.config.js)_


To make our MFE dynamically loadable as an ES Module, we'll configure Vite to build it as a library with the `'es'` format.

We'll do this by adding the necessary configuration to the `vite.config.js` file at the root of our component's directory:

```javascript
import { defineConfig } from 'vite';
import reactPlugin from '@vitejs/plugin-react';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { myRemoteModules } from '@learnbit/react-es-mfe.remote-modules';

export default defineConfig({
  plugins: [
    reactPlugin({
      jsxRuntime: 'classic', // it's easier to exclude react from the bundle when not using the new jsx runtime
    }),
    cssInjectedByJsPlugin(), // this is to inject this MFE's CSS, when it is loaded
  ],
  define: {
    'process.env': '{}', // this is to avoid 'process is undefined' error in the browser
  },
  build: {
    lib: {
      formats: ['es'],
      entry: './my-mfe.tsx',
      fileName: 'v0.0.1', // it's recommended to use a version for each MFE deployment
    },
    rollupOptions: {
      external: myRemoteModules.listModules(), // the remote modules to exclude from the bundle
    },
  },
});
```

The [remote modules component](#creating-the-remote-modules-mapping), used by this config, contains a list of modules to be loaded dynamically at runtime, and to be excluded from the MFE and host app bundles. This list contains shared dependencies like `react`, as well as micro frontends.

We'll also exclude shared dependencies from our MFE's bundle to keep our bundles small and avoid duplications.

Bit components are configured as "app components" when they include a [`*.bit-app.ts` file in their component directory](https://bit.cloud/learnbit/react-es-mfe/my-mfe/~code/my-mfe.bit-app.ts). This file configures the app's build and deployment. In this example, we'll deploy our app components (the MFE and host app) to Netlify.

Since this is an app, we can run it locally using the `bit run` command:

```bash
bit run my-mfe
```


![The micro frontend running locally](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5t16f02dzhnsmmypw8b.png)

> _Run `bit app list` to list the apps available in your workspace_

## Creating the remote modules mapping

As mentioned earlier, our MFEs and host app will use a (shared) Bit component to manage the list of modules to be loaded dynamically at runtime.

We'll create a new Bit component to manage this list. This time, we'll use Bit's out-of-the-box component template for a simple Node.js module:

```bash
bit create node my-remote-modules
```
> _[See the remote module component in its scope](https://bit.cloud/learnbit/react-es-mfe/remote-modules/~code/remote-modules-example.ts)_

Our list of modules will include the shared dependencies and the MFEs. In this example, we'll include `react` and the previously created MFE.

```javascript
import { RemoteModules } from './remote-modules.js';

export const myRemoteModules = new RemoteModules([
  {
    name: 'react',
    path: 'https://esm.sh/react@18.2.0',
  },
  {
    name: '@learnbit/react-es-mfe.my-mfe',
    path: 'https://bit-react-esm-mfe.netlify.app/v0.0.1.js', // the MFE's URL as defined by its bit-app.ts file.
  },
]);
```

## Creating the host app

To create our host application, we'll, again, use Bit's out-of-the-box component template for a [React Vite app](https://bit.dev/docs/apps/create-react-apps):

```bash
bit create react-app my-host-app
```

> _[See the host application component in its remote scope](https://bit.cloud/learnbit/react-es-mfe/my-host-app/~code/vite.config.js)_

Our host app will be built as a regular Vite app. However, since it should dynamically load the MFEs, we'll need to configure it to exclude shared dependencies and MFEs from its bundle.
We'll, also inject an import map to the HTML file, to instruct the browser on how to resolve imports at runtime.

```javascript
import { defineConfig } from 'vite';
import reactPlugin from '@vitejs/plugin-react';
import { myRemoteModules } from '@learnbit/react-es-mfe.remote-modules'; // the remote modules list
import importMapPlugin from '@learnbit/react-es-mfe.vite-import-map-plugin'; // a custom Vite plugin to inject the import map

export default defineConfig({
  plugins: [reactPlugin(), 
// inject the import map (from the remote-modules component)
importMapPlugin(myRemoteModules.createImportMap())], 
  build: {
    rollupOptions: {
      // exclude remote modules from the app's bundle
      external: myRemoteModules.listModules(),
    },
  },
});
```

[The custom plugin](https://bit.cloud/learnbit/react-es-mfe/vite-import-map-plugin/~code/vite-import-map-plugin.ts) used by the host app replaces the `<--importmaps-->` placeholder in the HTML file with the import map generated by the remote modules list.

To run the host app locally, we can use the `bit run` command:

```bash
bit run my-host-app
```


![The host application running locally](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pr1jsffif6n5x40484qx.png)



In development, our host app will consume the MFEs from the local `node_modules` directory in the workspace, and will not load them from their deployed URLs (as it would in production).

This allows us to develop and test the MFEs locally, without having to deploy them (when Bit components are maintained locally, they are symlinked from their source directory to the `node_modules` directory. Otherwise, they are installed from the registry).

## Sharing the components and deploying the apps

Run the following to build your components, export (push) them to their remote scope on bit.cloud, and deploy the app components to their hosting (as configured in the `.bit-app.ts` file of each app component):

```
bit tag -m "first version"
bit export
```

> The components will be built on bit.cloud. Once the build is over, the components will be available in their remote scope (the remote scope was configured at the beginning of this tutorial when we initialized a new Bit workspace with the `--default-scope` property).

{% cta https://bit.cloud/learnbit/react-es-mfe %} Explore the components in their scope on bit.cloud {% endcta %}