Shared Tree Shaking

Al usar Module Federation, a menudo compartimos dependencias comunes (como antd, React, etc.) mediante la configuración shared para reducir el empaquetado duplicado y mantener versiones de dependencias coherentes entre aplicaciones. Sin embargo, el mecanismo clásico de shared tiene un inconveniente: siempre proporciona el bundle completo de la dependencia, aunque tu aplicación solo use una pequeña parte (por ejemplo, solo el componente Button de antd).

Esto puede provocar:

  • Artefactos de build grandes: se empaqueta código innecesario y aumenta el tamaño final del resultado.
  • Sobrecarga en runtime: el navegador descarga y ejecuta más JavaScript del necesario, lo que ralentiza la carga y el renderizado de la página.

Shared Tree Shaking está diseñado para resolver esto. Analiza tu código, identifica con precisión qué exports se usan realmente y empaqueta solo ese subconjunto. Como resultado, tu aplicación puede consumir una dependencia shared optimizada y más pequeña.

Ventajas principales
  • Menor tamaño del resultado: reduce el código innecesario desde el origen.
  • Carga más rápida: los usuarios descargan solo lo que necesitan.
  • Mejor rendimiento en runtime: hay menos JavaScript que analizar y ejecutar.

Inicio rápido

Habilitar Shared Tree Shaking es sencillo: añade la opción treeShaking en tu configuración.

Ejemplo

rspack.config.ts
export default {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      // ...
      shared: {
        antd: {
          treeShaking: { mode: 'runtime-infer' },
        },
      },
    }),
  ],
};

Verificación local

Después de hacer el build en local, revisa el tamaño del resultado y el contenido exportado de la dependencia shared (p. ej., antd). Lo esperado es que solo contenga los módulos que realmente usas.

Configuración

La opción principal es sharedItem.treeShaking. También puedes controlar dónde se generan los artefactos de Tree Shaking mediante:

Modos

Hay dos modos para adaptarse a distintas configuraciones de equipo y escalas de proyecto.

runtime-infer

runtime-infer es una estrategia ligera que no requiere un servicio centralizado.

  • Habilitar: establece mode en runtime-infer.
  • Ideal para:
    • desarrollo local y validación rápida,
    • equipos sin un servicio de despliegue o de CI,
    • proyectos pequeños con un único consumer.
  • Cómo funciona: en runtime, si las necesidades de la página actual ya están cubiertas por un bundle shared con tree shaking cargado previamente, lo reutiliza. En caso contrario, recurre a cargar el bundle completo de la dependencia para garantizar que todo funcione correctamente.
  • Mejorar la tasa de aciertos: sin una visión global, la tasa de reutilización de runtime-infer puede ser limitada. Puedes ampliar manualmente la lista usedExports en tu configuración para declarar de antemano los módulos que podrías necesitar, guiando al compilador para que genere un bundle shared más adecuado y mejorando la reutilización.

server-calc

server-calc es la buena práctica muy recomendada. Se apoya en un servicio centralizado para maximizar las ventajas de Tree Shaking y lograr un óptimo global.

Deploy Server (implementación de referencia)

Si planeas construir tu propio Deploy Server, puedes usar el siguiente flujo general para implementar server-calc.

1. Agregar los usedExports durante el despliegue

En tu pipeline de despliegue de CI/CD, tu servicio debe:

  • recopilar los metadatos de build de todas las aplicaciones que están a punto de publicarse (normalmente mf-stats.json o archivos similares);
  • para una misma dependencia shared (p. ej., antd@6.1.0), agregar los usedExports declarados por cada aplicación.

2. Calcular la unión mínima global

Combina y deduplica todas las listas de usedExports recopiladas para obtener el conjunto global mínimo (la unión) que necesita el sistema.

3. Ejecutar un build secundario para producir el bundle shared optimizado

Invoca tu herramienta de build (p. ej., la CLI de Rspack) como un build aislado y pásale:

  • el nombre y la versión de la dependencia shared objetivo (p. ej., antd@6.1.0);
  • la unión global de usedExports del paso anterior. El resultado es un bundle shared independiente que contiene solo los módulos necesarios.

4. Actualizar y publicar el Snapshot

Sube el bundle optimizado a tu servidor de recursos estáticos (CDN). Después, actualiza el archivo Snapshot de Module Federation (normalmente la versión remota de mf-manifest.json) con campos como:

  • secondarySharedTreeShakingEntry: URL del bundle optimizado,
  • secondarySharedTreeShakingName: nombre único del bundle,
  • treeShakingStatus: márcalo como disponible.

Por último, distribuye el Snapshot actualizado a todos los consumers.

5. Carga bajo demanda en runtime

Cuando un usuario visita la aplicación, el MF Runtime lee el Snapshot. Si treeShakingStatus está disponible y el entorno cumple las condiciones de carga, carga el bundle optimizado mediante secondarySharedTreeShakingEntry en lugar del bundle completo.

Versionado, caché y compatibilidad
  • El versionado estricto es la base. Asegúrate de que los builds secundarios y la carga en runtime se basen exactamente en la misma versión (p. ej., antd@6.1.0) para evitar discrepancias.
  • Usa una estrategia de caché razonable para los artefactos del build secundario. Como el contenido cambia con los usedExports globales, es preferible usar nombres de archivo basados en el hash del contenido para el almacenamiento en caché a largo plazo.
  • Este flujo es compatible hacia adelante: cuando no está habilitado o no se cumplen las condiciones, los consumers recurren al bundle completo sin afectar la funcionalidad existente.

