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>
Our micro frontend solution will consist of three Bit components:
A micro frontend: An app bundled as an ESM library that can be imported and rendered by the host app (at runtime).
A remote module list: A list of shared modules and micro frontends consumed at runtime by our host app and micro frontend.
A host app: A container application that imports and renders the micro frontends.
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.
Explore this project by:
Reviewing the Bit components in their remote scope on bit.cloud
Forking (copying) the entire solution to your Bit workspace by running
bit scope fork learnbit.react-es-mfe YOUR_ACCOUNT_AND_SCOPE_NAME
(use your bit.cloud account and scope name)Visiting the deployed application
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
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
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
},
},
});
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
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
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.
},
]);
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
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(),
},
},
});
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
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).