Infoxicator.com

Composición de Módulos Holocron

Published

English version here


En mi artículo anterior, les presenté Holocron, una nueva forma de crear Micro Frontends en React. Ahora, esta es la pregunta principal …

“¿Cómo se ensamblan todas las piezas del rompecabezas en la arquitectura Micro Frontend? la respuesta es … ¡NO usar Iframes!”

La mayoría de los ejemplos que encuentra en Internet sobre cómo crear una aplicación basada en Micro frontend mencionan el uso de iframes, pero seamos honestos, los iframes son malos 🙁 … Y de ninguna manera son la solución elegante que utiliza la web moderna en estos días … así que mi pregunta nuevamente es:

¿Existe una forma segura de ensamblar docenas de módulos React en una sola página, preservando una pequeño tamaño, “Server Side Render” y cargando solo partes de la interfaz de usuario que sean necesarias?

¡Con One App y Holocron, la respuesta es SÍ! … ¡todo lo anterior es posible y mucho más!

Composición de módulos con Holocrón

Los módulos de holocrón se cargan en la memoria y se actualizan dinámicamente cada vez que el “One App Server” busca el “module-map” que contiene las URL de los paquetes de los módulos (por ejemplo, my-module.browser.js). Cuando llega una nueva solicitud HTTP, One App Server presenta uno o más módulos en la página y se asembla en una experiencia única para el usuario. De forma similar a cómo funcionan los componentes en React, los módulos son componibles, lo que significa que un solo módulo puede cargar y reutilizar otros módulos.

El “Root Module” (módulo raíz)

El primer módulo o el “modulo raíz” se llama “root-module”. Este módulo es el punto de entrada para su aplicación. Puede compararlo con el componente raiz de una aplicación React donde todos los demás componentes se presentan debajo como elementos secundarios. Además de presentar los módulos secundarios, el módulo raíz se encarga de la configuración de la aplicación, La seguridad CSP, los orígenes de CORS y el Enrutamiento.

Módulos (children modules)


Estos módulos son la verdadera definición en práctica de lo qué es un “micro frontend” . Son experiencias web autónomas que consisten en componentes React, “reducers” de Redux y “actions”, así como rutas. Son independientes de otros módulos, cargan todos los datos / realizan todas las solicitudes de API que necesitan, y pueden reutilizarse en toda la aplicación con una API configurable a través de “props”.


Empecemos a desarrollar!

Vamos a utilizar el módulo raíz que creamos en la publicación anterior. Si aún no tiene uno, no se preocupe, simplemente ejecute el generador dos veces, una para su módulo raíz y otra para sus módulos secundarios; luego seleccione el tipo de módulo correcto cuando se le solicite.

Use el siguiente comando para crear un módulo utilizando el Generador de módulos de One App: One App Module Generator:

export NODE_ENV=development
npx -p yo -p @americanexpress/generator-one-app-module -- yo @americanexpress/one-app-module

Siga los pasos, asigne a su módulo el nombre “child”. Diríjase a su editor de código favorito y abra el archivo package.json de su módulo raíz y agregue la ruta relativa a su módulo secundario al array “modules” en la sección one-amex.

{
  "name": "root-module",
  "version": "1.0.0",
  "description": "",
  "contributors": [],
  "scripts": {
    "start": "one-app-runner",
    "prebuild": "npm run clean",
    "build": "bundle-module",
    "watch:build": "npm run build -- --watch",
    "clean": "rimraf build",
    "prepare": "npm run build",
    "test:lint": "eslint --ignore-path .gitignore --ext js,jsx,snap .",
    "test:unit": "jest",
    "test": "npm run test:lint && npm run test:unit"
  },
  "one-amex": {
    "runner": {
      "dockerImage": "oneamex/one-app-dev:latest",
      "rootModuleName": "root-module",
      "modules": [
        ".",
        "../child"
      ]
    }
  },
  "dependencies": {
    "@americanexpress/one-app-router": "^1.0.0",
    "holocron": "^1.1.0",
    "react": "^16.12.0",
    "content-security-policy-builder": "^2.1.0"
  },
  "devDependencies": {
    "@americanexpress/one-app-bundler": "^6.0.0",
    "@americanexpress/one-app-runner": "^6.0.0",
    "amex-jest-preset-react": "^6.0.0",
    "babel-eslint": "^8.2.6",
    "babel-preset-amex": "^3.2.0",
    "enzyme": "^3.11.0",
    "enzyme-to-json": "^3.4.4",
    "eslint": "^6.8.0",
    "eslint-config-amex": "^11.1.0",
    "jest": "^25.1.0",
    "rimraf": "^3.0.0"
  },
  "jest": {
    "preset": "amex-jest-preset-react"
  }
}

El array de módulos apunta a la ruta relativa de otros módulos en el mismo proyecto que se puede cargar durante el desarrollo local. Agregue a esta array las rutas relativas de cualquier módulo que desee cargar localmente.

Componer Módulos Usando Rutas:

La primera forma de componer módulos Holocron es creando una ruta que cargue el módulo individual usando holocron-module-route.

npm i -S holocron-module-route

Vamos a crear una nueva ruta llamada child-route que cargará nuestro módulo secundario.

En el archivo childRoutes.jsx de su módulo raíz, agregue lo siguiente:

import React from 'react';
import { Route } from '@americanexpress/one-app-router';
import ModuleRoute from 'holocron-module-route';


const childRoutes = () => [
  <Route path="/" />,
  <ModuleRoute path="child-route" moduleName="child" />,
];

export default childRoutes;