Validación y rollback

¿Cómo confirmar que Tree Shaking funciona?

  1. Revisión en el panel Network: en el panel Network de las DevTools del navegador, filtra los archivos JS cargados. Confirma que el archivo cargado es el artefacto del build secundario (normalmente contiene identificadores como secondary o un hash) y no el bundle completo original. Su tamaño debería ser significativamente menor.
  2. Inspeccionar el contenido del bundle: descarga el archivo JS de la dependencia shared cargada y busca exports que no hayas usado (p. ej., si solo usaste Button, busca Modal). Si no aparecen, se eliminaron correctamente con tree shaking.
  3. Verificación con Chrome DevTools: en Chrome DevTools, abre el panel "Shared" y selecciona la dependencia shared objetivo. Revisa sus etiquetas de estado: Tree Shaking Loaded significa que Tree Shaking es efectivo y se ha cargado el artefacto recortado; Loaded significa que se ha recurrido a cargar el bundle completo; Tree Shaking Loading significa que se está cargando por la ruta de Tree Shaking.

¿Cómo hacer rollback de forma segura?

El Tree Shaking de dependencias shared está diseñado con un fallback seguro automático. Si el runtime detecta problemas (Snapshot no entregado, errores de red, discrepancia de versiones, etc.), carga por defecto el bundle completo de la dependencia shared para mantener estable la aplicación.

Si necesitas deshabilitarlo manualmente, detén los pasos de build secundario y de actualización del Snapshot en tu servicio de despliegue.

Generar un artefacto shared secundario con tree shaking

Nota

Si tu objetivo es "aplicar tree shaking a una dependencia shared para una sola aplicación", normalmente basta con usedExports.

Esta sección trata sobre cómo generar un artefacto secundario independiente que puede cargarse bajo demanda en runtime y que puede distribuirse o reutilizarse desde el lado del despliegue.

Este proceso solo construye las dependencias shared; los remotes no tendrán efecto. La entrada original del proyecto se ignora automáticamente durante el build y solo se empaquetan los módulos shared.

Proyectos que no son Modern.js

Usa TreeShakingSharedPlugin en tu configuración de build, establece secondary: true y reutiliza tu mfConfig existente.

@module-federation/enhanced
@module-federation/rsbuild-plugin
Integrado en Rspack
import { TreeShakingSharedPlugin } from '@module-federation/enhanced';
// import { TreeShakingSharedPlugin } from '@module-federation/enhanced/rspack';
// ...
plugins: [
  new TreeShakingSharedPlugin({
    secondary: true,
    mfConfig,
  }),
];

Proyectos Modern.js

Cuando uses @module-federation/modern-js-v3, habilita secondarySharedTreeShaking: true en las opciones del plugin para generar el artefacto secundario.

modern.config.ts
import mfPlugin from '@module-federation/modern-js-v3';
// ...
plugins: [
  mfPlugin({
    secondarySharedTreeShaking: true,
  }),
];

Preguntas frecuentes

1. ¿Se puede usar Shared Tree Shaking con eager: true?

No. eager: true empaqueta las dependencias shared en el chunk inicial, lo que entra en conflicto con el modelo de carga bajo demanda que requiere Tree Shaking. Debes elegir entre ambos:

  • Si la dependencia shared es pequeña y quieres la carga inicial más rápida posible, considera eager: true.
  • Si la dependencia shared es grande (p. ej., una biblioteca de componentes), deshabilita eager y usa Tree Shaking para obtener mejoras significativas de tamaño y rendimiento.

2. ¿Qué debo tener en cuenta con las dependencias singleton en runtime-infer?

Ten cuidado. Si una dependencia shared está configurada como singleton: true (debe ser un singleton global), puedes encontrarte con escenarios como:

  • La App A usa solo antd/Button y carga su propio bundle con tree shaking.
  • La App B usa antd/Modal y carga su propio bundle completo.

Esto puede dar lugar a dos instancias distintas de antd en la misma página (una mínima y otra completa). Eso rompe la restricción de singleton y puede provocar conflictos de estilos, estado no compartido o incluso fallos.

Recomendación: para bibliotecas que deben seguir siendo singleton, es preferible usar server-calc para que todos los consumers carguen el mismo bundle optimizado globalmente. Si tienes que usar runtime-infer, amplía usedExports para que el bundle generado sea más completo y reducir el riesgo de conflictos.

3. ¿Cuáles son los requisitos previos para que funcione Tree Shaking?

Depende principalmente del análisis estático del bundler. Tu código debe usar ES Modules (import/export) para que el compilador pueda determinar qué exports se usan. Los módulos CommonJS (require/module.exports) normalmente no admiten un tree shaking eficaz.

4. ¿Esto romperá las dependencias shared de los proyectos ya publicados?

No. La fuente de datos y la ruta de carga de Tree Shaking están aisladas del mecanismo shared existente, y hay condiciones estrictas de activación y un comportamiento de fallback seguro. No afecta a los proyectos heredados estables.

Lecturas adicionales