Tailwind CSS + Module Federation

How to use Tailwind CSS in an Angular Microfrontend

tailwind-module-federation-cover
portrait-image

Tailwind CSS is a modern CSS utility library used by thousands of people. It allows to use predefined css classes directly inside the html such that creating custom css classes is rarely needed. But setting it up for a microfrontend is a bit tricky. Anyway, don't worry, in this article you will learn how to setup an Nx workspace with module federation and use Tailwind CSS in the microfrontend.

Tailwind in a Nutshell

Tailwind CSS is all about making styling web applications easier. Instead of having to come up with CSS class names, which actually is a really difficult task, you can use Tailwind CSS and add lots of predefined utility classes to the class attribute inside your html templates. Although, it is often debated if Tailwind is counter-productive because it clutters the html template, I made the personal experience that I was a lot faster using it. The biggest advantage is that you do not have to think about good telling css class names and that is a huge win for Tailwind.

typical tailwind classes
Typical Tailwind classes

Setting up Module Federation

In case you are not so familiarwith microfrontends and module federation yet, I would recommend reading this article I wrote a while a go which is tailored for module federation beginners.
Getting started with Module Federation
In this article I have created an Nx workspace containing two applications (shell, remote) with the standalone API. Hence, a routing configuration is exposed by the microfrontend instead of the usual ngModule. But that is just because routes can be officialy lazy loaded since Angular 15.

Adding Tailwind

First of all, we need to install Tailwind.
Running the following command will install the necessary packages ( tailwindcss, postcss, autoprefixer).

npm install tailwindcss
Afterwards we can setup Tailwind for each application by running the following command:
npx nx generate @nrwl/angular:setup-tailwind [remoteName]
Replace [remoteName] with the name of your remote app
This generator adds a the tailwind.config.js file to your project. You can leave it as is if you do not have any custom tailwind configurations. It also generated the tailwind imports in your global stylesheet file. Usually that is okay, when you are running a monolith, but since the global stylesheet of a microfrontend is not exposed, we have to find a solution to include the tailwind styles in the exposed chunk of the microfrontend.
@tailwind base;
@tailwind components;
@tailwind utilities;

/* You can add global styles to this file, and also import other style files */

styles.scss
Therefore, you can remove these imports from the global stylesheet for now.

Proxy Pattern

In order to also share the tailwind styles during module federation, we have to include tailwind imports in the styles of each component that the microfrontend uses. Well yes, but there is a better way where we can just import tailwind once. We can use a ProxyComponent that is simply wrapping the entire microfrontend that usually would be exposed in the webpack config and expose this proxy instead. Now we can put the tailwind imports into the styles of this proxy component. The last thing that has to be done is to set the ViewEncapsulation of the proxy component to None. This is because usually Angular would encapsulate all styles by default by obfuscating css classes. This is prevented and therefore the styles of this component act like a global stylesheet for all the child components and other components without view encapsulation. So be aware, that you should only put the tailwind imports in there to be on the safe side.

proxy component
Proxy Component
@tailwind base;
@tailwind components;
@tailwind utilities;
proxy.component.scss
<router-outlet></router-outlet>
proxy.component.html
import { Component, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'tailwind-microfrontends-proxy',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './proxy.component.html',
  styleUrls: ['./proxy.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class ProxyComponent {}
proxy.component.ts
import { Route } from '@angular/router';

export const routes: Route[] = [
  {
    path: '',
    loadComponent: async () =>
      (await import('./proxy.component')).ProxyComponent,
    loadChildren: async () =>
      (await import('@tailwind-microfrontends/remote-lib')).routes,
  },
];
proxy.routes.ts

Result

result

GitHub Repository

Comments