A continuación, debemos hacer “render” del módulo secundario en el componente principal del módulo raíz. Los módulos cargados como rutas con ModuleRoute se pasan al método “render” del módulo raíz a través de “props”.

import React from 'react';
import childRoutes from '../childRoutes';

const RootModule = ({ children }) => (
  <div>
    <h1>Welcome to One App!</h1>
    { children }
  </div>
);

RootModule.childRoutes = childRoutes;

if (!global.BROWSER) {
  // eslint-disable-next-line global-require
  RootModule.appConfig = require('../appConfig').default;
}

export default RootModule;

Después de que haya realizado sus cambios, agrupe su módulo raíz corre el comando npm run build. También puede usar el comando npm run watch: build para actualizar cambios durante el desarrollo.

Para ver los resultados, inicie el servidor “One App” ejecutando el comando npm start (requiere Docker installado en tu máquina) desde el módulo raíz y diríjase a http://localhost:3000/child-route. Debería ver el contenido de su módulo raíz, así como el contenido de su módulo secundario.

Nota: La primera vez que use npm start, puede tomar un par de minutos descargar la Imagen del servidor “One App” desde Docker Hub.

Y ahora un desafío!: realice cambios en el método render de su módulo secundario, mantenga el servidor One App ejecutándose en segundo plano y no se olvide de usar npm run build en su módulo para ver los cambios.


Holocron ComposeModules

El segundo método es cargar módulos como “fragmentos” dentro de la misma página usando composeModules. Este método es útil cuando se desea cargar múltiple módulos en la misma ruta.

En el siguiente ejemplo, vamos a crear un tercer módulo, pero en este caso, en lugar de cargarlo en una ruta usando holocron-module-route, dejaremos que el módulo secundario lo componga y lo cargue en su propio método render.

Vamos a crear un nuevo módulo llamado “grand-child” usando el generador. Seleccione “child” como el tipo de módulo.

*La estructura recomendada ayuda a mantener los módulos relacionados en la misma carpeta para que sea más fácil agregarlos por la ruta relativa.

holocron-example
├── root-module
├── child
├── grand-child

Agregue el “child-module” a la matriz de módulos en package.json del módulo raíz para que se cargue durante el desarrollo local:

 "one-amex": {
    "runner": {
      "dockerImage": "oneamex/one-app-dev:latest",
      "rootModuleName": "root-module",
      "modules": [
        ".",
        "../child",
        "../grand-child"
      ]
    }
  }

El primer paso es despachar composeModules desde el módulo desde el que desea cargar el módulo “grand-child“. composeModules es un “action creator” que carga módulos Holocron y sus datos.

Abra el componente principal del módulo “child” Child.jsx, para que podamos cargar el módulo “grand-child” que acabamos de crear.

import React from 'react';
import { composeModules } from 'holocron';

export const Child = () => (
  <div>
    <h1>I am the child module!</h1>
  </div>
);

export const loadModuleData = ({ store: { dispatch } }) => dispatch(composeModules([
  { name: 'grand-child' },
]));

Child.holocron = {
  name: 'child',
  loadModuleData,
};

export default Child;

Necesitamos despachar el “action creator” composeModules desde la función loadModuleData. Esta función Holocrón obtiene los datos requeridos por su módulo. Por último, agregamos la función loadModuleData al objeto de configuración del módulo Holocron.

Holocron RenderModule

El último paso es renderizar el módulo “grand-child” en nuestro módulo “child” utilizando el componente RenderModule.

Dentro del método render de su módulo “child” Child.jsx, agregue lo siguiente:

<RenderModule moduleName="grand-child" />

La versión final de Child.jsx debería verse así:

import React from 'react';
import { composeModules, RenderModule } from 'holocron';

export const Child = () => (
  <div>
    <h1>I am the child module!</h1>
    <RenderModule moduleName="grand-child" />
  </div>
);

export const loadModuleData = ({ store: { dispatch } }) => dispatch(composeModules([
  { name: 'grand-child' },
]));

Child.holocron = {
  name: 'child',
  loadModuleData,
};

export default Child;

Para ver el resultado final, agrupe su módulo secundario ejecutando npm run build y verifique sus cambios en http://localhost:3000/child-route

Asegúrese de que su servidor “One App” todavía se esté ejecutando en segundo plano, si no esta corriendo, ejecute npm start otra vez desde su módulo de raiz.

Ahora podemos ver el resultado de sus tres módulos en la página:

¡Segundo desafío! (x2): Agregue 2 módulos más, 1 cargado bajo /child-route/module-4 y el último módulo compuesto y renderizado por el cuarto módulo.

Super! 🎉

Conclusión

El poderoso modelo de composición que hace que React sea tan flexible ahora se puede extender a experiencias completas encapsuladas en módulos Holocron. Estas experiencias se pueden desarrollar, probar e implementar individualmente sin la necesidad de reiniciar el servidor. También se pueden compartir en la aplicación de la misma manera que se comparten y reutilizan los componentes, lo que reduce la duplicación de código y permite que equipos independientes trabajen simultáneamente en diferentes partes de la aplicación sin interrumpir o interferir en el trabajo de los demás.

El codigo fuente the este articulo se encuentra aquí: https://github.com/infoxicator/holocron-composition-example


Recent Posts

Do you own a Car 🚗? Here is how to tackle tech debt.

Most developers struggle to explain to product teams why addressing tech debt or refactoring code is important and why it is valuable to the company.

Modular Monoliths: Have we come full circle?

The promise to bring back the “good old productivity win” of monolithic frameworks like Ruby on Rails but keeping the modularity.

The case against DRY, Micro-Frontends edition.

“Don’t Repeat Yourself” How does a modular architectural approach affect the DRY principle?