Frontend modules

Developing frontend modules

After setting up your local environment, you'll typically want to work on a frontend module. Broadly speaking, the approach to follow to get a frontend module running locally involves the following steps:

Installing dependencies

We use yarn as our package manager. To install dependencies, you'll typically cd into the repo of the frontend module you want to work on and run yarn. For example, if you want to work on the patient-chart frontend module, you'd run:

cd openmrs-esm-patient-chart

Running frontend modules locally

Typically, you’ll want to run a specific frontend module within a monorepo. For example, you might want to run only the conditions frontend module. To do that, run yarn start --sources and pass in the name of your package of interest as a string:

yarn start --sources 'packages/esm-patient-conditions-app'

Note that the argument following packages in the command has to be the frontend module’s full path. It is also possible to pass multiple arguments to yarn start --sources. Doing so allows you to run multiple frontend modules simultaneously. Imagine a situation where you need to make a change to both the vitals and biometrics widgets in the patient chart. To get them to run simultaneously, you’d run:

yarn start --sources 'packages/esm-patient-biometrics-app' 'packages/esm-patient-vitals-app'

Running this command fires up a dev server in your default browser. By default, this server will run on port 8080. If you wish to run the server on a different you can specify that by passing a --port argument. For example, if you run the command above, you'd have biometrics and vitals getting served from http://localhost:8080/openmrs/spa (opens in a new tab). If you then want to run, say the patient search frontend module from the patient management monorepo, you could specify a different port number. You'd then run it like so:

yarn start --sources 'packages/esm-patient-chart-app' --port=7000

And that should fire up a local dev server at http://localhost:7000/openmrs/spa.

When you fire up a dev server in this way, several things happen under the hood. If you look at the scripts section of a manifest file (package.json) in a frontend module, you should see a start script that looks something like:

“start”: "openmrs develop"

What this means is that running yarn start invokes the develop command of the openmrs tooling. This command starts a new frontend development session using the OpenMRS app shell. The tooling sets up the import map, adds entries for core applications from the app shell and overrides the entries for the modules running locally. Ultimately, the corollary is that you can make changes to frontend modules in your local environment and have those changes propagate to the dev server in your browser. The tooling allows you to do various other cool things, such as:

  • Specifying an alternative backend server (other than the community dev3 server) and proxying requests to it.
  • Specifying an alternative target server path (defaults to /openmrs/spa).
  • Specifying an alternative API URL (defaults to openmrs).
  • Specifying a custom import map.

Running esm-patient-common-lib locally

The esm-patient-common-lib is a library of common components and utilities used primarily by the patient chart in O3. You might to make changes to the library and test those changes against a local dev server. To achieve that, you can do the following:

  • Launch the package.json file for the frontend module you want to work on. Look for the esm-patient-common-lib dependency in the peerDependencies section. Remove that entry and run yarn to install dependencies.

  • Run your local dev server using yarn start. For example, for the labs app, run:

    yarn start --sources 'packages/esm-patient-labs-app'
  • Try making a change to a library, e.g. adding a console.log to a file such as DashboardExtension.tsx. Your dev server should reload and you should see your console.log printed in the browser console once you navigate to the patient chart.

  • Once you're done making changes, you can revert the changes you made to the package.json file and run yarn to install dependencies again.

Running the app shell locally

Most of the time, this should be all a developer needs to do to get a local dev server running. One notable departure, however, is running the app shell. You’ll need to run the shell directly if you want to:

  • Make changes to the styleguide frontend module and test them out locally.

  • Test out changes to the following applications:

    • devtools - used for setting up import map overrides.
    • implementer tools - used for configuring extensions and UI components on the fly.
    • login - the reference application login page.
    • offline tools - the offline tools dashboard.
    • primary navigation - the navigation menu.

To run the app shell, you’ll need to cd into the esm-core monorepo and run:

yarn run:shell

This command will fire up a dev server at http://localhost:8080/openmrs/spa. You can then navigate to the app shell at http://localhost:8080/openmrs/spa/shell. From there, you can navigate to the applications you want to test out.

Because these applications reside in the apps directory of the esm-core monorepo, running them within the context of the app shell makes it a lot easier to test out changes. Something to note when using this approach, however, is that for your changes to propagate to your local server, you might need to rebuild your frontend module. To do this, run:

yarn turbo build

