How to implement micro-frontends (MFEs)

This is an experimental feature, currently available on the branch Bahmni-IPD-master of openmrs-module-bahmniapps. To follow this guide, you need to be on this branch as of now. Some details may change over time.

This guide will go step by step through implementing your own MFE with Bahmni. For example purposes, let’s say you want to create a micro-frontend named diagnostics which will export a single component called DiagnosticsDashboard.

Step 1: Create a MFE repository

You can clone bahmni-microfrontend-reference repository for a solid starting point. The README.md will contain a checklist for you to go through and set-up the repository with essential information.

Once you follow through the checklist, you should end up with a module name of bahmni_diagnostics in the ModuleFederation plugin and DiagnosticsDashboard should be part of the exposes property of the same.


Step 2: Decide on a serving strategy for your MFE

On running yarn build, you should get built files in dist/federation/ folder. Files within this need to be served from a web server. This would be our remote URL.

2.1: Docker container with proxy

The reference repository assumes that you want to serve your MFE from within the bahmni-docker setup as a single container based on a reverse proxy. If you go through this route, follow the steps below

2.1.1: Update the GitHub workflow and Dockerfile to build an image named bahmni/microfrontend-diagnostics.

2.1.2: Add appropriate entry into the docker-compose setup that you are using. This could be bahmni-docker or some fork of it as per your use case. You should end up with a container named (for example) diagnostics. An example entry with volume mount for local development is shown below.

diagnostics: image: bahmni/microfrontend-diagnostics:latest profiles: ["emr", "bahmni-lite", "bahmni-mart"] volumes: - "${DIAGNOSTICS_PATH:?}/dist/federation/:/usr/local/apache2/htdocs/diagnostics"

2.1.3: Add a proxy entry for the container in your proxy setup. Assuming you are using bahmni-proxy, add a reverse proxy rule to resources/bahmni-proxy.conf

# diagnostics ProxyPass /diagnostics http://diagnostics/diagnostics ProxyPassReverse /diagnostics http://diagnostics/diagnostics

This make the apache folder accessible at /diagnostics proxy. Confirm that https://<bahmni-host>/diagnostics/remoteEntry.js is accessible.


Step 3: Create MFE module in openmrs-module-bahmniapps

3.1: Create source files to hold new angular module

mkdir micro-frontends/src/diagnostics touch micro-frontends/src/diagnostics/index.js

3.2: Add WebPack entry point for new module

Completing the above steps adds a new WebPack module which will be built into ui/app/micro-frontends-dist/diagnostics.min.js and `ui/app/micro-frontends-dist/diagnostics.min.css.

3.3: Mock out your module for tests

Assuming that your MFE will have full test coverage in your own repository, you should mock out this module for unit tests in Bahmni to make things easier. This needs to be done in ui/test/__mocks__/micro-frontends.js


Step 4: Create a hosting React component

4.1: Add ModuleFederation config for your MFE

This sets up the remote URL accessible for dynamic imports

4.2: Create a hosting React component loading the remote component

Step 5: Register angular component

The angular module created in Step 3 would need a wrapper angular component added to, which will interface with the hosting component created in Step 4. There are two ways to achieve this

option A (recommended): Use React2AngularBridgeBuilder abstraction

We have created a good abstraction which simplifies the wrapper build process using conventions. Check it out at micro-frontends/src/utils/bridge-builder.js

option B: Manual

If you need more control over the wrapper build process, you can do it manually, following what the abstraction in micro-frontends/src/utils/bridge-builder.js does internally.


Step 6: Use your MFE in an angular module

To use your MFE in a given angular module, you just need to include the the module as a dependency and add the required built files along with react to the html entry point. Let’s take the example of the clinical module.

 

 

It is very important to correctly order and place the javascript and css files within the index.html. There are build marker positions in here which will make Grunt pick up minified files within and try to minify again. This will fail with the mfe build. Follow the instructions in the comments given

The last step after this is to include the module as a dependency in your init.js file.

 

Which then allows you to render your component in a template like this


Managing information flow

Currently, we have not set up any kind of event bus architecture for communication between host and MFE components. As a convention, we will use hostData and hostApi keys to do this communication.

Passing data from Host to MFE

The remote MFE can receive data from the host angular application though standard React props. As a matter of convention, it has been decided to wrap these inputs in a single prop object called hostData. An example usage would look like

Note: Remember, angularJs maps camelCase bindings to kebab-case so the hostData prop will be bound to host-data binding in the html.

Host reacting to events from MFE

For now, we have decided to rely on the host app passing callbacks to the MFE through a conventional prop named hostApi which needs to be an object. An example usage would look like

Important note regarding hostApi

For some reason, the react2angular adapter take an extra tick to resolve the functions within hostApi binding. Due to this, even if your propTypes mark the hostApi functions as required, React will throw reference errors for accessing functions in undefined and similar. I recommend using the optional chaining operator (?.) to get around this

 

The Bahmni documentation is licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)