Building Micro Frontends with React that can be used in any Application

Photo by Markus Winkler on Unsplash
  • Do you have an application using an old version of Angular or an HTML templating engine that you want to add React components to?

If any of those interest you or you just want to know how to easily bundle existing React components so they can be used in any application than keep reading.

TL;DR:

This micro frontend pattern takes any existing react component and exposes a wrapper around it that allows the component to be bundled, shared and used in any other app. Using this pattern allows for incremental upgrades, decoupled codebases, independent deployments, and autonomous teams which are some of the great benefits of micro frontends that the article by Cam Jackson on Martin Fowlers Blog discusses in more detail.

High Level Steps:

  1. Create a new file for each component you want to share that wraps the component with a `ReactDOM.render()` call

Check out the CodeSandbox links at the end of this article to see live code examples using this pattern within Angular, Vue.JS, and React apps.

Example snippet to add an element to a pure HTML app (step 4):

<div
id="ReactDynamicCard"
data-card-image="https://reactjs.org/logo-og.png"
data-card-header="React"
data-card-sub-text="My preferred JS library for building UIs"
data-card-link="https://reactjs.org/"
></div>
<script
data-id="ReactDynamicCard"
src="https://universal-component-library.s3.amazonaws.com/DynamicCard.js"
></script>

For frameworks that do not allow script HTML tags (React, Angular and Vue), you can add a pure JS code snippet like below instead of the script tag:

const script = document.createElement('script');
script.setAttribute('data-id', 'ReactDynamicCard');
script.src = 'https://universal-component-library.s3.amazonaws.com/CustomAppBar.js';
document.body.appendChild(script);

Example unpkg.com script elements (step 5):

<script
src="https://unpkg.com/react@16.11.0/umd/react.production.min.js">
</script>
<script
src="https://unpkg.com/react-dom@16.11.0/umd/react-dom.production.min.js">
</script>

Full Walk Through

This walkthrough references code examples from my universal-components GitHub repo. It shows this pattern in use and how the components are structured, built, uploaded, and demoed/documented using Storybook.

Prerequisite:
- Have existing React component(s) or page(s) that you want to share
- Use rollup as the build tool. Parcel or Webpack bundled apps can be converted to use Rollup, but this article is written assuming that is done.

Note: Rollup is used since it has the ability to pass multiple entry points into the configuration, which is required, if wanting to bundle multiple components into their own separate JS file.

Additions needed within your existing repo

1.) Create a new file called “widget.jsx” in the existing component’s directory.

Note: The name of the file is the naming convention utilized by rollup in this example. When rollup iterates over each components folder it looks for a specific file name to exist, but that name can be anything instead of “widget”, if desired.

2.) Add import statements for your existing component, React, and ReactDOM.

3.) Create a variable that pulls the “data-id” property from current script element.

4.) Create a variable that gets an element by id using the “data-id” value in the previous step.

Note: The built components will be referenced in other applications via a script element with the “data-id” attribute that maps to the “id” attribute of the div element where the React component should be rendered.

5.) Add a call to the “ReactDOM.render()” function passing in your existing component and the element retrieved in the previous step.

6.) Optional: if your component has any props that should be passed then spread the div elements “dataset” field within your components JSX syntax.

Note: The dataset property on an element maps all “data-*” attributes from that element to a DOMStringMap which is very similar to a JSON object except only strings can be used for values. For example, a value of false will get converted to a string of the word false. This piece allows the consuming application to pass properties into a div element that the React component will consume as props during rendering.

7.) Repeat all of the previous steps for each component within your repo.

Example “widget.jsx” file after completing steps 1–6:

import React from 'react';
import ReactDOM from 'react-dom';
import DynamicCard from './DynamicCard';
const widgetId = document.currentScript.dataset.id;
const widget = document.getElementById(widgetId);
ReactDOM.render(
(<DynamicCard {...widget.dataset} />),
widget,
);

This one new file per component is all that is needed as far as modifications go to the existing source code.

Rollup bundler updates:

View the rollup.config.js file in the universal-components-library example repo to see a full example of all the changes discussed in this section.

There are four changes that are needed for the rollup config to create multiple output files compared to a config that produces one bundle of the entire app or library.

  1. Create a new variable that contains an array of all the component names you want to bundle separately.
    Note: This could be automated by using the file system packages and pulling directory names, but this way it allows adding one component at a time instead of an all or nothing approach.

Add components to a public object storage tool

To allow other applications to utilize the bundled components they need to be accessible to the public. Since the files are bundled into static JS files, they can be stored in any object storage service of your liking.

For this example, I utilized AWS’s S3 and created an uploadWidget.js script to upload all files that were created during the rollup build process. This script requires four environment variables which are provided when creating a bucket with S3.

Here is a good medium article walking through the steps to create a S3 bucket and retrieving the needed environment variables.

Use Component in existing application

Now that your components are built and uploaded to an object storage location you can start consuming them. There is one prerequisite needed prior to your app using the shared components. If you are adding these components to a React app then the prerequisite is already met and can be skipped.

Prerequisite: Add the react and react-dom script elements (see snippet below) into the head element within the top level index.html file of your app. By adding both dependencies one time in the root HTML page, several of these micro frontend components can now be added anywhere throughout the app.

<script
src="https://unpkg.com/react@16.11.0/umd/react.production.min.js">
</script>
<script
src="https://unpkg.com/react-dom@16.11.0/umd/react-dom.production.min.js">
</script>

Why these scripts are needed: The rollup config in this example sets react and react-dom as peer dependencies. This means that each component requires the consuming application to have both of those dependencies available in order to render on the page. Existing React applications already have them built in, but all other applications need them manually added. Instead of bundling within each component adding substantially to the minified size, just adding it once in the consuming app allows for several components to be added.

Step 1:
Add a div HTML element (similar to the snippet below) with an id and optional “data-*” attributes in the code where you want it displayed. The “data-*” attributes get mapped to the React component properties as we discussed previously within the “widget.jsx” file that was used as the entry point for bundling the component.

<div
id="widgetId"
data-card-image="https://reactjs.org/logo-og.png"
data-card-header="React"
data-card-sub-text="My preferred JavaScript library for building user interfaces"
data-card-link="https://reactjs.org/"
>
</div>

Step 2:
Add script HTML element that will hydrate the div with the shared component within your UI. The script needs a “data-id” attribute that should be set to the same id of the div element added from step 1. It also requires a “src” attribute that should link to the object storage location of the component you are adding. Most UI frameworks do not allow script tags within the code of the UI. If you are using one of those frameworks then reference Snippet B instead of A.

Snippet A (for Pure HTML apps):

<script
data-id="widgetId"
src="https://universal-component-library.s3.amazonaws.com/DynamicCard.js"
>
</script>

Snippet B (for apps using a framework):

const script = document.createElement('script');
script.setAttribute('data-id', 'widgetId');
script.src = 'https://universal-component-library.s3.amazonaws.com/DynamicCard.js';
document.body.appendChild(script);

Step 3:
Start your web application and you will now see your React component running within your existing app.

Live Component Documentation

Every component library should have some kind of UI documentation. With this pattern you can utilize storybook to view the components when running locally as well as produce the exact code snippet someone would need to copy and use in their consuming application. Check out the live storybook doc created for this example repo as a Github page. You will see both the HTML only snippet and the JS snippet shown in the notes section.

XP Engineer @ Allstate

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store