Micro Frontends with Vite and Bit

Eden Ella - Mar 13 - - Dev Community

In this tutorial, you'll learn the steps to create a micro frontend application leveraging the capabilities of Vite, Bit, 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 in the host app's index.html. For example:



<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>


Enter fullscreen mode Exit fullscreen mode

Our micro frontend solution will consist of three Bit components:

  1. A 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: A list of shared modules and micro frontends consumed at runtime by our host app and micro frontend.

  3. A host app: A container application that imports and renders the micro frontends.

The dependency graph of our micro frontends solution

The dependency graph of our solution. 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 on bit.cloud and deploy the apps (also Bit components) to their respective hosting services.

A component-based solution for Micro Frontends with Vite

Explore this project by:


Creating the micro frontend

To get started with our project, we'll create a new Bit workspace (replace my-account.my-scope with your own bit.cloud account and scope name):



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


Enter fullscreen mode Exit fullscreen mode

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:



bit create react-app my-mfe


Enter fullscreen mode Exit fullscreen mode

See the micro frontend component in its scope

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:



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
    },
  },
});


Enter fullscreen mode Exit fullscreen mode

The remote modules component, 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. 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:



bit run my-mfe


Enter fullscreen mode Exit fullscreen mode

The micro frontend running locally

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:



bit create node my-remote-modules


Enter fullscreen mode Exit fullscreen mode

See the remote module component in its scope

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



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.
  },
]);


Enter fullscreen mode Exit fullscreen mode

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:



bit create react-app my-host-app


Enter fullscreen mode Exit fullscreen mode

See the host application component in its remote scope

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.



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(),
    },
  },
});


Enter fullscreen mode Exit fullscreen mode

The custom plugin 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:



bit run my-host-app


Enter fullscreen mode Exit fullscreen mode

The host application running locally

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


Enter fullscreen mode Exit fullscreen mode

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).

Explore the components in their scope on bit.cloud

. . . . . . . . . .