Docs
Frontend modules
Development

Developing frontend modules

After setting up a frontend module locally, you'll want to start making changes to it. This guide will walk you through the steps you need to follow when doing local development on a frontend module. We'll cover how to install dependencies, run frontend modules locally, write tests, push your changes to GitHub, maintain dependencies and troubleshoot common issues.

Installing dependencies

In most cases, we use the latest version of yarn (opens in a new tab) as our package manager. To install dependencies, you'll typically cd into your local copy of the frontend module you want to work on and run yarn (which is short for yarn install). For example, if you want to work on the Patient Chart (opens in a new tab) frontend module, you'd run:

cd openmrs-esm-patient-chart
yarn

Running frontend modules locally

Non-monorepos

Some of our projects are not monorepos. For example, the Form Builder (opens in a new tab) is a standalone project. To run the Form Builder locally, you'd cd into the project directory and run yarn start. For example:

cd openmrs-esm-form-builder
yarn start

You shouldn't need to scope commands by their target packages in non-monorepos.

Monorepos

Most of our frontend modules are part of monorepos. For example, the Patient Chart (opens in a new tab) monorepo consists of several frontend modules that handle Patient-related concerns. You'll typically want to run a specific frontend module within a monorepo. To do that, run yarn start --sources and pass in the relative path to your target frontend module. For example, to run just the Vitals (opens in a new tab) frontend module, you'd run:

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

Where the argument following --sources is the relative path to the frontend module you want to run.

It is possible to run multiple frontend module simultaneously. For example, if you're making changes to the Order Basket (opens in a new tab), you'll likely want to test those changes against the Medications and Orders frontend modules. To do that, you'd run:

yarn start --sources packages/esm-patient-medications packages/esm-patient-orders-app

This should fire up a Webpack dev server running local copies of the Medications and Orders frontend modules. Any local changes should propagate to the dev server in your browser.

By default, the dev server will run on port 8080. If you wish to run the server on a different port, you can pass a port number as the argument to the --port flag. For example, to run a dev server for the Vitals frontend module on port 7000, you'd run:

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

openmrs tooling deep dive

yarn start is a shorthand for running the openmrs CLI (opens in a new tab). More specifically, it's aliased to run openmrs develop, which creates a produ ction build of the app shell and runs it against a given OpenMRS backend. It spins up a Webpack dev server on port 8080 and proxies requests to the default backend, which is hosted at https://dev3.openmrs.org (opens in a new tab). This dev server will by default run local copies of the frontend modules you specify and override their import map entries. All the other frontend modules run on that dev server should be the latest versions from the community dev3 server. With openmrs develop, you can also:

  • Specify an alternative backend server (other than the community dev3 server) and proxy requests to it. For example, to run the Vitals app and proxy requests to the O3 QA dev environment's backend, you can run:

    yarn start --sources packages/esm-patient-vitals-app --backend=https://test3.openmrs.org
  • Specifying an alternative target server path (defaults to /openmrs/spa).

  • Specifying an alternative API URL (defaults to openmrs).

  • Specifying a custom import map.

To learn more about the develop command, read through its implementation (opens in a new tab).

Using import map overrides

O3 determines where to load the code for frontend modules from based on the import map provided to the app shell. The import map is a JSON object that maps module specifiers to URLs. You can find the import map for your O3 distro by navigating to /openmrs/spa/importmap.json in your browser. The Devtools (opens in a new tab) frotend modules provides an interface for overriding entries in your import map. You can leverage this when doing local development to load copies of your frontend modules from your local dev server. For example, if you fire up a local development server running the app shell, it's possible to override import map entries for multiple frotend modules from multiple projects at the same time. This is useful when you're working on a feature that spans multiple frontend modules.

⚠️

Import map overrides don't work on the dev3 server because of security restrictions. We'll work on loosening these restrictions in the future. For now, you can only use import map overrides on a local dev server.

Example: Running the Implementer tools and Primary navigation frontend modules against the app shell

Run the app shell

