Reduce las cargas útiles de JavaScript con la división de código

La mayoría de las páginas web y las aplicaciones se componen de distintas partes. En lugar de enviar todo el código JavaScript que compone la aplicación en cuanto se carga la primera página, dividir el código JavaScript en varios fragmentos mejora el rendimiento de la página.

En este codelab, se muestra cómo usar la división de código para mejorar el rendimiento de una aplicación simple que ordena tres números.

Una ventana del navegador muestra una aplicación titulada Ordenador mágico con tres campos para ingresar números y un botón para ordenar.

Medir

Como siempre, es importante medir primero el rendimiento de un sitio web antes de intentar agregar optimizaciones.

  1. Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completa pantalla completa.
  2. Presiona "Control + Mayús + J" (o bien "Comando + Opción + J" en Mac) para abrir Herramientas para desarrolladores.
  3. Haga clic en la pestaña Red.
  4. Selecciona la casilla de verificación Inhabilitar caché.
  5. Vuelve a cargar la app.

Panel Network que muestra un paquete de JavaScript de 71.2 KB.

71.2 KB de JavaScript solo para ordenar algunos números en una aplicación simple. ¿Para qué me sirve esto?

En el código fuente (src/index.js), se importa la biblioteca lodash y se usa en esta aplicación. Lodash proporciona muchas funciones de utilidad útiles, pero aquí se usa un solo método del paquete. Instalar y, luego, importar dependencias completas de terceros en las que solo se usa una pequeña parte de ellas es un error común.

Optimiza

Existen varias formas de cortar el tamaño del paquete:

  1. Escribe un método de ordenamiento personalizado en lugar de importar una biblioteca de terceros
  2. Usa el método integrado Array.prototype.sort() para ordenar de forma numérica.
  3. Solo importa el método sortBy desde lodash, no toda la biblioteca.
  4. Descarga el código para ordenar solo cuando el usuario haga clic en el botón.

Las opciones 1 y 2 son métodos perfectamente apropiados para reducir el tamaño del paquete (y probablemente tendrían más sentido para una aplicación real). Sin embargo, en este instructivo no se usan para enseñar ;".

Las opciones 3 y 4 ayudan a mejorar el rendimiento de esta aplicación. En las próximas secciones de este codelab, se abordan estos pasos. Como en cualquier instructivo de programación, siempre intenta escribir el código tú mismo en lugar de copiar y pegar.

Importa solo lo que necesites

Se deben modificar algunos archivos para importar solo el método desde lodash. Para comenzar, reemplaza esta dependencia en package.json:

"lodash": "^4.7.0",

con esto:

"lodash.sortby": "^4.7.0",

Ahora en src/index.js, importa este módulo específico:

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

Y actualiza el modo en que se ordenan los valores:

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

Vuelve a cargar la aplicación, abre Herramientas para desarrolladores y vuelve a ver el panel Red.

Panel Network que muestra un paquete de JavaScript de 15.2 KB.

Para esta aplicación, el tamaño del paquete se redujo más de 4 veces con muy poco trabajo, pero todavía hay más posibilidades de mejorar.

División de código

webpack es uno de los agrupadores de módulos de código abierto más populares que se usan en la actualidad. En resumen, agrupa todos los módulos de JavaScript (y otros elementos) que conforman una aplicación web en archivos estáticos que el navegador puede leer.

El paquete único que se usa en esta aplicación se puede dividir en dos fragmentos distintos:

  • Uno responsable del código que conforma la ruta inicial
  • Un bloque secundario que contiene el código de ordenación

Con el uso de las importaciones dinámicas, un fragmento secundario se puede cargar de forma diferida o a pedido. En esta aplicación, el código que compone el fragmento solo se puede cargar cuando el usuario presiona el botón.

Primero, quita la importación de nivel superior para el método de ordenamiento en src/index.js:

import sortBy from "lodash.sortby";

Y impórtalo en el objeto de escucha de eventos que se activa cuando presionas el botón:

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

La función import() es parte de una propuesta (actualmente en la etapa 3 del proceso de TC39) para incluir la capacidad de importar un módulo de forma dinámica. Webpack ya incluyó compatibilidad con esto y sigue la misma sintaxis establecida por la propuesta.

import() muestra una promesa y, cuando se resuelve, se proporciona el módulo seleccionado que se divide en un fragmento independiente. Después de que se muestra el módulo, se usa module.default para hacer referencia a la exportación predeterminada que proporciona lodash. La promesa está encadenada con otra .then que llama a un método sortInput para ordenar los tres valores de entrada. Al final de la cadena de promesas, .catch() se usa para manejar casos en los que la promesa se rechaza debido a un error.

Lo último que debes hacer es escribir el método sortInput al final del archivo. Debe ser una función que muestre una función que tome el método importado de lodash.sortBy. Luego, la función anidada puede ordenar los tres valores de entrada y actualizar el DOM.

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

Supervisión

Vuelve a cargar la aplicación por última vez y vuelve a revisar el panel Network. Solo se descarga un pequeño paquete inicial apenas se carga la app.

Panel Network que muestra un paquete de JavaScript de 2.7 KB.

Después de que se presiona el botón para ordenar los números de entrada, se recupera y ejecuta el fragmento que contiene el código de ordenamiento.

Panel de red que muestra un paquete de JavaScript de 2.7 KB seguido de un paquete de JavaScript de 13.9 KB.

Observa cómo se ordenan los números.

Conclusión

La división de código y la carga diferida pueden ser técnicas extremadamente útiles para reducir el tamaño inicial del paquete de tu aplicación, lo que puede generar directamente tiempos de carga de la página mucho más rápidos. Sin embargo, debes considerar algunos aspectos importantes antes de incluir esta optimización en tu aplicación.

IU de carga diferida

En el momento de realizar la carga diferida de módulos de código específicos, es importante considerar cómo sería la experiencia para los usuarios con conexiones de red más débiles. Dividir y cargar un fragmento muy grande de código cuando un usuario envía una acción puede hacer que parezca que la aplicación dejó de funcionar, por lo que debes considerar mostrar un indicador de carga de algún tipo.

Carga diferida de módulos de nodos de terceros

No siempre es el mejor enfoque para cargar dependencias de terceros de forma diferida en tu aplicación y depende de dónde las uses. Por lo general, las dependencias de terceros se dividen en un paquete vendor independiente que se puede almacenar en caché, ya que no se actualizan con tanta frecuencia. Obtén más información sobre cómo el SplitChunksPlugin puede ayudarte a hacerlo.

Carga diferida con un framework de JavaScript

Muchos frameworks y bibliotecas populares que usan webpack proporcionan abstracciones para facilitar la carga diferida que usar importaciones dinámicas en medio de la aplicación.

Si bien es útil para comprender cómo funcionan las importaciones dinámicas, siempre usa el método que recomienda tu framework o biblioteca para cargar de forma diferida módulos específicos.

Precarga y carga previa

Cuando sea posible, aprovecha las sugerencias del navegador, como <link rel="preload"> o <link rel="prefetch">, para intentar cargar los módulos críticos aún más pronto. Webpack admite ambas sugerencias mediante el uso de comentarios mágicos en las declaraciones de importación. Esto se explica con más detalle en la guía Precarga fragmentos críticos.

Carga diferida más que código

Las imágenes pueden representar una parte importante de una aplicación. La carga diferida de los elementos que se encuentran en la mitad inferior o fuera del viewport del dispositivo puede acelerar un sitio web. Obtén más información al respecto en la guía Lazysizes.