Aislamiento de dependencias shared: múltiples Share Scopes
En Module Federation, las dependencias shared se registran por defecto en el Share Scope default. Un único Scope a menudo no es suficiente cuando:
- Quieres aislar parte de tus dependencias shared del pool por defecto (por ejemplo, para ejecutar dos ecosistemas de React en paralelo, hacer actualizaciones graduales o aislar dominios en micro-frontends).
- Quieres que el mismo paquete use versiones o estrategias distintas en dominios diferentes, sin dejar de compartirse dentro de cada dominio (singleton/reutilización sigue funcionando dentro de un dominio).
La idea clave de los múltiples Share Scopes es: trasladar el registro y la resolución de dependencias shared a distintos espacios de nombres (Scopes), para que puedas aislar los pools compartidos y aplicar políticas por capas.
Mapa rápido de configuración
La forma más sencilla de entender los múltiples Share Scopes es centrarte en lo que configuras en el producer, en el consumer y en cada entrada shared. No necesitas conocer las estructuras de datos internas ni los nombres de variables del runtime.
- Producer: usa shareScope para declarar qué Share Scopes inicializa este provider (por defecto:
default; admitestring | string[]). - Consumer: usa remotes[remote].shareScope para declarar qué Share Scopes alinea el consumer con un provider determinado (por defecto:
default). - Entrada shared: usa
shared[pkg].shareScopepara decidir en qué pool se registra / resuelve una dependencia (consulta shared.shareScope).
Qué ocurre con las distintas combinaciones
Cuando el consumer inicializa un provider, primero alinea los Share Scopes según la configuración de shareScope de ambos lados —para que el provider sepa qué pools compartidos reutilizar— y después el provider inicializa las dependencias shared según su propio shareScope.
Para describir más fácilmente la relación entre alineación e inicialización, usamos:
- HostShareScope para el
remotes[remote].shareScopeconfigurado en el lado del consumer - RemoteShareScope para el
shareScopeconfigurado en el lado del provider
No configures shareScope / remotes[remote].shareScope como ['default'] ni como []:
- Un solo Scope: usa un string, no un array. Ambos siguen ramas internas distintas para la alineación / inicialización del pool compartido. Si el consumer usa un array y el provider usa un string, el provider alinea los Scopes usando la lista del consumer; si el provider usa un array, solo procesa la lista del provider.
- Array vacío
[]: hace que no se inicialice ningún Scope (nidefault, ni alineación); es una configuración incorrecta.
Los pools compartidos los proporciona el consumer y los inicializa el provider. Los Scopes que el consumer no incluye se rellenan como {} (así, los nombres de scope que faltan nunca provocan un fallo); los Scopes que el provider no incluye no se inicializan. Ambos lados deben incluir un Scope para que una dependencia shared pueda reutilizarse realmente en él.
Playground
Configuración del plugin de build
Producer
Puntos clave:
shareScope: ['default','scope1']controla qué Scopes inicializa en runtime el remoteEntry del provider.shared[pkg].shareScopedecide en qué Scope se registra / resuelve una dependencia. Si@company/design-systemestá enscope1, solo participa en la selección de versión y la reutilización dentro del poolscope1.
Consumer
Puntos clave:
remotes[remote].shareScopecontrola qué Scopes alinea el consumer al inicializar un provider; se pasan al provider comoshareScopeKeys.- Si el consumer está configurado con múltiples Scopes pero el provider solo tiene uno, el scopeMap se alinea, pero el provider solo inicializa la compartición para su único Scope (consulta la tabla de combinaciones anterior). Para que la reutilización entre varios pools "funcione de verdad", normalmente tanto el consumer como el provider deben ponerse de acuerdo en los mismos Scopes.
Runtime puro (Runtime API)
Si no declaras los providers y las dependencias shared a través del plugin de build (por ejemplo, porque quieres registrarlos dinámicamente en runtime), puedes usar la Runtime API para conseguir el mismo efecto de múltiples Share Scopes. Dos APIs clave:
- Registrar providers:
registerRemotesocreateInstance({ remotes }), declarando los Share Scopes que se deben alinear medianteshareScope: string | string[]en la configuración de cada provider. - Registrar dependencias shared:
registerSharedocreateInstance({ shared }), decidiendo en qué Share Scope se ubica una dependencia mediantescope: string | string[]en cada entrada.
El nombre del campo al registrar dependencias shared es scope, no shareScope (a diferencia de shared[pkg].shareScope en el plugin de build).
Control detallado con Runtime Hooks
Los múltiples Share Scopes, en esencia, agrupan los pools compartidos por nombre. Si necesitas un control más fino sobre la selección de Scopes, la alineación y las estrategias de fallback, puedes usar Runtime Hooks para intervenir durante la fase de inicialización o en la resolución de shared.
1. Reescribir shareScopeKeys por provider (beforeInitContainer)
El siguiente ejemplo obliga a legacy_remote a usar siempre el Scope legacy (aunque se haya establecido un shareScope distinto en tiempo de build o en el registro en runtime):
2. Alias / fallback cuando falta un Scope (initContainerShareScopeMap / resolveShare)
initContainerShareScopeMap: ajusta el mapeo deshareScopede cada Scope durante la inicialización del pool compartido del provider.resolveShare: sobrescribe el resultado final de la selección reemplazandoargs.resolver. Devolver solo{ ...args, scope: 'default' }no es suficiente con la implementación actual del runtime.
Ejemplo: si un paquete no se encuentra en scope1, recurre al Scope default:
También puedes crear un alias de un Scope a otro en initContainerShareScopeMap (para que dos Scopes compartan el mismo objeto de pool):