Loading frontend modules into the app shell
Frontend modules in O3 are loaded dynamically into the app shell using Webpack Module Federation (MF) (opens in a new tab). Module Federation allows code to be shared at runtime between independently deployed apps, which is what makes O3 microfrontends possible.
The app shell determines what to load from the distribution's import map (generated from spa-assemble-config.json during distro assembly). The import map is provided as a SystemJS import map (type="systemjs-importmap"), which SystemJS uses to resolve module names to URLs before the app shell loads them.
In short: SystemJS resolves the URLs, and Module Federation loads the code. This split lets O3 load modules on demand and keep the initial bundle smaller.
Example flow
Here’s a minimal end-to-end example of how a module gets loaded:
-
spa-assemble-config.jsonincludes@openmrs/esm-patient-chart-app. -
The distro build produces an import map entry like:
{ "imports": { "@openmrs/esm-patient-chart-app": "https://example.org/openmrs/spa/esm-patient-chart-app.js" } } -
SystemJS resolves the module name to that URL.
-
The app shell loads the remote container from that URL via Module Federation and calls
init/get.
Module federation
As mentioned before, our module loading system is based on Module Federation. O3 uses a dynamic remote containers pattern. The application entry point is the app shell, and the list of remotes to load is supplied via the import map.
Each module ("remote") is provided as a name and URL. For each URL, the app shell appends a <script> element to the DOM. Because those scripts use Webpack's var library type, they create a global variable that exposes the Webpack container interface (init and get). The app shell calls those methods to load the module's exports.