Saltar al contenido principal

Una nueva estrategia de fragmentación de paquetes web en Next.js y Gatsby minimiza el código duplicado para impulsar el rendimiento de carga de la página.

Chrome está colaborando con herramientas y marcos en el ecosistema de código abierto de JavaScript. Recientemente se agregaron varias optimizaciones más nuevas para impulsar el rendimiento de carga de Next.js y
Gatsby. Este post cubre una estrategia de fragmentación granular mejorada que ahora se envía de forma predeterminada en ambos marcos.

Introducción

Como muchos frameworks web, Next.js y Gatsby utilizan paquete web como su agrupador principal. webpack v3 ingresado
CommonsChunkPlugin para hacer factible la salida de módulos compartidos entre diferentes puntos de entrada en un solo (o pocos) fragmentos (o fragmentos) «comunes». El código compartido se puede descargar de forma separada y almacenar en la caché del navegador desde el principio, lo que puede resultar en un mejor rendimiento de carga.

Este patrón se hizo popular entre muchos marcos de aplicaciones de una sola página que adoptaron una configuración de punto de entrada y paquete que se veía así:

commons-pattern-diagram-7854309

Aún cuando es práctico, el concepto de agrupar todo el código del módulo compartido en un solo fragmento tiene sus limitaciones. Los módulos no compartidos en cada punto de entrada se pueden descargar para rutas que no lo usan, lo que resulta en la descarga de más código del necesario. A modo de ejemplo, cuando page1 carga el common fragmento, carga el código para moduleC aún cuando page1 no utiliza moduleC. Por esta razón, junto con algunos otros, webpack v4 eliminó el complemento a favor de uno nuevo: SplitChunksPlugin.

Fragmentación mejorada

La configuración predeterminada para SplitChunksPlugin anda bastante bien para la mayoría de los usuarios. Se crean varios fragmentos divididos en función de una serie de condiciones
para evitar la consecución de código duplicado en varias rutas.

A pesar de todo, muchos frameworks web que utilizan este complemento siguen un enfoque de «común único» para la división de fragmentos. Next.js, a modo de ejemplo, generaría un commons paquete que contenía cualquier módulo que se usa en más del 50% de las páginas y todas las dependencias del marco (react, react-dom, y así).

const splitChunksConfigs = {

prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
},
react: use-subscription)[/]/,
,
},
},

Aún cuando incluir código dependiente del marco en un fragmento compartido significa que se puede descargar y almacenar en caché para cualquier punto de entrada, la heurística basada en el uso de incluir módulos comunes utilizados en más de
la mitad de las páginas no es muy eficaz. La modificación de esta proporción solo daría como consecuencia uno de dos resultados:

  • Si reduce la proporción, se descarga más código innecesario.
  • Si aumenta la proporción, se duplicará más código en varias rutas.

Para solucionar este problema, Next.js adoptó un configuración distinto paraSplitChunksPlugin que reduce el código innecesario para cualquier ruta.

  • Cualquier módulo de terceros suficientemente grande (más de 160 KB) se divide en su propio fragmento individual
  • Una separacion frameworks se crea un fragmento para las dependencias del marco (react, react-dom, y así)
  • Se crean tantos fragmentos compartidos como sean necesarios (hasta 25)
  • El tamaño mínimo para generar un fragmento se cambia a 20 KB

Esta estrategia de fragmentación granular proporciona los siguientes beneficios:

  • Se mejoran los tiempos de carga de la página. La emisión de varios fragmentos compartidos, en lugar de uno solo, minimiza la cantidad de código innecesario (o duplicado) para cualquier punto de entrada.
  • Almacenamiento en caché mejorado durante la navegación. La división de bibliotecas grandes y dependencias del marco en fragmentos separados reduce la oportunidad de invalidación de caché, puesto que es poco probable que ambos cambien hasta que se realice una actualización.

Puede ver la configuración completa que Next.js adoptó en webpack-config.ts.

Más solicitudes HTTP

SplitChunksPlugin definió la base para la fragmentación granular, y aplicar este enfoque a un marco como Next.js no era un concepto totalmente nuevo. A pesar de todo, muchos frameworks continuaron utilizando una única estrategia heurística y de paquetes «comunes» por algunas razones. Esto incluye la preocupación de que muchas más solicitudes HTTP puedan afectar negativamente al rendimiento del sitio.

Los navegadores solo pueden abrir un número limitado de conexiones TCP a un solo origen (6 para Chrome), por lo que minimizar el número de fragmentos generados por un agrupador puede asegurar que el número total de solicitudes permanezca por debajo de este umbral. A pesar de todo, esto solo es cierto para HTTP / 1.1. La multiplexación en HTTP / 2 posibilita transmitir diversos solicitudes en paralelo utilizando una sola conexión sobre un solo origen. Dicho de otra forma, de forma general no tenemos que preocuparnos por limitar la cantidad de fragmentos emitidos por nuestro agrupador.

