Skip to main content

Microfrontend

🧩 ¿Qué es un Microfrontend?

Es una arquitectura donde una aplicación frontend grande se divide en múltiples aplicaciones más pequeñas e independientes (microfrontends), cada una manejada por equipos diferentes, pero que juntas forman una experiencia unificada.

⚙️ ¿Qué es Webpack Module Federation?

Es una funcionalidad de Webpack (desde la versión 5) que permite que diferentes aplicaciones compartan módulos entre sí en tiempo de ejecución, como si fueran parte de la misma aplicación.

🏠 Host y 🌐 Remotos

🔹 Host (o shell)

Es la aplicación principal que carga otras aplicaciones remotas y coordina su integración. No contiene toda la lógica, sino que importa módulos desde otros microfrontends.

🔹 Remotos (remotes)

Son otras aplicaciones independientes que exponen sus componentes, módulos o funciones para que el host (u otros remotos) los utilicen.

🔄 Comunicación entre ellos (simplificada)

  • El host se configura para consumir los módulos que exportan los remotos.
  • Los remotos se configuran para exponer sus módulos (por ejemplo, un componente en Angular, Vue o React).
  • Todo esto ocurre en tiempo de ejecución, no es necesario empacar todo junto.

🛠️ Como crear y configurar un host & remotos (Microfrontend)

Inicialización de proyectos

Angular ofrece un conjunto de comandos que facilitan el desarrollo, entre los cuales podemos encontrar uno para la generación automática de proyectos. Es importante mencionar que estaremos trabajando en una arquitectura microfrontend. Partiendo de este punto, se muestra a continuación, la creación de la app que será nuestro host:

ng new host-app --routing --style=scss

Este comando desplegara una serie de opciones entre las cuales nos preguntara, si queremos habilitar Server-side Rendering (SSR)a lo cual responderemos que NO, ya que para la arquitectura microfrontend no es necesaria. Una vez seleccionadas las opciones se desplegará un listado de proceso en ejecución que tardará unos minutos en concluir.

host

Posteriormente entraremos a la carpeta creada:

cd host-app

Luego, instalamos y configuramos Webpack Module Federation que se encargará de crear los archivos de configuración necesarios y de modificar los archivos main.ts y bootstrap.ts para habilitar la carga dinámica de módulos:

ng add @angular-architects/module-federation --project host-app --type host --port 4200

Una vez que concluye el proceso, se generará un directorio con el nombre de la aplicación, este contiene todos los componentes necesarios para desplegar una aplicación demo.

host-directorio

Luego crearemos la app "remote1”, que será nuestro "remote" dentro de nuestro proyecto de microfrontend. Para ello ejecutaremos el siguiente comando:

ng new remote1 --routing --style=scss

Después accedemos a la carpeta creada, con el siguiente comando:

cd remote1

Y posteriormente, instalamos y configuramos Webpack Module Federation que se encargará de crear los archivos de configuración necesarios y de modificar los archivos main.ts y bootstrap.ts para habilitar la carga dinámica de módulos:

ng add @angular-architects/module-federation@19.0.3 --project remote1 --type remote --port 4201

Una vez que concluye el proceso, se generará un directorio con el nombre de "remote1", este contiene todos los componentes necesarios para desplegar una aplicación demo.

remote-directorio

Posteriormente crearemos un module llamado "sales" el cual expondremos a través del webpack module federation, para que pueda ser usado desde nuestro "host".

Para ello ejecutaremos los siguientes comandos, dentro de nuestro proyecto "remote1":

ng g c modules/sales/components/sales --standalone --skip-tests

Posteriormente crearemos manualmente el archivo: "sales.routes.ts" dentro de la carpeta "sales", con el siguiente contenido:

sales-routes

Una vez creado el archivo "sales.routes.ts", tendremos la siguiente estructura:

sales-directorio

Es importante verificar que nuestro componente "sales.component.ts" tenga la inyección "standalone" y que tenga en los imports el CommonModule & RouterModule.

sales-component

Después, actualizaremos el archivo "src/app/app.routes" de nuestro proyecto "remote1", donde cargaremos en la ruta "/sales" donde cargaremos las rutas de nuestro archivo "sales.routes.ts"

app-routes

Y posteriormente, actualizaremos el archivo "webapack.config.js" de "remote1" para exponer nuestro módulo de sales, para que sea importado después por nuestro "host".

webpack-remote

tip

Es importante mencionar que se debe tener cuidado con el nombre con el que se exporta el modulo/componente ya que este debe tener el mismo nombre dentro de nuestro host, a la hora de importarlo. Y ademas el nombre debe ser escrito en PascalCase y debe estar en idioma ingles

Ejemplo:

  • SalesComponent
  • SalesListComponent

Y como pasos finales, abriremos el archivo "tsconfig.json" de "remote1" e incluiremos la siguiente configuración, en la llave "include".

