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 ofreact
orreact-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
orreact-redux
.No multiple instance, both
react-redux
andreact-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