This command should run the build script in all of the constituent frontend modules whose code you’ve changed. Once the build is complete, you should see your changes reflected in the dev server in your browser.

How do I know where to make changes?

There is no simple correlation between frontend modules and pages and content. The first step to figuring out where to make changes is to identify the frontend module you want to work on. For example, if you want to make changes to the patient chart, you’d work on the patient-chart frontend module. If you want to make changes to the patient search, you’d work on the patient-search frontend module. If you want to make changes to the login page, you’d work on the login frontend module. And so on. There are a few ways to figure out which frontend module you need to work on:

  • You can inspect an element in the browser and look at its class name. You can then search through your IDE for that class name. This should point you to the specific bit of code in the frontend module you need to work on.
  • You could search for specific strings in the codebase. For example, if you see the string Vitals history in the UI, you could search for that string in your IDE. This should point you to the frontend module that renders that string (the vitals frontend module, in this case).
  • You could also use the UI editor in the implementer tools to see which extensions and extension slots get rendered in the UI. You can then search for those extensions and extension slots in your IDE and make changes to them. This is a bit more involved, but it’s a good way to get a sense of the structure of the UI and frontend modules it contains.
  • Go through the list of key repositories and their descriptions. There aren’t very many and they're typically domain-specific, so it’s likely you can narrow down what you’re trying to work on just by looking at the list.

There are no hard and fast rules for figuring out where to make changes. The best approach is to try out different things and see what works for you. The more you work on the codebase, the more familiar you’ll become with it and the easier it’ll be to figure out where to make changes. If you have done all of the things above and still haven’t found the code you’re looking for, feel free to ask in the #openmrs-helpme channel (opens in a new tab) on Slack.

How can I develop against a restricted environment?

In general, you can develop against another environment using the --backend flag. If the other environment is guarded, e.g., by an IP or network restriction then this is something you need to take care of on your local machine. In case the guarded environment is restricted via some SSO mechanism using a cookie you could use the --add-cookie flag to achieve this. As an example, look at the access for a development server from the ICRC:

npx openmrs start --backend "" --add-cookie "MRHSession=1234..."

The cookie must be obtained by you and strongly depends on the backend used.

Should I write tests?

Yes, absolutely! We have a test suite for the frontend modules in the monorepo. We use Jest (opens in a new tab) for testing. We also use React Testing Library (opens in a new tab) for testing React components. We have a testing guide that you can refer to for more information on how to write tests. We're adding support for Playwright e2e tests (opens in a new tab) as well across our repositories. Consider adding e2e tests for your changes as well. Going through the testing guide as well as the tests in the codebase should give you a good sense of how to write tests.

Pushing your changes to GitHub

Once you’re done working on a feature or fix, you’ll want to push your changes to GitHub and file a PR. To do this, package your changes in a commit. We have guidelines for writing commit messages in our contributing guide (opens in a new tab).

Creating a commit should trigger some pre-commit hooks. Typically, part of the work these hooks do involves running yarn run extract-translations. This task fires up turborepo and runs the extract-translations task in each package in the monorepo. This task extracts translation keys and strings and updates the default locale’s translations file, en.json in the translations directory of your frontend module. This is useful for internationalization. After the pre-commit hooks run, you then want to push your changes to GitHub. Doing so will trigger a pre-push hook that will typically run the following tasks in parallel using turborepo:

  • typescript - checks your application’s types.
  • lint - runs a lint check using Prettier.
  • test - runs tests using Jest and React Testing Library.

If any of these tasks fail, the push should fail. The terminal should display the error message(s) that caused the push to fail. You’ll need to fix the error(s) and push again. Once the push succeeds, you can go to the GitHub repo related to your package and file a pull request.

Maintaining dependencies

Maintaining up-to-date dependencies is important to leverage the latest features, performance improvements and security patches. Yarn 3 provides a handy interactive tool to help you keep your dependencies up to date. To use it, run the following command:

yarn upgrade-interactive

This command lists the dependencies of your project that are eligible for updates. You can navigate the list using your keyboard's arrow keys. In general, you want to avoid making major version updates because they can introduce breaking changes. You also want to keep OpenMRS-related dependencies (such as openmrs and @openmrs/esm-framework) pinned to next. Everything else should generally be OK to update to the latest version. Once you've selected the dependencies you want to update, you can press Enter to update them. Make sure to test your changes to ensure that nothing has broken. Ensure the app runs OK, tests pass and the app can get built correctly. If everything looks good, you can commit your changes and push them to GitHub.


