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

La mayoría de las páginas y aplicaciones web se componen de muchas partes diferentes. 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.

En la ventana del navegador, se muestra una aplicación llamada Magic Sorter con tres campos para ingresar números y un botón de ordenamiento.

Medir

Como siempre, es importante medir primero el rendimiento de un sitio web antes de intentar agregar cualquier optimización.

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

Panel de red 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 y se usa la biblioteca lodash en esta aplicación. Lodash proporciona muchas funciones de utilidad útiles, pero aquí solo se usa un método del paquete. Un error común es instalar e importar dependencias externas completas cuando solo se utiliza una pequeña parte de ellas.

Optimizar

Existen varias formas de reducir 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 Array.prototype.sort() integrado para ordenar numéricamente.
  3. Solo importa el método sortBy de lodash y 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 adecuados para reducir el tamaño del paquete (y probablemente serían los más lógicos para una aplicación real). Sin embargo, no se usan en este instructivo para fines didácticos 😈.

Las opciones 3 y 4 ayudan a mejorar el rendimiento de esta aplicación. En las siguientes secciones de este codelab, se explican estos pasos. Al igual que con cualquier tutorial de programación, siempre intenta escribir el código por tu cuenta en lugar de copiarlo y pegarlo.

Importa solo lo que necesitas

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

"lodash": "^4.7.0",

con este:

"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 la forma 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 observa el panel Red una vez más.

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

En esta aplicación, el tamaño del paquete se redujo más de 4 veces con muy poco trabajo, pero aún hay margen para mejorar.

División del código

webpack es uno de los empaquetadores 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 (así como otros recursos) que componen una aplicación web en archivos estáticos que el navegador puede leer.

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

  • Una persona responsable del código que compone nuestra ruta inicial
  • Un fragmento secundario que contiene nuestro código de clasificación

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

Comienza por quitar la importación de nivel superior para el método de ordenamiento en src/index.js:

import sortBy from "lodash.sortby";

Luego, impórtalo dentro del objeto de escucha de eventos que se activa cuando se presiona 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() forma 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 esta función y sigue la misma sintaxis establecida en la propuesta.

import() devuelve una promesa y, cuando se resuelve, se proporciona el módulo seleccionado, que se divide en un fragmento independiente. Después de que se devuelve el módulo, se usa module.default para hacer referencia a la exportación predeterminada que proporciona Lodash. La promesa se encadena con otro .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 controlar los 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. Esta debe ser una función que devuelva 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>
    `
  };
}

Supervisar

Vuelve a cargar la aplicación por última vez y observa con atención el panel Network. Solo se descarga un pequeño paquete inicial en cuanto se carga la app.

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

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

El panel de red muestra un paquete de JavaScript de 2.7 KB seguido de un paquete de JavaScript de 13.9 KB.

Observa cómo los números siguen ordenándose.

Conclusión

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

IU de carga diferida

Cuando se cargan de forma diferida módulos específicos de código, es importante tener en cuenta cómo sería la experiencia para los usuarios con conexiones de red más débiles. Dividir y cargar una parte 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 se recomienda mostrar algún tipo de indicador de carga.

Carga diferida de módulos de nodos de terceros

No siempre es el mejor enfoque cargar de forma diferida las dependencias de terceros 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 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 en comparación con el uso de importaciones dinámicas en el medio de tu aplicación.

Si bien es útil comprender cómo funcionan las importaciones dinámicas, siempre usa el método recomendado por 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 incluso antes. webpack admite ambas sugerencias a través del uso de comentarios mágicos en las declaraciones de importación. Esto se explica con más detalle en la guía Precarga de fragmentos críticos.

Carga diferida de más que código

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