⏰ It’s time to talk about Import Map, Micro Frontend, and Nx Monorepo
Tags: microfrontends, angular, nx, react
Published: 2024-03-11 08:03:40

In this article, I am sharing about an architecture that has profoundly changed my perception of software development for **large enterprises** with **complex infrastructure** and **team organization**.

I’ve been eager to write this article for a long time, and **now’s the time!** I’ve always valued **sticking to standards**, and I believe the ecosystem is now ready to properly handle this architecture with the latest tools.

The advent of [**esbuild**](https://esbuild.github.io/), the native support for [**ES Modules**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) in browsers, the widespread adoption of [**import map**](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap), the emergence of tools like [**Native Federation**](https://www.angulararchitects.io/blog/announcing-native-federation-1-0/), and the [**Nx**](https://nx.dev/) ecosystem all combine to forge a flexible and well-maintained **Micro Frontend Architecture**.

I’ll cover:  
* [Real Story!](https://medium.com/p/0b8e2c07568a#e6e9)  
* [A short reminder about browsers](https://medium.com/p/0b8e2c07568a#017b)  
* [Micro frontend architecture in a nutshell](https://medium.com/p/0b8e2c07568a#9c3f)  
* [What is an Import Map?](https://medium.com/p/0b8e2c07568a#57ef)  
* [Exploring the Full Potential of Import Maps and Overrides](https://medium.com/p/0b8e2c07568a#2f22)  
* [Nx Enables Scalable Micro Frontend Architecture](https://medium.com/p/0b8e2c07568a#e1ed)  
* [What about Native Federation?](https://medium.com/p/0b8e2c07568a#a8b4)  
* [Final Thoughts](https://medium.com/p/0b8e2c07568a#656e)

## **Real Story!**

Just to give you more context, I led the migration of several [AngularJS](https://angularjs.org/) applications to the newer [Angular](https://angular.dev/) Framework. My client finally decided to make that move following the AngularJS deprecation announcement (stay up to date please 🙏)️.

Using the usual migration process was not possible. After investigating multiple scenarios, the micro frontend architecture was chosen. As we see, it facilitates **incremental migration**, provides **isolation**, and allows the integration of apps from **multiple teams** into one **unified platform**.

At that time, the micro frontend architecture was not yet popular and only the [**single-spa**](https://single-spa.js.org/docs/getting-started-overview/) library was **mature** enough. It supports many frameworks, including AngularJS and Angular, making it a perfect choice for us!

Single-spa orchestrates the micro frontend by toggling between AngularJS or Angular implementations based on a feature flag:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hs6v9gq3hsi4f82i9ma.png)

Using [single-spa](https://single-spa.js.org/docs/getting-started-overview/) has significantly enhanced my understanding of implementing micro frontend architecture, particularly highlighting the substantial benefits of utilizing **import maps** and **micro frontend overrides**. These tools have greatly improved my experience in **local development**, **testing**, and **deployment**.

> I highly recommend having a look at the [**single-spa**](https://single-spa.js.org/docs/getting-started-overview/) documentation to understand the concepts of **micro frontend** and **import map**.

## **A short reminder about browsers**

To grasp the following subjects, I believe it’s crucial first to recall the **basics** of the web, focusing on the primary flow of a **browser running a web application**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tj75rgbiu5xyy57v2bjf.png)

1. The first action is always to get an `index.html` file, which has everything needed to start the application.

2. Then, the browser loads all the files that the `index.html` says it should. This often includes the **main files** for the application, like JavaScript and stylesheets.

3. After that, the application or the user interaction leads to more requests being made, for example, **calling APIs** or **loading parts** of the site as needed.


> **The browser’s job is simply to load these files or assets and put them together into the web application.**

## **Micro frontend architecture in a nutshell**

Let’s start with a short definition: the micro frontend architecture involves **breaking down** a frontend application into **smaller**, more **manageable** pieces — each responsible for a **distinct feature** or **domain** of your application. It’s often compared to the [microservices concept](https://avinetworks.com/glossary/microservice/) but at the frontend layer.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amm0e2omid0d50qbc68y.png)

Determining the exact point at which an application adheres to micro frontend architecture can be challenging, like defining the ideal size for a microservice.

The key aspect is having a platform capable of **plugging** in and **combining** multiple pieces of functionality to produce a **unified application**. Whether these pieces are **lazy-loaded** components or **micro frontend**, the principle remains essentially the **same**.

### **In which situation it suits you well?**

There are many use cases where the micro frontend architecture can be useful:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bz7kjs5ck3btrklipg7.png)


* **Multiple Frameworks**: The most common use case involves integrating various technologies into a single product, particularly useful for unifying disparate systems.

* **Team Decentralization**: When teams operate independently, within a monorepo or different repositories, micro frontends make it easier to merge their work into one cohesive product.

* **Separation of Concerns**: Ideal for structuring your application into isolated domains and features for better organization.

* **Complex Infrastructure**: The ability to plug a micro frontend into an existing environment can significantly enhance the development experience! We’ll delve into this reason further later on.


> Don’t use micro frontend architecture if you don’t need it

### **Major Concepts**

In a micro-frontend architecture, we distinguish various types of entities, each adhering to a distinct concept:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tp62ihe27fyopdqs8ry.png)


* **The Micro Frontend** (or micro app) is loaded by the **Host** upon navigation or routing. Each micro frontend is responsible for a distinct feature or domain within the application. Like any app, it can contain child routes and multiple components.

* **The Parcel** (also referred to as a component or expose) is loaded independently on-demand. It can be a shared component or a shared service and can be plugged in anywhere.


### **Tools/Frameworks**

There are several implementations of the micro frontend architecture, and I’ll delve into three notable ones here:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gezqukaj1tcixnluryq7.png)


* **Single-spa**: This framework keeps things **simple** and works with many technologies. However, its **simplicity** might mean you have to do more work if you’re using just one technology.

* **Webpack Module Federation**: Almost everyone uses **Webpack**, and its module federation feature makes micro frontends easy for these users. But, if you’re using a different tool, you might need to find another solution.

* **Native Federation**: This method combines the ease of Webpack’s approach with **newer** tools like **esbuild** or **Vite**, fitting well with modern development practices while supporting micro frontend architecture.


## **What is an Import Map?**

Let’s begin with the most intriguing aspect. In my opinion, the import map is an **underappreciated browser technology**. It is compatible with all browsers and plays a role in directly supporting JavaScript modules in the browser.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mm7loxr09rkxhv4qrbh.png)


> For full compatibility and extra features, we usually use the library [`es-module-shims`](https://github.com/guybedford/es-module-shims).

### **How does it work?**

The principle is quite straightforward. Since the introduction of the ES module into our JavaScript ecosystem, we’ve all started using syntax like:

```javascript
import moment from "moment";
import { partition } from "lodash"
```

However, when using ES modules natively in a browser, you need to specify the full path to the JS file, something like:

```javascript
import moment from "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js";
import { partition } from "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js";
```

This approach isn’t very readable or maintainable, is it? Therefore, the import map was created to map a library name to a URL:

```javascript
<script type="importmap">
{
  "imports": {
    "moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js",
    "lodash": "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"
  }
}
</script>
```

It functions similarly to TypeScript’s path mapping but directly in your browser. Now, you can use the same syntax whether loading modules locally or in the browser.

This import map can be specified **inline** or as an **external file**, like:

```json
<script type="importmap" src="assets/shared.importmap.json"></script>
<script type="importmap" src="assets/remotes.importmap.json"></script>
```

> For more information, I recommend checking out the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) and the [proposal’s GitHub repository](https://github.com/WICG/import-maps?tab=readme-ov-file).

### **How is it related to the micro frontend architecture?**

As I mentioned, the micro frontend architecture is a just a way for dynamically loading bundles from the browser and integrating them into the actual apps.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzksgti0ghweo1nc4pa3.png)

This orchestration is the role of the **Host**. However, when the host needs to load an **ES module**, it can simply utilize the **JS import system** and, with the aid of the **import map**, map the module to its location

Similarly, for **Parcels**, when you need to load a component on demand, the import map will map your **JS import** to the current location.

### **Import Maps are overridable!**

You can declare multiple import maps in the same HTML. This means that if two import maps declare the same key, the last one will override the previous one.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddnzmx3puebl7r6lhexk.png)

By injecting a new import map into your HTML, you can hook/remap any bundle. Thus, you can replace a micro frontend, a component, or even a shared library!

> I recommend the library [`import-map-override`](https://github.com/single-spa/import-map-overrides) which allows you to manage the import map directly in your browser.

### **Security**

Overriding an import map in a web application does not inherently reduce its security, as all frontend assets are public and can be modified client-side. However, for applications that load assets from multiple servers, configuring a [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) is crucial.

CSP helps whitelist trusted domains, significantly reducing the risk of [Cross-Site Scripting](https://en.wikipedia.org/wiki/Cross-site_scripting) (XSS) and other security threats. This security measure ensures that even if client-side modifications are possible, the application’s integrity and user safety are maintained.

## **Exploring the Full Potential of Import Maps and Overrides**

Now that we understand the principles of the import map and the fact that we can override the bundles loading directly in the browser, let’s see how we can get the advantage of that concept within our development process:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tiqv6ztz572fy8g6w4p5.png)

### **Local Development**

Setting up a complex local environment in a large organization often involves:

* Spending **more than a day to set up** your local machine.

* Installing a **wide range of software**, like backend systems, local databases or connections to external environments, local queuing systems, etc.

* Adjusting settings for **multi-tenants**.

* Take coffee breaks while you **wait** for your local environment to bootstrap in the morning, hoping it stays stable throughout the day.


This complexity can be quite frustrating, especially when you only need to make a minor UI adjustment. **This is the exact challenge I aimed to tackle through the adoption of micro frontend architecture in combination with the import map overrides.**

Instead of running an entire complex ecosystem, you can just plug your local environment into an external environment where all of the complexity is already in place.

To do so, you just need to serve your micro frontend locally and use the import map override principle on the distant environment:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yox4rz9bfk2mpwg1l5s3.png)


After the reload, the micro frontend loaded by the browser will be not the one on the distant server but the one on your local machine.

One crucial aspect is that you are directly integrating your code into a real environment that contains the latest main branch. This means we can move past the infamous “**It Works On My Machine!**” scenario.

> This approach showcases true Continuous Integration

### **Pull Request**

When you’ve completed your implementation (and tested it 😋), you typically create a pull request to merge your code into the shared codebase.

**Facilitate Reviews**  
You can once again leverage the advantage of import map overriding to make the review process easier, allowing reviewers to validate your changes without needing to deploy or clone the code locally:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aj7enhtonb6rt8j7qiuw.png)


At this stage, the CI will build your app and generate new bundles for the modified micro frontend. Additionally, you can generate an [affected `importmap.json`](https://medium.com/p/0b8e2c07568a#c8c8) with the updated bundles.

**Simplify UI e2e tests**  
You can also use the affected import map for your **UI tests** (mocks). In this scenario, the affected `importmap.json` generated can be injected into tools like **Playwright** or **Cypress** to directly test the affected micro frontends.

### **Acceptance**

This step signifies the moment when you need to confirm that your code is ready for production deployment. It can be automated on CI or manually (please automate 🙏).

Typically, this is run several times per day with the most recent codebase in an environment that mirrors production. In this scenario, you’ll generate an `importmap.json` that includes the latest versions of all bundles:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8x5vyu7a7qrfe7lti2s.png)


If the latest `importmap.json` generated proves successful, it can then become a release candidate for production.

### **Production**

When your release is validated and ready, you can consider deploying it to production. Here, too, having an `importmap.json` offers significant advantages.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/972594nq8u3d1oxuz8xo.png)


**Deploy in a Sec**
You can deploy/upload your bundles to production at any time. Until the `importmap.json` references them, they will not be loaded. Thus, deployment involves merely modifying and uploading the latest import map. This deployment process takes a mere second, requires no freeze, and is completely transparent to the user.

> You should have a look to them [import-map-deployer](https://github.com/single-spa/import-map-deployer)*library which enable to update an*`importmap.json` directly on the server

**Keep Previous Bundles in Cache**  
It’s also important to note that the `importmap.json` can still reference bundles with previous versions. In fact, if some micro frontends have not been modified, there’s no need to generate a new version for them.

This means that users won’t have to reload these existing versions because they are probably already cached in their browser. On the other hand, the `importmap.json` should never be cached!

**Canary Deployment & A/B Testing**  
One last, and not negligible, benefit of the `importmap.json` is that it can be generated dynamically. This means you can decide whether a micro frontend should load an old version or a new one.

As a result, you can easily conduct [A/B testing](https://medium.com/@chandu.bathula16/machine-learning-concept-70-a-b-a-a-b-testing-1c082cd91795) or [canary deployments](https://semaphoreci.com/blog/what-is-canary-deployment) based on feature flags or authenticated user criteria!

## **Nx Enables Scalable Micro Frontend Architecture**

I won’t delve into all the benefits of Nx, a topic I’ve extensively covered in previous writings. I’ll encourage you to have a look at the [Nx website](https://nx.dev/) for more detailed information.

My conviction in the value Nx brings to not just JavaScript/TypeScript repositories but to any codebase is unwavering. Its strengths in enhancing sharing, visibility, performance, and adherence to conventions are universally applicable.

### **Monorepo and Micro Frontend aren’t the opposite?**

Not at all! A monorepo adds value through enhanced code maintenance, build, and integration processes. Conversely, micro frontend architecture delivers benefits at runtime.

Both strategies advocate for separation of concerns and reusability, showcasing significant advantages in incorporating micro frontends within a monorepo.

{% embed https://nx.dev/concepts/more-concepts/why-monorepos %}

> Nx still delivers value even if you don’t use a monorepo.

### **Affected micro frontends**

A pivotal concept in Nx is the ability to execute tasks solely on the affected code. This feature significantly simplifies working on a single micro frontend at a time in a remote environment, streamlining local development.

By limiting actions like build, lint, and testing to impacted micro frontends, the efficiency of your CI/CD processes can be markedly improved. Utilizing an affected `importmap.json` that lists the affected micro frontends can enhance various processes, including testing PRs on existing environments, running e2e tests, and facilitating incremental deployments.

{% embed https://nx.dev/ci/features/affected %}

### **Single Version Policy**

While independence and isolation are cornerstone principles of micro frontend architecture, sharing some services and components across all instances is inevitable.

The monorepo approach, coupled with a single version policy, ensures by design that micro frontends remain compatible with one another, fostering a cohesive ecosystem.

{% embed https://nx.dev/concepts/more-concepts/dependency-management %}

## **What about Native Federation?**

Like I said at the beginning, I think now the ecosystem is mature enough to apply the same principles by using **Angular**, or other frameworks using **esbuild**, and **Native Federation** within an **Nx** monorepo.

> I encourage you to have a look at the [blog post announcing Native Federation](https://www.angulararchitects.io/en/blog/announcing-native-federation-1-0/)

Unfortunately, I was unable to implement the import map overrides in conjunction with Native Federation. However, this issue is currently under discussion on GitHub:

{% embed https://github.com/angular-architects/module-federation-plugin/issues/489 %}

However, the underlying principles remain unchanged. Rather than directly utilizing the `importmap.json`, I have the option to override the `federation.manifest.json`. This requires the creation of custom code within the application to enable the overrides of the bundles.

### **Do you want to try it?**

1. First, clone my GitHub repository:


```bash
git clone git@github.com:jogelin/nx-nf.git && cd nx-nf
```

2\. Begin by installing the packages:

```bash
pnpm install
```

3\. Next, you can start one micro frontend, for example, `mf-admin`:

```bash
npx nx run mf-admin:serve
```

4\. Then, access the URL [https://nx-nf-a2d7c.web.app/admin](https://nx-nf-a2d7c.web.app/admin) where I have already deployed the application. You should see the application:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4wp9btmpbv8mvnqu6z9.png)


5\. Now, open your favorite browser debugging tool and connect your local server to the remote application by adding this entry in the local storage:

```javascript
localStorage.setItem('native-federation-override:mfAdmin', 'http://localhost:4203/remoteEntry.json') // override mfAdmin with you local server
```

6\. Then, make modifications to the `mf-admin` micro frontend. For example, change the message from "Welcome to the Admin Page" to “Welcome to the LOCAL Admin Page”

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f9rt2ovmy8bbh9gpt1r.png)


7\. After you make changes, reload the page, and you should see your modifications reflected on the remote server immediately!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83vl3mpgm4rx1o5hty0e.png)


8\. To revert the changes, simply remove the entry from the local storage and refresh the page to see the original state again.

```javascript
localStorage.removeItem('native-federation-override:mfAdmin');
```

You can override any micro frontend using this approach. However, as I mentioned, the method involving native federation is not entirely native yet because it doesn’t utilize the default behavior of import maps.

You can find all the code utilizing Native Federation, Angular, and Nx in my GitHub repository.

{% embed https://github.com/jogelin/nx-nf %}

## **Final Thoughts**

This exploration reveals the power of the native JavaScript ecosystem in browsers, highlighting how native support for ES modules enhances our development experience beyond faster build times.

The simplicity and effectiveness of the import map principle show us a way to solve complex issues with elegant solutions. It hints at a future where reliance on custom framework implementations diminishes in favor of native browser features, making development smoother and more intuitive.

Moreover, the use of Nx as part of this ecosystem offers a powerful toolkit that enables developers to approach complex projects with enhanced agility and precision.

The hope for more native features like these grows, promising a simpler, yet more powerful development landscape. With Nx and advancements in browser capabilities, we’re moving towards a future where building sophisticated web applications becomes more accessible and efficient.

🚀 Stay Tuned!

## **Credits**

### [**Joel Denning**](https://medium.com/u/a142a504a37c?source=post_page-----0b8e2c07568a--------------------------------)

Joel Denning is the visionary behind [single-spa](https://single-spa.js.org/), boasting profound insights into the true mechanics of the web and is, in my view, a pioneer in micro frontend architecture. I highly recommend checking the [single-spa](https://single-spa.js.org/) website and his [YouTube channel](https://www.youtube.com/@jbdenning/videos). While the videos might appear dated at first glance, rest assured, Joel is ahead of his time, and the content remains incredibly relevant today.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcho817cmtebvwwjmagc.png)

Engineer, Architect, Speaker, Trainer, Consultant, and Author, Manfred knows what he is talking about. For everyone, not just Angular, interested in micro frontends and web architecture in general, I encourage you to have a look at his book [Enterprise Angular: Micro Frontends and Moduliths with Angular](https://www.angulararchitects.io/en/ebooks/micro-frontends-and-moduliths-with-angular/) and the blog of the [Angular Architect](https://www.angulararchitects.io/en/blog/) team.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31il46v1v1jdxeqyp8vy.png)


## **Start a Collaboration?**

Connect with me on [**Twitter**](https://twitter.com/jonathan_gelin) • [**LinkedIn**](https://www.linkedin.com/in/jonathan-gelin/) • [**Github**](https://github.com/jogelin).