Reduce y comprime las cargas útiles de red con gzip

En este codelab, se explora cómo la reducción y la compresión del paquete de JavaScript para la siguiente aplicación mejoran el rendimiento de la página, ya que reducen el tamaño de la solicitud de la app.

Captura de pantalla de la app

Medir

Antes de comenzar a agregar optimizaciones, siempre es conveniente analizar primero el estado actual de la aplicación.

  • Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completapantalla completa.

Esta app, que también se analizó en el codelab "Quita el código que no se usa", te permite votar por tu gatito favorito. 🐈

Ahora, observa el tamaño de esta aplicación:

  1. Presiona "Control + Mayúsculas + J" (o "Comando + Opción + J" en Mac) para abrir DevTools.
  2. Haga clic en la pestaña Red.
  3. Selecciona la casilla de verificación Inhabilitar caché.
  4. Vuelve a cargar la app.

Tamaño del paquete original en el panel de red

Aunque se avanzó mucho en el codelab "Quita el código no utilizado" para reducir el tamaño del paquete, 225 KB sigue siendo bastante grande.

Reducción

Considera el siguiente bloque de código.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Si esta función se guarda en un archivo propio, el tamaño del archivo es de alrededor de 112 B (bytes).

Si se quitan todos los espacios en blanco, el código resultante se verá de la siguiente manera:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

Ahora, el tamaño del archivo será de alrededor de 83 B. Si se altera aún más cuando se reduce la longitud del nombre de la variable y se modifican algunas expresiones, es posible que el código final se vea de la siguiente manera:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

El tamaño del archivo ahora alcanza los 62 B.

Con cada paso, el código es cada vez más difícil de leer. Sin embargo, el motor de JavaScript del navegador los interpreta de la misma manera. El beneficio de ofuscar el código de esta manera puede ayudar a lograr tamaños de archivo más pequeños. 112,000 millones de bytes no era mucho al principio, pero aún así se logró una reducción del 50% en el tamaño.

En esta aplicación, se usa la versión 4 de webpack como un agrupador de módulos. La versión específica se puede ver en package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

La versión 4 ya reduce el paquete de forma predeterminada durante el modo de producción. Usa TerserWebpackPlugin, un complemento para Terser. Terser es una herramienta popular que se usa para comprimir el código JavaScript.

Para tener una idea de cómo se ve el código reducido, haz clic en main.bundle.js mientras estás en el panel Red de DevTools. Ahora, haz clic en la pestaña Response.

Respuesta reducida

El código en su forma final, reducida y moldeada, se muestra en el cuerpo de la respuesta. Para averiguar qué tan grande podría haber sido el paquete si no se recortó, abre webpack.config.js y actualiza la configuración de mode.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Vuelve a cargar la aplicación y vuelve a observar el tamaño del paquete a través del panel Network de DevTools.

Tamaño del paquete de 767 KB

Esa es una gran diferencia. 😅

Asegúrate de revertir los cambios aquí antes de continuar.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

La inclusión de un proceso para reducir el código en tu aplicación depende de las herramientas que uses:

  • Si se usa webpack v4 o una versión posterior, no es necesario realizar ningún trabajo adicional, ya que el código se reduce de forma predeterminada en el modo de producción. 👍
  • Si se usa una versión anterior de webpack, instala y, luego, incluye TerserWebpackPlugin en el proceso de compilación de webpack. En la documentación, se explica esto en detalle.
  • También existen otros complementos de reducción que se pueden usar en su lugar, como BabelMinifyWebpackPlugin y ClosureCompilerPlugin.
  • Si no se usa un empaquetador de módulos, usa Terser como herramienta de CLI o inclúyela directamente como dependencia.

Compresión

Aunque el término "compresión" a veces se usa de manera flexible para explicar cómo se reduce el código durante el proceso de reducción, en realidad no se comprime en el sentido literal.

Por lo general, la compresión se refiere al código que se modificó con un algoritmo de compresión de datos. A diferencia de la reducción, que proporciona un código perfectamente válido, el código comprimido debe descomprimirse antes de usarse.

Con cada solicitud y respuesta HTTP, los navegadores y los servidores web pueden agregar encabezados para incluir información adicional sobre el recurso que se recupera o recibe. Esto se puede ver en la pestaña Headers del panel de red de Herramientas para desarrolladores, en el que se muestran tres tipos:

  • General representa encabezados generales relevantes para toda la interacción de solicitud-respuesta.
  • En Encabezados de respuesta, se muestra una lista de encabezados específicos de la respuesta real del servidor.
  • En Encabezados de solicitud, se muestra una lista de encabezados que el cliente adjuntó a la solicitud.

Observa el encabezado accept-encoding en Request Headers.

Encabezado de codificación de aceptación

El navegador usa accept-encoding para especificar qué formatos de codificación de contenido, o algoritmos de compresión admite. Existen muchos algoritmos de compresión de texto, pero solo hay tres que se admiten aquí para la compresión (y descompresión) de solicitudes de red HTTP:

  • Gzip (gzip): Es el formato de compresión más usado para las interacciones entre servidores y clientes. Se basa en el algoritmo Deflate y es compatible con todos los navegadores actuales.
  • Deflate (deflate): No se usa comúnmente.
  • Brotli (br): Es un algoritmo de compresión más reciente que busca mejorar aún más las proporciones de compresión, lo que puede generar que la página se cargue aún más rápido. Es compatible con las versiones más recientes de la mayoría de los navegadores.