tsconfig

Y por último, abriremos el archivo "app.routes" de nuestro proyecto de "host" y agregaremos el modulo "sales" que está dentro de nuestro proyecto "remote1":

host-app-routes

Y ahora sí, podremos ejecutar nuestro microfrontend, para lo cual abriremos 2 terminales, para ejecutar los siguientes comandos:

  • Dentro de la carpeta "host":

ng serve --port=4200

  • Dentro de la carpeta "remote1":

ng serve --port=4201

Donde dentro de la ruta localhost:4200 se desplegará nuestra app "host"

path-host

Y en la ruta localhost:4200/sales se desplegará nuestra app "host" y además el módulo de "sales" de la app "remote1"

path-host-remote

Y en la ruta de localhost:4201, se desplegará nuestra app "remote1"

path-remote

Y por último en la ruta localhost:4201/sales, se desplegará nuestro modulo "sales".

path-remote-sales

warning

Notas: en caso de tener problemas con el ruteo, bajar los servicios y volver a compilar.

Referencias:

TecnologíaReferencias
Angular 19https://angular.dev/overview
Module Federation with Angular's Standalone Componentshttps://www.angulararchitects.io/blog/module-federation-with-angulars-standalone-components/
Webpack Module Federationhttps://webpack.js.org/concepts/module-federation/

🏗️ ¿Como importar remotos en el shell proporcionado por la area de arquitectura?

Para poder importar microfrontends remotos en nuestro host, a continuacion se muestran las reglas y los pasos a seguir:

🔹Inicialización del host

Al inicializar nuestro host, se inicia un proceso para poder obtener la configuración de los remotos a cargar a traves del metodo initializeFederation.

/* Archivo shell\src\app\app.config.ts */

/**
* Inicializa la configuración de federación de módulos (Native y Module Federation)
* @returns {() => Promise<void>} Función de inicialización
* @description
* 1. Carga la configuración remota
* 2. Inicializa Native Federation para módulos .json
* 3. Inicializa Module Federation para módulos .js
* 4. Genera rutas dinámicas
* 5. Actualiza la configuración del router
*/
const initializeFederation = () => {
const remoteRoutesService = inject(RemoteRoutesService);
const router = inject(Router);

return () => {
// 1. Carga la configuración remota
return remoteRoutesService
.getRemoteConfig()
.pipe(
// 2. Validar y cargar Native Federation (.json)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
switchMap((apiRoutes: any) => {
const nativeConfig = remoteRoutesService.transformToFederationConfig(
apiRoutes.menuItems,
true
);

const hasNativeRemotes = Object.keys(nativeConfig.remotes).length > 0;
return hasNativeRemotes
? from(initNativeFederation(nativeConfig.remotes)).pipe(
map(() => apiRoutes),
catchError((err) => {
console.error("Error in initNativeFederation:", err);
return of(apiRoutes);
})
)
: of(apiRoutes);
}),

// 3. Validar y cargar Module Federation (.js)
switchMap((apiRoutes) => {
const moduleConfig = remoteRoutesService.transformToFederationConfig(
apiRoutes.menuItems,
false
);

const hasModuleRemotes = moduleConfig.remotes.length > 0;
if (hasModuleRemotes) {
/**
* Paso 3.1: Obtiene metadatos sobre las librerías compartidas via Native Federation
* @step Obtención de dependencias compartidas
* @type {SharedMappings}
*/
const shared = getShared();
initModuleFederation({
name: "shell",
remotes: moduleConfig.remotes,
shared,
});
}
return of(apiRoutes);
}),

// 4. Generar rutas dinámicas
switchMap((apiRoutes) => from(generateDynamicRoutes(apiRoutes))),

// 5. Actualizar rutas
tap((dynamicRoutes) => {
router.resetConfig([
...routes,
...dynamicRoutes,
{ path: "**", redirectTo: "/dashboard" },
]);
}),

// Manejo de errores
catchError((error) => {
console.error("Error inicializando federación:", error);
router.resetConfig(routes);
return of(null);
})
)
.toPromise();
};
};

/**
* Configuración principal de la aplicación Angular.
*
* @constant {ApplicationConfig} appConfig
* @property {Array<Provider>} providers - Proveedores de la aplicación
*
* @description
* Configuración incluye:
* - Optimización de detección de cambios con Zone.js
* - Proveedor HttpClient para peticiones HTTP
* - Router con rutas iniciales (sobrescritas después de inicialización)
* - Inicializador APP_INITIALIZER para carga asíncrona de rutas
*/
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideHttpClient(),
importProvidersFrom(HttpClientModule),
provideRouter(FALLBACK_ROUTES),
{
provide: APP_INITIALIZER,
useFactory: initializeAppFactory,
multi: true,
deps: [RemoteRoutesService, Router],
},
],
};

