Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save alexvijo/802f6beec5a9e02e3d80c131b46b7650 to your computer and use it in GitHub Desktop.
Save alexvijo/802f6beec5a9e02e3d80c131b46b7650 to your computer and use it in GitHub Desktop.

ARQUITECTURA DE MICROFRONTENDS CON ANGULAR Y MODULE FEDERATION

Introducción

La arquitectura de microfrontends permite dividir una aplicación web monolítica en aplicaciones más pequeñas y manejables, cada una de las cuales puede ser desarrollada y desplegada de manera independiente. Angular, combinado con Module Federation de Webpack 5, facilita la implementación de microfrontends, permitiendo el intercambio dinámico de módulos entre diferentes aplicaciones.

Estructura del Árbol de Ficheros

A continuación, se presenta un ejemplo de la estructura del árbol de ficheros para un proyecto de microfrontends con Angular y Module Federation. Este ejemplo incluye una aplicación host y dos microfrontends.

/microfrontends-project
|-- /host-app
| |-- /src
| | |-- /app
| | | |-- app.module.ts
| | | |-- app.component.ts
| | | -- app.component.html
| | -- bootstrap.ts
| |-- webpack.config.js
| |-- webpack.prod.config.js
| -- angular.json
|-- /microfrontend1
| |-- /src
| | |-- /app
| | | |-- mf1.module.ts
| | | |-- mf1.component.ts
| | | -- mf1.component.html
| | -- bootstrap.ts
| |-- webpack.config.js
| |-- webpack.prod.config.js
| -- angular.json
|-- /microfrontend2
| |-- /src
| | |-- /app
| | | |-- mf2.module.ts
| | | |-- mf2.component.ts
| | | -- mf2.component.html
| | -- bootstrap.ts
| |-- webpack.config.js
| |-- webpack.prod.config.js
|-- angular.json

Configuración de Module Federation

Configuración del Host

// webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  // ... otras configuraciones
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        mf1: "mf1@http://localhost:3001/remoteEntry.js",
        mf2: "mf2@http://localhost:3002/remoteEntry.js",
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"],
    }),
  ],
};

Configuración de un Microfrontend

Microfrontend 1

// webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  // ... otras configuraciones
  plugins: [
    new ModuleFederationPlugin({
      name: "mf1",
      filename: "remoteEntry.js",
      exposes: {
        './Module': './src/app/mf1.module.ts',
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"],
    }),
  ],
};

Microfrontend 2

// webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  // ... otras configuraciones
  plugins: [
    new ModuleFederationPlugin({
      name: "mf2",
      filename: "remoteEntry.js",
      exposes: {
        './Module': './src/app/mf2.module.ts',
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"],
    }),
  ],
};

Configuración Angular

Host

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {
        path: 'mf1',
        loadChildren: () =>
          import('mf1/Module').then((m) => m.Mf1Module),
      },
      {
        path: 'mf2',
        loadChildren: () =>
          import('mf2/Module').then((m) => m.Mf2Module),
      },
    ]),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Microfrontends

// mf1.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { Mf1Component } from './mf1.component';

@NgModule({
  declarations: [Mf1Component],
  imports: [
    CommonModule,
    RouterModule.forChild([
      {
        path: '',
        component: Mf1Component,
      },
    ]),
  ],
})
export class Mf1Module {}
// mf2.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { Mf2Component } from './mf2.component';

@NgModule({
  declarations: [Mf2Component],
  imports: [
    CommonModule,
    RouterModule.forChild([
      {
        path: '',
        component: Mf2Component,
      },
    ]),
  ],
})
export class Mf2Module {}

Tipo de Repositorios

En una arquitectura de microfrontends, se pueden utilizar diferentes tipos de repositorios:

1. Monorepo: Un único repositorio que contiene el código del host y todos los microfrontends. Ventajas: fácil coordinación entre equipos, configuración compartida.