Todos los principales navegadores admite HTTP / 2. Los equipos de Chrome y Next.js querían ver si incrementar el número de solicitudes dividiendo el paquete «commons» único de Next.js en varios fragmentos compartidos afectaría el rendimiento de carga de alguna manera. Comenzaron midiendo el rendimiento de un solo sitio mientras modificaban el número máximo de solicitudes paralelas usando el
maxInitialRequests

propiedad.

performance-num-requests-1-2748678

En un promedio de tres ejecuciones de diversos pruebas en una sola página web, el
load,
iniciar-renderizar
y los tiempos de First Contentful Paint permanecieron prácticamente iguales al variar el número máximo de solicitudes iniciales (de 5 a 15). Curiosamente, notamos una ligera sobrecarga de rendimiento solo posteriormente de dividir de forma agresiva a miles de solicitudes.

performance-num-requests-2-1561814

Esto mostró que mantenerse por debajo de un umbral confiable (20 ~ 25 solicitudes) logró el equilibrio adecuado entre el rendimiento de carga y la eficiencia del almacenamiento en caché. Posteriormente de algunas pruebas de referencia, se seleccionó 25 como maxInitialRequest contar.

Modificar el número máximo de solicitudes que ocurren en paralelo resultó en más de un paquete compartido y separarlos apropiadamente para cada punto de entrada redujo significativamente la cantidad de código innecesario para la misma página.

js-payload-num-requests-7283783

Este experimento solo consistía en modificar la cantidad de solicitudes para ver si habría algún efecto negativo en el rendimiento de carga de la página. Los resultados sugieren que la configuración maxInitialRequests a
25 en la página de prueba fue óptimo debido a que redujo el tamaño de la carga útil de JavaScript sin ralentizar la página. La cantidad total de JavaScript que se necesitaba para hidratar la página seguía siendo la misma, lo que explica por qué el rendimiento de carga de la página no necesariamente mejoró con la cantidad reducida de código.

webpack utiliza 30 KB como tamaño mínimo predeterminado para que se genere un fragmento. A pesar de todo, acoplar un
maxInitialRequests el valor de 25 con un tamaño mínimo de 20 KB resultó en un mejor almacenamiento en caché.

Reducciones de tamaño con trozos granulares

Muchos marcos, incluido Next.js, se centran en el enrutamiento del lado del cliente (manejado por JavaScript) para inyectar etiquetas de script más nuevas para cada transición de ruta. Pero, ¿cómo predeterminan estos fragmentos dinámicos en el momento de la construcción?

Next.js utiliza un archivo de manifiesto de compilación del lado del servidor para establecer qué fragmentos generados son utilizados por diferentes puntos de entrada. Para proporcionar esta información además al cliente, se creó un archivo de manifiesto de compilación abreviado del lado del cliente para adjudicar todas las dependencias para cada punto de entrada.


getDependencies (route) {
return this.promisedBuildManifest.then(
man => (man[route] && man[route].map(dirección url => `/_next/${dirección url}`)) || []
)
}

outputted-chunks-9444947

Esta nueva estrategia de fragmentación granular se implementó por primera vez en Next.js detrás de una bandera, donde se probó en varios de los primeros usuarios. Muchos vieron reducciones significativas en el JavaScript total utilizado para todo su sitio:

La versión final se envió por defecto en versión 9.2.

Gatsby

Gatsby utilizado para seguir el mismo enfoque de usar una heurística basada en el uso para establecer módulos comunes:

config.optimization = {

splitChunks: {
name: false,
chunks: `all`,
cacheGroups: {
default: false,
vendors: false,
commons: {
name: `commons`,
chunks: `all`,


minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
},
react: react-dom,

Al aprovechar al máximo la configuración de su paquete web para adoptar una estrategia de fragmentación granular semejante, además notaron reducciones considerables de JavaScript en muchos sitios grandes:

Eche un vistazo al PR para saber cómo implementaron esta lógica en la configuración de su paquete web, que se envía de forma predeterminada en v2.20.7.

Conclusión

El concepto de envío de fragmentos granulares no es específico de Next.js, Gatsby o inclusive webpack. Todo el mundo debería considerar mejorar la estrategia de fragmentación de su aplicación si sigue un gran enfoque de paquete «común», independientemente del marco o paquete de módulos utilizado.

  • Si desea ver las mismas optimizaciones de fragmentación aplicadas a una aplicación de React vainilla, eche un vistazo a esto muestra de la aplicación React. Usa una versión simplificada de la estrategia de fragmentación granular y puede ayudarlo a empezar a aplicar el mismo tipo de lógica a su sitio.
  • Para Rollup, los fragmentos se crean de forma granular de forma predeterminada. Echa un vistazo a
    manualChunks si desea configurar manualmente el comportamiento.

R Marketing Digital