cd @openmrs/esm-core
yarn run:shell

This should fire up a dev server on port 8080 (assuming you have no other dev server running).

Run the implementer tools app

In a different terminal window, run:

cd openmrs/esm-core
yarn start --sources packages/apps/esm-implementer-tools-app --port=7000

This should fire up a dev server on port 8081 (the next available port). To see the actual bundled JavaScript code for the app, navigate to http://localhost:8081/openmrs-esm-implementer-tools-app.js (opens in a new tab) in your browser. This URL is the entry point for the app shell to load the implementer tools app. It's important that the URL points to the correct location of the bundled JavaScript code. Often, you might want to specify a different port number to ensure that the URL points to the correct location than the default port or whatever port the tooling assigns.

Port numbers

By default, the Webpack dev server runs on port 8080. If you're running multiple dev servers, the next available port is assigned to the next dev server. For example, if you're running a dev server on port 8080 and you start another dev server, it will run on port 8081. If you start another dev server, it will run on port 8082, and so on. This is also important to not when setting up import map overrides. If you're running a frontend module using yarn start on port 8080, the URL to the bundled JavaScript code will be http://localhost:8081/openmrs-esm-form-builder-app.js (opens in a new tab). The name of this URL corresponds to the name used in the browser (opens in a new tab) entry in the package.json file of the frontend module you're running. If you're running a frontend module on a different port, you'll need to adjust the URL accordingly.

Note that if you're running multiple frontend modules simultaneously using yarn start, the app shell will run on port 8080 and the frontend modules will run on the next available ports. That is, the packages will run on port 8081, 8082, and so on, if available. However, if you're running each frontend module individually, you'll likely have to configure the port number manually as the app shell will default to trying to load the frontend module from the next available port after 8080.

Run the primary navigation app

In a different terminal window, run:

cd openmrs/esm-core
yarn start --sources packages/apps/esm-primary-navigation-app --port=9000

Consolidate everything

On the dev server running the app shell, login and navigate to the home page. We want to make sure that import map overrides are enabled. Type this command in the browser devtools console and press enter:

localStorage.setItem('openmrs:devtools', 'true')

Reload the page. You should see a new icon in the bottom right corner of the screen. Click on it to launch the import map overrides panel. You can now override import map entries for the frontend modules in your distribution with locally running copies. Add an import map override for the Implementer tools app by searching for the app in the Search modules input field. You should see the app in the search results. Type the URL to the bundled JavaScript code for the app in the URL input field.

For the Implementer tools app on port 7000, the URL would be http://localhost:7001/openmrs-esm-implementer-tools-app.js (opens in a new tab). Click the Apply button to add the override. Next, do the same for the Primary navigation app. The URL for the Primary navigation app on port 9000 would be http://localhost:9001/openmrs-esm-primary-navigation-app.js (opens in a new tab). Click the Apply button to add the override. The changes should take effect immediately. If you reload the page, you should see the icon turn red to signify that there are import map overrides in place.

You should now see your changes to the Implementer tools and Primary navigation apps propagate to the app shell. You can now test out your changes in the context of the app shell.

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 "https://emr-v2.test.icrc.org/" --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.

Troubleshooting

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 see errors about missing APIs when I run the app

If your dev server is throwing errors about missing APIs or functions, it's likely because you're running outdated versions of core tooling. This means that new functionality was likely added to Core (opens in a new tab) that your local dev server doesn't have. New pre-release versions of the openmrs (opens in a new tab) CLI and @openmrs/esm-framework (opens in a new tab) get published each time a pull request is merged in Core. To ensure you have the latest versions of these packages, run:

yarn up openmrs@next @openmrs/esm-framework@next

This command might change the versions of the packages specified in your manifest file (package.json). If it does, you'll need to revert the versions back to next before you commit your changes. You might need to run yarn to install the new versions of the packages after effecting the changes. Once you've done that, you should be able to run the dev server without any issues. Be sure to only commit the updated yarn.lock file.

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',
  code: 'ERR_OSSL_EVP_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.