La versión corta: Module Federation permite que una app de React Native cargue sus features en runtime, así cada una se puede desplegar y actualizar por su cuenta en vez de viajar dentro de una única release de la App Store. Eso te da deploys independientes y arreglos over-the-air. También te deja encima un problema de sistemas distribuidos que antes era trabajo del bundler. Esta serie construye un setup federado funcionando desde cero sobre una pequeña app de Pokédex. Este primer post va de si deberías hacerlo o no.
Cada feature se publica en cada release
Una app estándar de React Native es un único bundle. Organízala bien, por feature en vez de por tipo, y aquí no cambia nada: la pantalla de login, la página de ajustes, el informe que nadie abre, todo compilado junto, todo bloqueado tras la misma revisión de la store, todo en el mismo tren de releases. Un arreglo de una línea en una pantalla espera a que toda la app se reconstruya, se vuelva a enviar y se apruebe.
Para una app pequeña con un solo equipo, eso está bien. El tren de releases es barato y todo el mundo está en él de todas formas. Para una app grande con varios equipos, sale caro. El arreglo urgente de un equipo se queda detrás de la feature a medias de otro equipo porque comparten un binario. La release se convierte en una negociación, y la cadencia cae al ritmo del equipo que más tarda en subirse.
Ese acoplamiento es lo que Module Federation intenta deshacer. No el tamaño del bundle, no la velocidad de build, esos son efectos secundarios agradables. El premio de verdad es romper el vínculo entre «cambié mi feature» y «toda la app tiene que publicarse».
Qué es en realidad
Una app federada tiene un host y un conjunto de remotes. El host es el shell: la navegación, la tab bar, las librerías compartidas, las piezas que siempre están ahí. Los remotes son las features, y cada una se construye y se despliega por su cuenta, y luego se carga en runtime desde una URL.
El host no compila los remotes dentro de sí mismo como hace un bundle único. Module Federation no resuelve por sí solo nada del offline: incrustar una copia de cada remote en el binario, para que la app revisada funcione sola y sin red, es una arquitectura que te toca montar a ti, y que esta serie monta más adelante. La versión en vivo viene de un CDN (una red de distribución de contenidos) y se actualiza sin una release. El host también aporta una sola vez las librerías pesadas que se comparten, React, el stack de navegación, la capa de estilos, así cada remote consume la copia del host en vez de cargar la suya propia. Un remote pasa a ser un payload pequeño de código de feature que encaja en un shell que ya sostiene todo lo que hay debajo.
En la práctica eso corre sobre Re.Pack (Rspack por debajo) con Module Federation 2.0. La mecánica la vemos en un post posterior. Por ahora el modelo mental basta: un shell que carga features en runtime, desde la red o desde un fallback empotrado, contra un contrato sobre lo que el shell provee.
Qué te aporta
Deploys independientes. Un equipo de feature publica cuando su feature está lista, no cuando sale el tren. La release deja de ser un recurso compartido por el que todo el mundo hace cola.
Arreglos over-the-air. Un bug en un remote es volver a subir ese remote, no un envío a la store. El arreglo está publicado en minutos, y cada usuario lo recoge en su siguiente arranque, dentro de las reglas de la plataforma (Qué cuesta las sopesa).
Arranques más rápidos. Las features que no se necesitan al lanzar se cargan de forma lazy, así corre menos JavaScript en el camino crítico. La descarga en sí no se reduce si publicas un fallback offline, porque el binario sigue llevando dentro todos los remotes, pero el arranque sí puede.
Autonomía de equipo a escala. Cada feature tiene su propio build, su propio deploy, su propia cadencia. La arquitectura deja de forzar a los equipos a ir al unísono.
Si no sufres de verdad ninguno de esos problemas, puedes dejar de leer aquí. Federation resuelve el acoplamiento. Sin acoplamiento, no hay razón para pagar por la solución.
Qué cuesta
Esta es la parte que los posts entusiastas se saltan, así que es la parte en la que merece la pena ir despacio.
El contrato de singletons compartidos. El host provee un React, una librería de navegación, una capa de estilos, y cada remote renderiza contra esas copias. En cuanto un remote necesita una versión más nueva de una librería compartida que la que el host carga, tienes un problema de version skew. Si no lo manejas, el resultado depende de cómo esté configurada la entrada compartida, y no siempre acaba en la copia del host: la resolución de singletons elige una única versión para todos y puede quedarse con la más alta compatible, así que cualquiera de los dos lados puede terminar ejecutándose contra una copia para la que no se construyó. Tiene solución, la serie construye el arreglo, pero resolverlo es el coste: el conjunto compartido pasa a ser un contrato del que respondes tú y que tienes que mantener compatible, un trabajo que antes hacía el compilador gratis.
La carga de compatibilidad, sobre todo para versiones antiguas de la app. No todos los usuarios actualizan. Un binario que alguien instaló hace meses tiene las librerías compartidas congeladas en lo que se publicó entonces. Publica un remote que necesite unas más nuevas y rompes justo a quien no se ha actualizado. Así que acabas manteniendo disponibles versiones antiguas del remote para versiones antiguas de la app, la misma disciplina que mantener vivo un endpoint de API viejo hasta que el último cliente deja de llamarlo. Eso no es trabajo de bundler. Eso es operar un servicio versionado.
Integridad. En cuanto tu app descarga y ejecuta código desde una URL, esa URL es una superficie de ataque. Tienes que firmar lo que publicas y que el dispositivo lo verifique antes de ejecutar, o un host comprometido puede entregar a tus usuarios lo que le dé la gana. Luego tienes que proteger también la elección de versión, para que un manifest reproducido o revertido no pueda servir en silencio un build viejo y vulnerable. La seguridad que te daba gratis un único binario firmado ahora te toca construirla.
Module Federation es un problema de sistemas distribuidos vestido de bundler.
Reglas de plataforma. Aquí mandan dos documentos de Apple, y cada uno traza una línea distinta. El Developer Program License Agreement (sección 3.3.1(B)) permite que una app ejecute código interpretado descargado, como JavaScript, con tres condiciones: ese código no puede cambiar el propósito principal de la app, no puede montar una tienda ni un escaparate para otro código u otras apps, y no puede saltarse la firma, el sandbox ni ninguna otra medida de seguridad del sistema operativo. La guideline 2.5.2 de revisión aprieta todavía más: el binario que envías tiene que funcionar por sí solo, y no puede descargar, instalar ni ejecutar código que introduzca o cambie funcionalidades de la app. Los dos documentos se aplican a la vez, y cumplir las condiciones de la licencia no demuestra que pases revisión: la redacción de 2.5.2 alcanza a cualquier código descargado que cambie lo que hace la app, no solo al que queda fuera del propósito revisado. La lectura con la que operan los servicios OTA es mantener lo que se envía por aire dentro de la funcionalidad que Apple ya ha revisado, en forma de correcciones y ajustes, aceptando que la redacción de la directriz deja a Apple margen para discrepar.
Superficie operativa. Un CDN que operar, cachés que invalidar, rollbacks que automatizar, fallos que monitorizar. Cuando un remote no carga, la app tiene que degradarse a algo seguro en vez de mostrar una pantalla en blanco. Esa red de seguridad es ingeniería de verdad, y corre de tu cuenta.
En conjunto, la parte del bundler está acotada: lo configuras y ya está. La parte de sistemas (firma, versionado, compatibilidad, fallback) es el trabajo de verdad, y nunca termina del todo.
Cuándo merece la pena
Recurre a ello cuando las tres cosas sean ciertas:
- Varios equipos se están pisando en una release compartida.
- El acoplamiento es un coste medido, cadencia más lenta, arreglos bloqueados, no uno teórico.
- Alguien puede responsabilizarse de la plataforma, el CDN, la firma, el contrato de versiones, la capa de fallback, como trabajo continuo.
Sáltalo cuando la app es pequeña, la mantiene un solo equipo, y una release a la store cada par de semanas no supone una carga. La complejidad que asumirías empequeñece el acoplamiento que quitarías. El code splitting por sí solo, chunks async sin la maquinaria de runtime-remote, te da la carga lazy y el arranque más rápido a una fracción del coste, y es una parada sensata antes de la federación completa.
Federation es una herramienta de escalado. Adóptala porque has llegado a la escala que la justifica, no porque la arquitectura sea interesante. Lo es. Esa es la trampa.
Qué hace el resto de esta serie
A partir de aquí es práctico. Construimos un setup federado sobre una pequeña app de Pokédex y lo llevamos hasta el final, en un arco de diecisiete partes:
- un host y un primer remote cargando en runtime, después el contrato de singletons compartidos y la trampa que lo rompe en silencio
- la frontera en sí: un paquete de contrato tipado, un store compartido y quién responde de qué cuando varios equipos comparten runtime
- estado de cliente, dos stacks de estado comparados sobre la misma app, dos backends en una caché y un design system compartido como singleton
- accesibilidad a través de la frontera, y el relevo entre React Native y el shell nativo
- el build de producción, la entrega por CDN con un mapa de versiones, un fallback offline dentro del binario y un mapa firmado desde el que la app puede revertirse sola
Al final tendrás una versión funcionando de todo lo que este post acaba de advertirte, y una idea clara de si a tu app le compensa.
Fuentes
- Re.Pack: el bundler de React Native que envuelve Rspack y trae soporte de Module Federation
- Module Federation 2.0: la arquitectura de runtime
- Rspack: el bundler basado en Rust que hay bajo Re.Pack
- App Store Review Guidelines, 2.5.2: la restricción de Apple sobre apps autocontenidas
- Apple Developer Program License Agreement: el permiso para código interpretado, sección 3.3.1(B), y sus tres condiciones