La aplicación de ejemplo de este instructivo es idéntica a la app que se completó en el codelab "Quita el código no utilizado", excepto por el hecho de que ahora se usa Express como framework de servidor. En las próximas secciones, se explorará la compresión estática y dinámica.

Compresión dinámica

La compresión dinámica implica comprimir los recursos sobre la marcha a medida que el navegador los solicita.

Ventajas

  • No es necesario crear ni actualizar versiones comprimidas guardadas de los recursos.
  • La compresión sobre la marcha funciona especialmente bien para las páginas web que se generan de forma dinámica.

Desventajas

  • La compresión de archivos en niveles más altos para lograr mejores relaciones de compresión tarda más. Esto puede provocar un impacto en el rendimiento, ya que el usuario espera a que los recursos se compriman antes de que el servidor los envíe.

Compresión dinámica con Node/Express

El archivo server.js es responsable de configurar el servidor de nodos que aloja la aplicación.

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

Actualmente, todo lo que hace es importar express y usar el middleware express.static para cargar todos los archivos HTML, JS y CSS estáticos en el directorio public/ (y webpack crea esos archivos con cada compilación).

Para asegurarte de que todos los activos se compriman cada vez que se soliciten, se puede usar la biblioteca de middleware de compresión. Comienza por agregarlo como devDependency en package.json:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

Y, luego, impórtalo en el archivo del servidor, server.js:

const express = require('express');
const compression = require('compression');

Y agrégalo como un middleware antes de que se monte express.static:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Ahora, vuelve a cargar la app y observa el tamaño del paquete en el panel Red.

Tamaño del paquete con compresión dinámica

De 225 KB a 61.6 KB. En el Response Headers ahora, un encabezado content-encoding muestra que el servidor está enviando este archivo codificado con gzip.

Encabezado de codificación de contenido

Compresión estática

La idea detrás de la compresión estática es tener recursos comprimidos y guardados con anticipación.

Ventajas

  • La latencia debido a los altos niveles de compresión ya no es un problema. No es necesario que se realice ninguna acción sobre la marcha para comprimir los archivos, ya que ahora se pueden recuperar directamente.

Desventajas

  • Los recursos deben comprimirse con cada compilación. Los tiempos de compilación pueden aumentar de manera significativa si se usan niveles altos de compresión.

Compresión estática con Node/Express y webpack

Dado que la compresión estática implica comprimir archivos con anticipación, la configuración del paquete web se puede modificar para comprimir los elementos como parte del paso de compilación. Para ello, puedes usar CompressionPlugin.

Comienza por agregarlo como devDependency en package.json:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Al igual que cualquier otro complemento de webpack, impórtalo en el archivo de configuración, webpack.config.js:.

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

Y, además, inclúyela en el array plugins:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

De forma predeterminada, el complemento comprime los archivos de compilación con gzip. Consulta la documentación y obtén información sobre cómo agregar opciones para usar un algoritmo diferente o incluir o excluir ciertos archivos.

Cuando la app se vuelve a cargar y compilar, se crea una versión comprimida del paquete principal. Abre Glitch Console para ver el contenido del directorio public/ final que entrega el servidor de Node.

  • Haz clic en el botón Herramientas.
  • Haz clic en el botón Consola.
  • En la consola, ejecuta los siguientes comandos para cambiar al directorio public y ver todos sus archivos:
cd public
ls

Archivos de salida finales en el directorio público

La versión con compresión gzip del paquete, main.bundle.js.gz, ahora también se guarda aquí. CompressionPlugin también comprime index.html de forma predeterminada.

Lo siguiente que se debe hacer es decirle al servidor que envíe estos archivos comprimidos con GZIP cada vez que se soliciten sus versiones originales de JS. Para ello, define una ruta nueva en server.js antes de que se entreguen los archivos con express.static.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get se usa para indicarle al servidor cómo responder a una solicitud GET para un extremo específico. Luego, se usa una función de devolución de llamada para definir cómo controlar esta solicitud. La ruta funciona de la siguiente manera:

  • Especificar '*.js' como primer argumento significa que esto funciona para cada extremo que se activa para recuperar un archivo JS.
  • Dentro de la devolución de llamada, se adjunta .gz a la URL de la solicitud y el encabezado de respuesta Content-Encoding se establece en gzip.
  • Por último, next() se asegura de que la secuencia continúe con cualquier devolución de llamada que pueda ser la siguiente.

Una vez que se vuelva a cargar la app, vuelve a revisar el panel Network.

Reducción del tamaño del paquete con compresión estática

Al igual que antes, se produce una reducción significativa en el tamaño del paquete.

Conclusión

En este codelab, se analizó el proceso de reducción y compresión del código fuente. Ambas técnicas se están convirtiendo en una opción predeterminada en muchas de las herramientas disponibles en la actualidad, por lo que es importante averiguar si tu cadena de herramientas ya las admite o si debes comenzar a aplicar ambos procesos por tu cuenta.