Yet Another Micro Frontend Proof of Concept

July 15, 2019

Yes, this is yet another proof of concept for micro frontend architecture.

Please check out the code and try to play with it locally.

What is this different?

  • No centralised routing definition

    Once the main SPA is deployed, new routes can be added to the SPA without redeploying the main SPA.

  • A central registry

    Individual micro frontend does not need to bundle in common dependencies like react, react-router, eg. Furthermore, they shouldn't be bundled into the micro frontend as multiple instance of react or react-router may cause some bugs.

  • A shared global state

    In this demo, I am using redux for state management.

    Each micro-frontend is connected to the same global store, so data is easily shared across the application.

The Bridge

The center piece of this micro frontend architecture is the Bridge.

It contains 2 types of communication method:

  • message passing
  • shared memory

Message passing is done via a pub sub mechanism called the Channel, whereas shared memory is done via the Registry.

I attach the Bridge to the window, so each micro frontend can connect to the bridge.

The routing mechanism

In the PoC, I used react-router as the routing library. The route definition is dynamic, as it listens "registerRoute" event through the Bridge, and add it to the route definition.

One thing to note is that, I use <Switch />, which will render the first <Route /> that matches the url.

The magic happens in the last route, which will be rendered when none of the routes in the route definition matches.

The last route renders the Fallback Discovery component, which will based on the current location and some pre-defined rules, tries to find the micro frontend manifest for the unknown route.

If the micro frontend manifest does not exist, it will render a 404 page. However, if the manifest exists, it will fetch the manifest file.

The micro frontend manifest file is nothing but a json file that contains the entry asset for the route micro frontend.

Once the entry asset is loaded, the new micro frontend itself will register a new route to the route definition via "registerRoute" event, which has to be the route that matches the current URL.

Whenever there's a new route added, the react-router's <Router /> component will re-render and it will match the newly added route, which renders our newly added micro-frontend.

The global registry

There's 2 main reasons why I am not bundling react, react-redux, and react-router into each micro frontends:

  • Bundle size, there's no reason to download duplicate copies of react or react-redux.

  • No multiple instance, both react-redux and react-router uses React Context to pass information down the React component tree. However, this way of data passing disallow multiple instances of the Context object, as each instances has a different memory reference.

So, the main SPA runtime bundles react, react-redux, etc and set it into the Bridge.Registry..

When each micro-frontend is built, the import statement for these modules will be transformed into the some aliases, that reads the module from the Bridge.Registry.

Shared Global State

In the PoC, the micro frontends are connected to the shared global store via connect() from react-redux

Interesting to note is that both the food detail and the food list register the same key for the reducer, but yet both of the works seamlessly.

Note that when we transit from Food List page to Food Detail page, we already have the food detail information from the store, therefore there is no need to fetch the food detail anymore.

Trying it out locally

Run yarn start in the root folder, which will start a simple express server. The express server is used to serve static files based on specific rules.

To build individual micro frontends, you need to go to each folder and run yarn build. Alternatively, you can start the watch mode via yarn dev.

Developing the micro frontend individually

WIP