2. Multirepo: Repositorios separados para el host y cada microfrontend. Ventajas: mayor independencia, facilita la gestión de permisos y la escalabilidad del equipo.

Despliegue y Ejecución

Desarrollo Local Para ejecutar la configuración localmente, primero necesitas arrancar cada aplicación de microfrontend y luego el host. Supongamos que utilizamos scripts de npm para arrancar cada aplicación.

Microfrontend 1 En el package.json de microfrontend1, añadimos un script para arrancar la aplicación:

{
  "scripts": {
    "start": "ng serve --port 3001"
  }
}

Luego, en la terminal, ejecutamos:

cd microfrontend1
npm install
npm run start

Microfrontend 2 En el package.json de microfrontend2, añadimos un script para arrancar la aplicación:

{
  "scripts": {
    "start": "ng serve --port 3002"
  }
}

Luego, en la terminal, ejecutamos:

cd microfrontend2
npm install
npm run start

Host Finalmente, en el package.json del host-app, añadimos un script para arrancar la aplicación:

{
  "scripts": {
    "start": "ng serve --port 3000"
  }
}

Luego, en la terminal, ejecutamos:

cd host-app
npm install
npm run start

Despliegue a Producción

Para desplegar las aplicaciones en producción, necesitas construir cada microfrontend y el host, y luego alojar los archivos construidos en un servidor web.

Construcción de Microfrontends Para construir cada microfrontend, puedes utilizar el siguiente comando:

ng build --prod

Esto generará los archivos construidos en el directorio dist. Asegúrate de configurar el outputPath en angular.json para especificar el directorio de salida.

Construcción del Host De manera similar, para el host:

ng build --prod

Servir los Archivos Construidos Puedes servir los archivos construidos utilizando cualquier servidor web estático, como Nginx, Apache, o incluso servicios de hosting como Firebase Hosting, AWS S3, o Netlify.

Ejemplo de Configuración de Nginx Aquí tienes un ejemplo de configuración de Nginx para servir las aplicaciones:

server {
    listen 80;

    server_name your-domain.com;

    location / {
        root /path/to/host-app/dist;
        try_files $uri $uri/ /index.html;
    }

    location /mf1/ {
        alias /path/to/microfrontend1/dist/;
        try_files $uri $uri/ /index.html;
    }

    location /mf2/ {
        alias /path/to/microfrontend2/dist/;
        try_files $uri $uri/ /index.html;
    }
}

Comunicación entre Microfrontends

Para la comunicación entre microfrontends, puedes utilizar diferentes enfoques:

Servicios Compartidos: Crear servicios compartidos que utilicen observables para emitir y escuchar eventos. Event Bus: Implementar un Event Bus que permita la comunicación basada en eventos entre los microfrontends. Local Storage o Session Storage: Utilizar el almacenamiento local del navegador para compartir datos entre microfrontends.

Ejemplo de Servicio Compartido Un ejemplo simple de un servicio compartido podría ser:

shared.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class SharedService {
  private messageSource = new Subject<string>();
  currentMessage = this.messageSource.asObservable();

  constructor() {}

  changeMessage(message: string) {
    this.messageSource.next(message);
  }
}

Uso en Microfrontend 1

import { Component } from '@angular/core';
import { SharedService } from './shared.service';

@Component({
  selector: 'app-mf1',
  template: `<button (click)="sendMessage()">Send Message</button>`,
})
export class Mf1Component {
  constructor(private sharedService: SharedService) {}

  sendMessage() {
    this.sharedService.changeMessage('Hello from Microfrontend 1');
  }
}

Uso en Microfrontend 2

import { Component, OnInit } from '@angular/core';
import { SharedService } from './shared.service';

@Component({
  selector: 'app-mf2',
  template: `<div>{{message}}</div>`,
})
export class Mf2Component implements OnInit {
  message: string;

  constructor(private sharedService: SharedService) {}

  ngOnInit() {
    this.sharedService.currentMessage.subscribe(
      message => this.message = message
    );
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment