La Revolución del Micro Frontend: Module Federation en Webpack 5
Tags: javascript, webpack, angular
Published: 2022-07-27 05:04:00

> Traducción en español del artículo original de Manfred Steyer "[The Microfrontend Revolution: Module Federation in Webpack 5](https://www.angulararchitects.io/en/aktuelles/the-microfrontend-revolution-module-federation-in-webpack-5/)" publicado el 22 diciembre 2020

Module Federación viene integrado con Webpack a partir de la versión 5 y permite la carga de partes de una aplicacion compiladas por separado. Por lo tanto, finalmente proporciona una solución oficial para la implementación de micro frontends.

Hasta ahora, al implementar micro frontends, había que rebuscar y hace mucha magia. Una de las razones es seguramente que las herramientas de compilación y los frameworks no conocen este concepto. Module Federation inicia un cambio de rumbo en este sentido.

Module Federation presenta un enfoque para referenciar partes de una aplicacion que aún no se conocen en tiempo de compilación. Éstas pueden ser micro frontend autocompilados. Además, las partes individuales de la aplicacion pueden compartir bibliotecas entre sí, de modo que los paquetes individuales no contengan duplicados.

En este artículo, voy a mostrar cómo utilizar la Module Federación un ejemplo sencillo. El código fuente se puede encontrar [aquí](https://github.com/manfredsteyer/federation-demo).

## Ejemplo
El ejemplo utilizado aquí consiste en un shell, que es capaz de cargar micro frontend individuales, proporcionados por separado si es necesario:

![enter image description here](https://www.angulararchitects.io/wp-content/uploads/2020/04/example.png)

La shell está representada aquí por la barra de navegación negra, el otro micro frontend en enmarcada, esta se puede  iniciar sin un shell

![Aplicacion sin la shell](https://www.angulararchitects.io/wp-content/uploads/2020/04/standalone.png)

> Esto es necesario para permitir el desarrollo y las pruebas por separado. También para los clientes como los dispositivos móviles, que sólo tienen que cargar la aplicacion necesaria.

## Conceptos de Module Federation
En el pasado, la implementación de escenarios como el mostrado aquí era difícil, especialmente porque herramientas como Webpack asumen que todo el código de la aplicacion está disponible cuando se compila. El Lazy loading es posible, pero sólo de las áreas que se separaron durante la compilación.

Con las arquitecturas de micro frontend queremos poder compilar y proporcionar las partes individuales de la aplicacion por separado. Además, hacer referencias mutuas a través de la respectiva URL. Por lo tanto, sería deseable contar con codigo como este:

```typescript
import('http://other-microfrontend');
```
Como esto no es posible, había que recurrir a enfoques  la carga manual de scripts. Afortunadamente, esto cambiará con el Module Federation en Webpack 5.

La idea detrás de esto es simple: Un llamado host hace referencia a un remoto usando un nombre configurado. A qué se refiere este nombre no se conoce en tiempo de compilación:
![enter image description here](https://www.angulararchitects.io/wp-content/uploads/2020/04/hub-and-spoke.png)

Esta referencia sólo se resuelve en tiempo de ejecución mediante la carga de un llamado punto de entrada remoto. Se trata de un script mínimo que proporciona la url externa real para dicho nombre configurado.

## Implementación del Host
El host es una aplicación JavaScript que carga un remoto cuando se necesita. Para ello se utiliza una importación dinámica.

El host carga el componente `mfe1/componente` de esta manera `mfe1` es el nombre de un remoto configurado y `componente` el nombre de un archivo que se proporciona.


```typescript
const rxjs = await import('rxjs');

const container = document.getElementById('container');
const flightsLink = document.getElementById('flights');

rxjs.fromEvent(flightsLink, 'click').subscribe(async _ => {
    const module = await import('mfe1/component');
    const elm = document.createElement(module.elementName);
    […]    
    container.appendChild(elm);
});
```
Normalmente, Webpack tendría en cuenta esta referencia al compilar y dividiría un paquete separado para ella. Para evitarlo, se utiliza el ModuleFederationPlugin:

```typescript
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

[…]
 output: {
      publicPath: "http://localhost:5000/",
      uniqueName: 'shell',
      […]
 },
plugins: [
  new ModuleFederationPlugin({
    name: "shell",
    library: { type: "var", name: "shell" },
    remoteType: "var",
    remotes: {
      mfe1: "mfe1"
    },
    shared: ["rxjs"]
  })
]
```
Con su ayuda se define el remoto `mfe1` (Micro Frontend 1). La configuración mostrada aquí mapea el nombre interno de la aplicación `mfe1` al mismo nombre oficial. Webpack no incluye ninguna importación que ahora se relacione con `mfe1` en los paquetes generados en tiempo de compilación.

Las librerias que el host debe compartir con los `remotes` se mencionan en el array `shared`. En el caso mostrado, se trata de Rxjs. Esto significa que toda la aplicación sólo necesita cargar esta biblioteca una vez. Sin esta especificación, rxjs terminaría en los paquetes del host así como en los de todos los remotos.

Para que esto funcione sin problemas, el host y el remoto deben acordar una versión común.

Además de la configuración del `ModuleFederationPlugin`, también necesitamos colocar algunas opciones en la sección `output`. El `publicPath` define la URL bajo la cual la aplicación puede ser encontrada posteriormente. Esto revela dónde se pueden encontrar los paquetes individuales de la aplicación, pero también sus assets, por ejemplo, imágenes o estilos.

El `uniqueName` se utiliza para representar el host o remoto en los bundles generados. Por defecto, webpack utiliza el nombre de package.json para esto. Para evitar conflictos de nombres cuando se utiliza Monorepos con varias aplicaciones, se recomienda establecer el uniqueName manualmente.

## Cargando Librerias compartidas
Para cargar librerias compartidas, debemos utilizar importaciones dinámicas:

```typescript
const rxjs = await import('rxjs');
```
Al ser llamadas asincronas, esto le da a webpack el tiempo necesario para decidir qué versión usar y cargarla. Esto es especialmente importante en los casos en los que diferentes `remotes` y `hosts` utilizan diferentes versiones de la misma libreria. En general, webpack intenta cargar la versión más compatible. 

> Mas adelante hablaremos sobre  la gestion de versiones entre micro frontends.

Para evitar este problema, es una buena idea cargar toda la aplicación con importaciones dinámicas en el punto de entrada utilizado. Por ejemplo, el micro frontend podría usar un main.ts que se vea así:

```typescript
import('./component');
```
Esto le da a webpack el tiempo necesario para la negociación y la carga de las bibliotecas compartidas cuando la aplicación se inicia. Por lo tanto, en el resto de la aplicación siempre se pueden utilizar importaciones estáticas  como:

```typescript
import * as rxjs from 'rxjs';
```
## Implementando el Remote
El remote también es una aplicación independiente y en este caso se basa en Web Components:

```typescript
class Microfrontend1 extends HTMLElement {

    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
    }

    async connectedCallback() {
        this.shadowRoot.innerHTML = `[…]`;
    }
}

const elementName = 'microfrontend-one';
customElements.define(elementName, Microfrontend1);

export { elementName };
```
En lugar de componentes web, también se puede utilizar cualquier component creados por un framework. En este caso, los frameworks deben ser compartidos entre los remotos y el host.

La configuración webpack del remote, que también utiliza el ` ModuleFederationPlugin ", exporta este componente con la propiedad exposes bajo el nombre component:

```javascript
output: {
      publicPath: "http://localhost:3000/",
      uniqueName: 'mfe1',
      […]
 },
 […]
 plugins: [
    new ModuleFederationPlugin({
      name: "mfe1",
      library: { type: "var", name: "mfe1" },
      filename: "remoteEntry.js",
      exposes: {
        './component': "./mfe1/component"
      },
      shared: ["rxjs"]
    })
]    
```
El nombre del componente hace referencia al archivo. Además, esta configuración define el nombre `mfe1` para el remoto. Para acceder a la remote, el host utiliza una ruta formada por los dos nombres configurados, `mfe1` y `component`. El resultado es la instrucción que se muestra arriba:

```typescript
import('mfe1/component')
```
Sin embargo, el host debe conocer la URL en la que encuentra `mfe1`. La siguiente sección muestra cómo se puede lograr esto.

## Conectar el host con el remoto
Para dar al host la opción de resolver el nombre `mfe1`, el host debe cargar un entryPoint del remoto. Este es un script que el `ModuleFederationPlugin` genera cuando se compila el remoto.

El nombre de este script se puede encontrar en la propiedad `filename` mostrada en la sección anterior. La url del micro frontend se toma de la propiedad `publicPath`. Esto significa que la url del remoto ya debe ser conocida cuando se compila. 

> Afortunadamente, ya existe un PR que elimina esta necesidad.

Ahora este script sólo debe ser integrado en el host:

```html
<script src="http://localhost:3000/remoteEntry.js"></script>
```
En tiempo de ejecución se puede observar que la instrucción
```typescript
import('mfe1/component');
```

Esto hace que el host cargue el remoto desde su propia url (que es localhost:3000 en nuestro caso):

![enter image description here](https://www.angulararchitects.io/wp-content/uploads/2020/04/runtime.png)

## Conclusión
Module Federation integrado en Webpack a partir de la versión 5 llena un gran vacío para los micro fronted. Por fin se pueden recargar las partes una aplicacion compiladas y suministradas por separado y reutilizar las librerias ya cargadas.

Sin embargo, los equipos que participan en el desarrollo de este tipo de aplicaciones deben asegurarse manualmente de que las partes individuales interactúan. Esto significa también que hay que definir y cumplir contratos entre los micro frontend individuales, pero también que hay que acordar una versión para cada una de las bibliotecas compartidas.

Hasta ahora, hemos visto que la Module Federation es una solución sencilla para crear micro frontends.

Photo by Clem Onojeghuo on Unsplash