Reduciendo el tamaño del Bundle en aplicaciones Angular

18 de agosto de 2020

Aunque Angular tiene múltiples tecnologías en su core que mejoran sin esfuerzo la forma en que se genera nuestro Bundle, no es extraño que el tamaño de este se convierta en un problema que requiera algún tipo de cuidado especial. En este post vamos a analizar ciertos consejos básicos que nos permitirán conseguir una aplicación más ligera y rápida de Angular.

Lazy Loading

Normalmente construimos nuestras aplicaciones Angular como una colección de módulos. Estos módulos representan un bloque cohesivo de código dedicado a un dominio de aplicación o incluso a un conjunto de funcionalidades estrechamente relacionadas.

El Lazy Loading se beneficia de esta modularidad al dividir el Bundle de nuestra aplicación en trozos (o chunks) más pequeños que representan el código de estos módulos. El router de Angular se encarga entonces de cargarlos cuando un usuario navega a cualquiera de las rutas servidas por ese módulo.

La siguiente imagen representa los artefactos resultantes construidos después de haber elegido aplicar el Lazy Loading sobre algunos de los módulos de una aplicación Angular:

Lazy Loading diagram

El archivo principal del Bundle contiene Angular y los core modules que nos sirven para arrancar la aplicación. Los módulos que contienen la funcionalidad de nuestra aplicación se encuentran separados en diferentes chunks que solo se cargan si el usuario navega a una ruta que resuelve a algún componente de este.

Como te puedes imaginar, es bastante simple implementar el Lazy Loading en una aplicación existente siempre y cuando se disponga de una buena arquitectura de módulos. El único requisito es dividir el router principal de la aplicación en routers independientes, parte de cada módulo sobre el que queramos aplicar el Lazy Loading.

Caso de uso

Pongamos en práctica este concepto con un simple caso de uso: Nuestra aplicación Angular tiene un módulo, UserModule que incluye componentes enlazados desde el AppComponent. Como ninguna de estas rutas es accesible hasta que la aplicación ha sido cargada, es un claro candidato para aplicar el Lazy Loading.

Empezamos modificando el router principal de la aplicación para definir qué segmentos de ruta serán servidos por módulos Lazy Loaded. En este caso de uso cualquier ruta por debajo de user será resuelta por un router hijo, parte del UserModule.

const appRoutes: AppRoutes = [	
	{ path: '', component: HomeComponent },
	{ path: 'user',
      loadChildren: () => import('./user/user.module').then(m => m.UserModule) }
];

Este router hijo solo contiene rutas que son resueltas por componentes de ese módulo. En nuestro caso, UserProfileComponent y UserPreferencesComponent.

const routes: UserRoutes = [
	{ path: '', component: UserProfileComponent },
	{ path: 'preferences', component: UserPreferencesComponent }
];

Por último, y no por ello menos importante, debemos eliminar el import estático que teníamos en la definición de AppModule para el UserModule, ya que en este caso será el router de Angular el encargado de cargarlo dinámicamente cuando sea necesario.

Tan pronto como el usuario navegue a cualquiera de las rutas resueltas por el child router, Angular cargará dinámicamente el chunk que representa el código de este módulo.

Lazy Loading demo

Angular Ivy y Differential Loading

Durante el año 2019, el equipo Angular introdujo dos características clave que realmente supusieron un cambio en términos de tamaño y rendimiento de nuestras aplicaciones Angular:

Al final, habilitar estas mejoras es una cuestión puramente de configuración. Aplicaciones modernas, generadas utilizando Angular CLI ya disponen tanto de Differential Loading como de Angular Ivy activado.

En el caso de la migración de aplicaciones antiguas, la cosa cambia un poco: Si bien el Differential Loading debería ser un cambio inocuo, la migración de Angular Ivy podría resultar un poco complicada y requiere cierto trabajo especialmente al tratar con librerías de terceros no actualizadas para soportar versiones modernas de Angular. Está en tu mano decidir qué camino tomar.

Angular Ivy size improvements

Webpack Bundle Analyzer

Si ya has implementado Lazy Loading, y estás usando Angular Ivy o Differential Loading, pero aún así sientes que el tamaño de tu bundle sigue siendo demasiado alto, podrías estar interesado en inspeccionar cómo se está construyendo este para buscar otras posibles mejoras a realizar. El Webpack Bundle Analyzer proporciona una agradable e interactiva visualización de los artefactos que Angular genera, haciendo un placer de averiguar sobre qué módulo debemos aplicar el Lazy Loading o qué librería de terceros debemos evitar usar debido al espacio que esta está ocupando.

Usarlo es bastante fácil: solo tienes que instalarlo con npm i webpack-bundle-analyzer --save-dev y luego generar un build de tu aplicación con --stats-json.

Lo único que queda por hacer es ejecutar el Webpack Bundle Analyzer especificando el archivo json generado como resultado del comando anterior: npx webpack-bundle-analyzer dist/stats.json. A través de la URL proporcionada podrás inspeccionar los entresijos de los artefactos generados para tu aplicación.

Generated bundle analysis

Angular CLI budgets

Imaginemos por un momento que acabas de terminar de optimizar el Bundle de tu aplicación, y quieres evitar que cualquier cambio futuro tenga un impacto descontrolado sobre el tamaño de este. Angular CLI nos proporciona una característica útil para rastrear cualquier cambio en el tamaño del paquete estableciendo límites o budgets sobre los que el proceso de build de la aplicación elevará warnings o incluso errors si el tamaño del artefacto generado va mucho más allá de sus valores predefinidos.

Configurar estos límites es realmente sencillo, solo tienes que añadir un array de budgets por cada uno de los entornos que tienes configurado en tu archivo angular.json. Dentro de un budget puedes especificar un type del mismo (ya que tal vez solo quieras monitorizar algunos de los artefactos generados), un name y todos los size limits que quieras establecer.

"budgets": [{
    "type": "bundle",
    "name": "main",
    "maximumWarning": "170kb",
    "maximumError": "250kb"
}]

Cada vez que se supera alguno de los budgets definidos, recibirás una notificación visual del mismo durante el proceso de build de la aplicación forzando así a revisar los últimos cambios realizados para analizar cuál es el que está causando que el tamaño de la aplicación crezca.

Conclusiones

Obviamente no recomendaría a nadie actualizar directamente a Angular Ivy, dado que es un proceso que requiere cariño y atención, pero tanto el Lazy Loading como el Differential Loading son dos técnicas que deberías tener en cuenta cuando pienses en reducir el tamaño del bundle de tu aplicación. Especialmente el Differential Loading ya que es solo una cuestión de configuración para un ahorro realmente valioso en el ancho de banda tanto para el usuario como para el servidor.

Si todavía tienes problemas con el tamaño de tu aplicación, arranca el Webpack Bundle Analyzer, y averigua lo que pasa: En la mayoría de las veces se trata de librerías de componentes de terceros que no se importan correctamente. Recuerda siempre establecer size budgets para prevenir aumentos descontrolados en el tamaño de tu aplicación.

No dudes en echar un vistazo a la Documentación de Angular para obtener más información de todas estas técnicas aquí descritas.

Sobre el autor: Oscar Rodríguez

Friendly Software Engineer at mimacom. Cries when he sees code wrapped by tons of empty lines.

Comments
Únete a nosotros