/
Migrating to esm-core v5

Migrating to esm-core v5

This guide is for migrating frontend modules to Core v5. Please check your frontend module's package.json entries for @openmrs/esm-framework and openmrs to see if you need to migrate. If you're running anything higher than @openmrs/esm-framework@5.0.0 and openmrs@5.0.0, you're already on Core v5. If not, then you need to migrate.

Introduction

O3 provides a powerful module loading system that handles loading of frontend modules in the app shell. This system leverages Webpack module federation and forms the basis of our microfrontends architecture. However, it does suffer from a few historical drawbacks:

  • All frontend modules get sequentially loaded at app startup time, which has a big performance impact.

  • Because we're loading all modules at startup, we're also incurring the cost of executing all the dynamic import code for each module, even if the module is not used in the current page.

To address these issues, we've introduced a new module loading mechanism in Core v5. This new system essentially swaps out the app shell's implementation that loads all the frontend modules from the import map for an implementation that loads modules on demand. This means that modules are only loaded when they are needed, and only the code that is needed is executed. This yields a significant performance improvement compared to the old system. For example, in local testing, we've seen an approximately 3x reduction in the number of network requests needed to load the login page. Additionally, we've seen improvements to core web vitals metrics such as First Contentful Paint (FCP), Largest Contentful Paint (LCP), and Speed Index (as tested using Lighthouse on Google Chrome).

To leverage these improvements, you'll need to migrate your existing frontend modules to Core v5. This guide will walk you through the process of doing so.

In general, you need to do the following:

  1. Factor out static metadata into a routes.json file

  2. Factor out dynamic metadata into a startupApp activator function

  3. Upgrade core dependencies

  4. Check out the Troubleshooting guide

Case Study: Login frontend module

Let's take a look at the Login frontend module as an example. The original index.ts file for the module looks like this:

import { getAsyncLifecycle, defineConfigSchema } from "@openmrs/esm-framework"; import { configSchema } from "./config-schema"; declare var __VERSION__: string; // __VERSION__ is replaced by Webpack with the version from package.json const version = __VERSION__; const importTranslation = require.context("../translations", false, /.json$/, "lazy"); const backendDependencies = { "webservices.rest": "^2.24.0", }; const sharedOnlineOfflineProps = { online: { isLoginEnabled: true, }, offline: { isLoginEnabled: false, }, }; function setupOpenMRS() { const moduleName = "@openmrs/esm-login-app"; const options = { featureName: "login", moduleName, }; defineConfigSchema(moduleName, configSchema); return { pages: [ { load: getAsyncLifecycle(() => import("./root.component"), options), route: "login", ...sharedOnlineOfflineProps, }, { load: getAsyncLifecycle(() => import("./root.component"), options), route: "logout", ...sharedOnlineOfflineProps, }, ], extensions: [ { name: "location-picker", slot: "location-picker", load: getAsyncLifecycle(() => import("./location-picker/location-picker.component"), options), ...sharedOnlineOfflineProps, }, { name: "logout-button", slot: "user-panel-actions-slot", load: getAsyncLifecycle(() => import("./logout/logout.component"), options), online: true, offline: false, }, { name: "location-changer", slot: "user-panel-slot", order: 1, load: getAsyncLifecycle(() => import("./change-location-link/change-location-link.component"), options), ...sharedOnlineOfflineProps, }, ], }; } export { setupOpenMRS, importTranslation, backendDependencies, version };Factor out static metadata

 

Factor out static metadata

Each frontend module defines metadata that are either static or dynamic in nature. Static metadata include:

  • backendDependencies - the versions of backend dependencies that the module depends on.

  • pages - the pages that the module provides.

  • extensions - the extensions that the module provides.

These metadata are static in the sense that they do not change at runtime. They are also the metadata that are used by the app shell to load the module.

Looking at the entrypoint for the Login example from above, the static metadata we need to factor out are shown in below:

import { getAsyncLifecycle, defineConfigSchema } from "@openmrs/esm-framework"; import { configSchema } from "./config-schema"; declare var __VERSION__: string; // __VERSION__ is replaced by Webpack with the version from package.json const version = __VERSION__; const importTranslation = require.context("../translations", false, /.json$/, "lazy"); const backendDependencies = { "webservices.rest": "^2.24.0", }; const sharedOnlineOfflineProps = { online: { isLoginEnabled: true, }, offline: { isLoginEnabled: false, }, }; function setupOpenMRS() { const moduleName = "@openmrs/esm-login-app"; const options = { featureName: "login", moduleName, }; defineConfigSchema(moduleName, configSchema); return { pages: [ { load: getAsyncLifecycle(() => import("./root.component"), options), route: "login", ...sharedOnlineOfflineProps, }, { load: getAsyncLifecycle(() => import("./root.component"), options), route: "logout", ...sharedOnlineOfflineProps, }, ], extensions: [ { name: "location-picker", slot: "location-picker", load: getAsyncLifecycle(() => import("./location-picker/location-picker.component"), options), ...sharedOnlineOfflineProps, }, { name: "logout-button", slot: "user-panel-actions-slot", load: getAsyncLifecycle(() => import("./logout/logout.component"), options), online: true, offline: false, }, { name: "location-changer", slot: "user-panel-slot", order: 1, load: getAsyncLifecycle(() => import("./change-location-link/change-location-link.component"), options), ...sharedOnlineOfflineProps, }, ], }; } export { setupOpenMRS, importTranslation, backendDependencies, version };

Let's walk through the changes that we need to make to this file to build out the routes.json file step by step.

1. Create a routes.json file

Create a routes.json file in the module's root directory:

{ "$schema": "https://json.openmrs.org/routes.schema.json" }

The $schema property points to the routes schema file which is a standard JSON schema that enables your IDE to provide autocompletion and validation for the routes.json file.

2: Move backendDependencies

backendDependencies represents a list of backend modules necessary for this frontend module to work and the corresponding required versions. Move backendDependencies from index.ts to routes.json as follows:

3. Move pages

pages are automatically mounted ba