🔹Llamado a la API de Configuración de los Remotos

Posteriormente se invoca un servicio de una API para obtener las rutas remotas usando el servicio RemoteRoutesService especificamente en el metodo getRemoteConfig

/**
* Obtiene la configuración de menú desde el archivo JSON
* @returns {Observable<{menuItems: RemoteRoutesMenu[]}|null>} Observable con la configuración
* @description
* - Realiza la petición solo la primera vez y cachea los resultados
* - Usa shareReplay para multicasting
* - Actualiza la propiedad remoteRoutesMenu con los datos obtenidos
*/
getRemoteConfig(): Observable<{ menuItems: RemoteRoutesMenu[] } | null> {
if (!this.cachedRemotes$) {
this.cachedRemotes$ = this.http
.get<{
menuItems: RemoteRoutesMenu[];
}>('../../../assets/data/menu-config.json')
.pipe(
shareReplay(1),
tap((t) => (this.remoteRoutesMenu = t.menuItems))
);

// Configuración para Native Federation (.json)
this.nativeFederationConfig$ = this.cachedRemotes$.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map((config: any) =>
this.transformToFederationConfig(config.menuItems, true)
)
);

// Configuración para Module Federation (.js)
this.moduleFederationConfig$ = this.cachedRemotes$.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map((config: any) =>
this.transformToFederationConfig(config.menuItems, false)
)
);
}
return this.cachedRemotes$;
}

La estructura del archivo menu.config.json, debera ser igual a esta:

{
"menuItems": [
{
"title": "Entrega NF",
"icon": "airplay",
"type": "sub",
"active": true,
"remoteEntry": "http://localhost:4201/remoteEntry.json",
"remoteName": "entrega",
"children": [
{
"path": "entrega/producto",
"title": "Producto",
"type": "link",
"exposedModule": "./EntregaProducto"
}
]
},
{
"title": "Animals MF",
"icon": "list",
"type": "sub",
"active": false,
"remoteEntry": "http://localhost:4202/remoteEntry.js",
"remoteName": "remote2",
"children": [
{
"path": "animals/list",
"title": "Animals List",
"type": "link",
"exposedModule": "./AnimalsComponent"
}
]
}
]
}

A continuacion se muestra la interfaz para el archivo menu-config.json

/**
* Configuración para módulos remotos (Micro Frontends)
*/
export interface RemoteRoutesMenu {
path: string; // Nombre de la ruta
title: string; // Nombre del item
icon: string; // Nombre del icono a mostrar en el item del menu
type: string; // Tipo de item 'sub' | 'link' | 'extLink' | 'extTabLink'
badgeType?: string; // Opcional
badgeValue?: string; // Opcional
active: boolean; // Item del menu que estara desplegado por default
children?: RemoteRoutesMenu[]; // Opcional, Array de los hijos que tenga el item del menu
// Propiedades para Module Federation
remoteEntry?: string; // Opcional, Ruta del remoto
exposedModule: string; // Opcional, Ruta del módulo/componente expuesto
remoteName: string; // Nombre del remoto
}

🧩Ejemplo real para cargar un modulo remoto

A continuacion se explica como cargar su propio modulo remoto, en el shell implemetado por la area de arquitectura:

Supongamos que su modulo remoto tiene la siguiente informacion en el archivo webpack.config.js o en su defecto federation.config.js

/**
* Exporta la configuración de Webpack utilizando `withModuleFederationPlugin`.
*
* @property {string} name - Nombre del microfrontend remoto.
* @property {object} exposes - Módulos que este remoto expone a otros.
* @property {object} remotes - Definición del host y su URL de entrada remota (`remoteEntry.js`).
* @property {object} shared - Librerías compartidas entre el host y los remotos.
*/
module.exports = withModuleFederationPlugin({
name: 'productos'
filename: 'remoteEntry.js',
exposes: {
'./ProductListComponent':
'./src/app/modules/products/views/list/product-list.component.ts',
'./ProductCreateComponent':
'./src/app/modules/products/views/create/product-create.component.ts',
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
}),
},
});

Entonces, siguiendo la informacion antes mencionada la estructura de nuestro menu-config.json debera ser la siguiente:

{
"menuItems": [
{
"title": "Productos",
"icon": "airplay",
"type": "sub",
"active": true,
"remoteEntry": "http://localhost:4201/remoteEntry.json",
"remoteName": "productos",
"children": [
{
"path": "producto/lista",
"title": "Producto Lista",
"type": "link",
"exposedModule": "./ProductListComponent"
},
{
"path": "producto/create",
"title": "Producto Create",
"type": "link",
"exposedModule": "./ProductCreateComponent"
}
]
}
]
}
tip

Siguiendo las instrucciones anteriores, usted podra visualizar sus modulos remotos al iniciar sesion en la app shell.