Module Federation
Invent utilizes Webpack Module Federation capabilities to manage runtime-loaded
"widgets".
Those are standalone applications of varying complexity, bundled in a specific way
and available from a remote host - "Invent App Store".
Read more on Module Federation here
TLDR for development
Enter the following commands inside the host app and each used widget:
npm run build_federation
npm run start_federation
Reasoning behind using Webpack Module Federation
-
Webpack is the most popular JS app bundler and is open source. We can get support and solve our problems just by looking at GitHub issues.
-
It offers a standard way of shared dependencies management, so we don't have to code and maintain it ourselves. "Remote apps" will reuse "host app" dependencies utilizing webpack functionality, the result is lighter bundles.
-
As INVENT targets vendor in-house developers as well, using standard solution as a base eliminates a lot of concerns regarding platform adoption.
INVENT Platform as Host Application
Through the Module Federation Webpack plugin, the host app exposes shared dependencies
for all the runtime-loaded widgets to consume. The list with required versions comes
from @invent/webpack-config, and is a source of truth regarding dep versions.
- There is a consistency problem right now, that
package.jsonof the host app is not synced in any way with the@invent/webpack-configshared deps list.
All the code used for runtime loading/init of remote apps resides in
lib/module-federation directory.
When there is a need to load a new widget - "remote module", the following steps happen:
RemoteModulewrapper - a React component - is being rendered somewhere in the tree, and receives required props, like that:
<RemoteModule
url="http://some-url/remoteEntry.js"
scope="ScopeName"
module="./ModuleName"
componentProps={props}
/>
Remote modules can render other remote modules, all the tooling is passed
as props and typings are available through the shared-types library. The following
steps are required:
- destructure
remoteModuleandgenerateWidgetDatafrom props. - use
RemoteModuleTandGenerateWidgetDataTfrom@invent/shared-typesas typings. - get widget metadata from
platformMetaprop. - generate the remote module data passing widget name and id according to the typing
- pass generated data to the remote module:
const data = generateWidgetData(name, widgetId)
<RemoteModule
scope={data.scope}
module={data.component}
componentProps={{...}}
url={data.host}
/>
-
The
<script />tag is being inserted into the document by theRemoteModulecode, withsrcequal to providedurlprop, and bundle code is being loaded by the browser. -
Webpack runtime tries to find a "namespace" according to the
scopewe provided, and the "module" inside - using a path from the prop. On success, it resolves the React.lazy component load and a React component instance is being created.
The host app also tracks used scopes (widgets), using the redux store, and if there aren't
any currently mounted components that use a remote module code, it <script />
the tag will be deleted from the document.
Make sure that
<RemoteModule />components are not being re-mounted in the tree, it will break<Suspense>and script-loading logic, resulting in not-loaded component fallback flash, and other problems usually connected with remounts
Widget as Remote Application
Each application resides in its repository in Invent GitLab, and is
prefixed with widgets-. Invent widgets also have the prefix core-, and vendors
have their company name prefixes. So for a calendar widget produced by Invent,
the repo name would be widgets-core-calendar, and for the Boeing notifications widget -
widgets-boeing-notifications.
It can be developed as a standalone application and served as a federated module using Invent widgets webpack config
- Please note, that
widgetIdused for bundle identity is a GitLab repository id, not just a random number.
Production Pipeline
We have the following understanding of the widget bundles delivery process:
- merge in a widget "master" branch triggers CI publish.
- CI populates build variables, giving it the URL where the bundle will be hosted.
- after being bundled by CI, code is accessible by the URL.
So, all the widgets can be available on a single domain, like:
invent.store.com/widgets/:id