This feature is currently in Early Availability (EA) status. For more information, see our product lifecycle phases.
Extensions Architecture
Overview
Behind our extensions architecture is a set of principles that we believe are key to building a successful ecosystem.
Principles
- Extensions are independent
- Extensions leverage the same tooling that our internal teams use
- Extensions are easy to build and test
Core Technologies
All new UI applications within the marketplace utilize React and Styled Components. Styled Components is particularly beneficial in a micro frontend environment, as it prevents style leakage to other applications. Webpack Module Federation manages the loading and unloading of UI applications without page refreshes.
How It Works
Our extensions employ a micro front-end architecture, meaning each extension is a self-contained application integrated into a container application. The container application is responsible for loading extensions at runtime.
Container Application
The container manages shared logic across all extension applications. While it uses React, it can load apps developed with any technology (e.g., Angular). The container's responsibilities include:
- Managing navigation components (header, footer, secondary navigation).
- Handling routing to micro front-end applications.
In the example below, all sections except the blue one are part of the container. The blue section represents an independently-loaded extension.
Leveraging Webpack Module Federation for Runtime Extension Dependency Management
We differentiate between local and remote modules. Local modules are standard modules included in the current build, while remote modules are not part of the current build and are loaded from a container at runtime.
In this context, extensions serve as remote modules loaded within the container.
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'Container',
remotes: {
MyApps: 'MyApps@http://localhost:3001/remoteEntry.js',
},
}),
Then we manage routing to extensions via a router.
const MyApps = lazy(() => import('MyApps'));
const Routes = () => {
return (
<Router>
<Header />
<main>
<SecondaryNav />
<Suspense fallback={<div>Loading MicroUI...</div>}>
<Switch>
<Route path="/mf-a">
<MyApps />
</Route>
</Switch>
</Suspense>
</main>
<Footer />
</Router>
);
};
Communication between the Container and Extensions
We may want the extensions to communicate with the container. There are multiple scenarios when this becomes necessary:
- Routing to another page
- Pushing global notifications
- Showing the error page
To this end, we can leverage DOM custom events. The main advantage here is that they are agnostic to JS frameworks and require no libraries. We could easily provide a better events API if needed but as long as internally it leverages DOM events the MicroUI container will be able to communicate with the MicroUIs.
Example:
window.dispatchEvent(new CustomEvent("router-update", { path: "/myapps"}));
window.dispatchEvent(new CustomEvent("show-error-page");
window.dispatchEvent(new CustomEvent("add-notification", { type: "info", message: "Profile Updated"}));
Was this page helpful?
Tell us more…
Help us improve our content. Responses are anonymous.
Thanks
We appreciate your feedback!