DEV Community 👩‍💻👨‍💻

Cover image for La Revolución del Micro Frontend: Module Federation en Webpack 5
ng-content
ng-content

Posted on • Originally published at ng-content.com

La Revolución del Micro Frontend: Module Federation en Webpack 5

Traducción en español del artículo original de Manfred Steyer "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í.

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

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

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:

import('http://other-microfrontend');
Enter fullscreen mode Exit fullscreen mode

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

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.

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);
});
Enter fullscreen mode Exit fullscreen mode

Normalmente, Webpack tendría en cuenta esta referencia al compilar y dividiría un paquete separado para ella. Para evitarlo, se utiliza el ModuleFederationPlugin:

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"]
  })
]
Enter fullscreen mode Exit fullscreen mode

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:

const rxjs = await import('rxjs');
Enter fullscreen mode Exit fullscreen mode

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í:

import('./component');
Enter fullscreen mode Exit fullscreen mode

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:

import * as rxjs from 'rxjs';
Enter fullscreen mode Exit fullscreen mode

Implementando el Remote

El remote también es una aplicación independiente y en este caso se basa en Web Components:

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 };
Enter fullscreen mode Exit fullscreen mode

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

`
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

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

Top comments (0)

Visualizing Promises and Async/Await 🤯

async await

☝️ Check out this all-time classic DEV post