I'm getting a bunch of errors related to missing dependencies

If you're working off of a fork, your fork might miss the latest changes from main. To fix this, you can run the following command to update your fork:

git pull upstream main --rebase

I'm getting an Error: ENOSPC: System limit for number of file watchers reached error

If you’re running Linux, you may see the following error the first time you run a dev server: Error: ENOSPC: System limit for number of file watchers reached. If that happens, you need to increase the system limit for the number of file watchers. See this StackOverflow answer (opens in a new tab).

I’m not seeing the latest @openmrs/esm-framework. How do I update the dependency?

If you notice your app suddenly stops working, it might be because a core library has changed in the app shell on the server you are developing against. The main dependencies shared by all OpenMRS frontend modules are openmrs and @openmrs/esm-framework. They can be updated like this:

# Upgrade core libraries
yarn up openmrs@next @openmrs/esm-framework@next

I'm getting a ERR_OSSL_EVP_UNSUPPORTED error when running yarn run build

If you're running or building the form-entry package (an Angular wrapper around the AMPATH Form Engine) using Node v18, you may see the following error:

Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:71:19)
    at Object.createHash (node:crypto:133:10)
    at BulkUpdateDecorator.hashFactory (/Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/util/createHash.js:145:18)
    at BulkUpdateDecorator.update (/Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/util/createHash.js:46:50)
    at RawSource.updateHash (/Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/webpack-sources/lib/RawSource.js:77:8)
    at NormalModule._initBuildHash (/Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:880:17)
    at handleParseResult (/Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:946:10)
    at /Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:1040:4
    at processResult (/Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:755:11)
    at /Users/denniskigen/code/openmrs/openmrs-esm-patient-chart/node_modules/@angular-devkit/build-angular/node_modules/webpack/lib/NormalModule.js:819:5 {
  opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
  library: 'digital envelope routines',
  reason: 'unsupported',

To get around this, run:

export NODE_OPTIONS=--openssl-legacy-provider

I'm getting errors after upgrading @carbon/react

You might see the following errors in your terminal when running a local dev server after upgrading @carbon/react:

ERROR in src/components/search-history/search-history.component.tsx:8:3
TS2305: Module '"@carbon/react"' has no exported member 'ModalHeader'.
ERROR in src/components/search-history/search-history.component.tsx:9:3
TS2305: Module '"@carbon/react"' has no exported member 'Pagination'.

To fix these, you need to update Carbon's ambient type declarations. To do so, add the following entries to your declarations.d.ts file:

declare module "@carbon/react";
declare type SideNavProps = {};

I'm getting merge conflicts in my yarn.lock file

To resolve merge conflicts in your yarn.lock file, run the following command:

git checkout HEAD yarn.lock && yarn

The verify job is failing on GitHub Actions for my PR

You likely have a lint or typescript error in your code. ESLint is setup to fail when it flags warnings. Run yarn turbo lint locally to the specific lint error in your IDE. For TypeScript errors, you should see the error in the CI log. If not, run yarn turbo typescript locally.

I'm getting 502 errors from attempting to fetch the session when running a local dev server

If you're running a local dev server and you're getting 502 errors from attempting to fetch the session, that's likely because the dev3 server (which your local dev server proxies to) is down. Check that you can log in to dev3 (opens in a new tab) first. If you cannot log in, then you'll need to wait for the server to come back up.

I'm using @openmrs/esm-patient-common-lib and getting "No workspace named '...' has been registered"

If you're getting this error, you most likely need to add @openmrs/esm-patient-common-lib to the peerDependencies list in your package.json. Also, make sure that you have @openmrs/esm-patient-common-lib: next listed under your devDependencies.

I'm getting an Oops! An unhandled promise rejection occurred error when loading my dev server

If you see an Unhandled promise rejection error when running a dev server or a production instance of O3, it likely means that you have a module that is not getting loaded correctly. The devtools console in your browser will likely contain an error message about the module that is not getting loaded. Typically, you'll want to start by checking that you don't have any stale overrides set up in the import map overrides panel. To fix the error, you will likely need to open the implementer tools panel and click the Reset all overrides button. Once you've done that, reload the page. The error should go away. Alternatively, you could also look through your browser's local storage and delete any overrides that you find there. Remember to reload the page after doing so.