Service workers y el modelo de shell de la aplicación

Una función de arquitectura común de las aplicaciones web de una sola página (SPA) es un conjunto mínimo de HTML, CSS y JavaScript necesario para potenciar la funcionalidad global de una aplicación. En la práctica, esto suele ser el encabezado, la navegación y otros elementos comunes de la interfaz de usuario que persisten en todas las páginas. Cuando un service worker almacena previamente en caché el código HTML mínimo de la IU y los elementos dependientes, lo llamamos shell de la aplicación.

Diagrama de la shell de una aplicación. Es una captura de pantalla de una página web con un encabezado en la parte superior y un área de contenido en la parte inferior. El encabezado está etiquetado como "Shell de la aplicación", mientras que la parte inferior está etiquetada como "Contenido".

El shell de aplicación desempeña un papel importante en el rendimiento percibido de una aplicación web. Es lo primero que se carga y, por lo tanto, también es lo primero que los usuarios ven mientras esperan a que el contenido se propague en la interfaz de usuario.

Si bien la shell de la aplicación se carga rápidamente (siempre y cuando la red esté disponible y al menos un poco rápida), un service worker que almacena previamente en caché el shell de la aplicación y sus recursos asociados le brinda al modelo de shell de la aplicación estos beneficios adicionales:

  • Rendimiento confiable y coherente en las visitas repetidas. En la primera visita a una app sin un service worker instalado, el lenguaje de marcado de la app y sus elementos asociados deben cargarse desde la red antes de que el service worker pueda colocarlos en su caché. Sin embargo, las visitas repetidas extraerán el shell de la aplicación de la caché, lo que significa que la carga y la representación serán instantáneas.
  • Acceso confiable a funciones sin conexión. A veces, el acceso a Internet es irregular o no está disponible por completo, y el temido "no podemos encontrar ese sitio web" la pantalla en posición horizontal. El modelo de shell de aplicación aborda esto respondiendo a cualquier solicitud de navegación con el lenguaje de marcado de shell de aplicación desde la caché. Incluso si un usuario visita una URL en tu app web que nunca antes visitó, el shell de la aplicación se entregará desde la caché y se podrá completar con contenido útil.

Cuándo se debe usar el modelo de shell de la aplicación

Una shell de aplicación tiene más sentido cuando tienes elementos comunes de la interfaz de usuario que no cambian de una ruta a otra, pero el contenido sí. Es probable que la mayoría de las SPA ya usen lo que ya es eficazmente un modelo de shell de aplicación.

Si esto describe tu proyecto y quieres agregar un service worker para mejorar su confiabilidad y rendimiento, el shell de la aplicación debe cumplir con lo siguiente:

  • Carga rápida.
  • Usa recursos estáticos de una instancia Cache.
  • Incluye elementos comunes de la interfaz, como un encabezado y una barra lateral, separados del contenido de la página.
  • Recupera y muestra contenido específico de la página.
  • Si corresponde, puedes almacenar en caché contenido dinámico para visualización sin conexión.

El shell de la aplicación carga contenido específico de la página de forma dinámica a través de APIs o contenido empaquetado en JavaScript. También debería actualizarse automáticamente, ya que si el lenguaje de marcado de la shell de la aplicación cambia, la actualización de un service worker debe recoger la nueva shell de la aplicación y almacenarla en caché automáticamente.

Cómo compilar el shell de la aplicación

El shell de la aplicación debe existir independientemente del contenido, pero debe proporcionar una base para que el contenido se complete en él. Idealmente, debe ser lo más delgada posible, pero incluir suficiente contenido significativo en la descarga inicial para que el usuario comprenda que la experiencia se carga rápidamente.

El equilibrio adecuado depende de tu app. La shell de la aplicación para la app de Trained To Thrill de Jake Archibald incluye un encabezado con un botón de actualización para incorporar contenido nuevo de Flickr.

Una captura de pantalla de la app web de Trained to Thrill en dos estados diferentes. A la izquierda, solo es visible la shell de la aplicación almacenada en caché, sin contenido propagado. A la derecha, el contenido (algunas imágenes de algunos trenes) se carga de forma dinámica en el área de contenido de la shell de la aplicación.

El lenguaje de marcado de la shell de la aplicación variará de un proyecto a otro, pero aquí hay un ejemplo de un archivo index.html que proporciona el código estándar de la aplicación:

​​<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Application Shell Example
    </title>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="styles/global.css">
  </head>
  <body>
    <header class="header">
      <!-- Application header -->
      <h1 class="header__title">Application Shell Example</h1>
    </header>

    <nav class="nav">
      <!-- Navigation items -->
    </nav>

    <main id="app">
      <!-- Where the application content populates -->
    </main>

    <div class="loader">
      <!-- Spinner/content placeholders -->
    </div>

    <!-- Critical application shell logic -->
    <script src="app.js"></script>

    <!-- Service worker registration script -->
    <script>
      if ('serviceWorker' in navigator) {
        // Register a service worker after the load event
        window.addEventListener('load', () => {
          navigator.serviceWorker.register('/sw.js');
        });
      }
    </script>
  </body>
</html>

Sin importar cómo construyas una shell de aplicación para tu proyecto, esta debe tener las siguientes características:

  • El código HTML debe tener áreas claramente aisladas para los elementos individuales de la interfaz de usuario. En el ejemplo anterior, esto incluye el encabezado, la navegación, el área de contenido principal y el espacio para un ícono giratorio de carga de la aplicación que solo aparece mientras se carga el contenido.
  • El JavaScript y el CSS iniciales que se cargan para el shell de la aplicación deben ser mínimos y solo deben relacionarse con la funcionalidad del shell de la aplicación propiamente dicho y no con el contenido. Esto garantiza que la aplicación represente su shell lo más rápido posible y minimiza el trabajo del subproceso principal hasta que aparezca el contenido.
  • Una secuencia de comandos intercalada que registra un service worker.

Una vez que se compila el shell de la aplicación, puedes compilar un service worker para almacenar en caché tanto este como sus recursos.

Cómo almacenar en caché la shell de la aplicación

El service worker debe almacenar en caché previamente el shell de la aplicación y sus recursos necesarios al momento de la instalación. Si suponemos una shell de aplicación como el ejemplo anterior, veamos cómo se puede lograr esto en un ejemplo básico de Workbox con workbox-build:

// build-sw.js
import {generateSW} from 'workbox-build';

// Where the generated service worker will be written to:
const swDest = './dist/sw.js';

generateSW({
  swDest,
  globDirectory: './dist',
  globPatterns: [
    // The necessary CSS and JS for the app shell
    '**/*.js',
    '**/*.css',
    // The app shell itself
    'shell.html'
  ],
  // All navigations for URLs not precached will use this HTML
  navigateFallback: 'shell.html'
}).then(({count, size}) => {
  console.log(`Generated ${swDest}, which precaches ${count} assets totaling ${size} bytes.`);
});

Esta configuración almacenada en build-sw.js importa el CSS y JavaScript de la app, incluido el archivo de lenguaje de marcado de la shell de la aplicación que se encuentra en shell.html. La secuencia de comandos se ejecuta con Node de la siguiente manera:

node build-sw.js

El service worker generado se escribe en ./dist/sw.js y registrará el siguiente mensaje cuando haya finalizado:

Generated ./dist/sw.js, which precaches 5 assets totaling 44375 bytes.

Cuando se carga la página, el service worker almacena previamente en caché el lenguaje de marcado de la shell de la aplicación y sus dependencias:

Captura de pantalla del panel de red en Herramientas para desarrolladores de Chrome que muestra una lista de recursos descargados de la red. Los recursos que el service worker almacena previamente en caché se distinguen de los demás recursos con un engranaje a la izquierda en la fila. Durante la instalación, el service worker almacena previamente en caché varios archivos JavaScript y CSS.
En el momento de la instalación, el service worker almacena previamente en caché las dependencias de shell de la aplicación. Las solicitudes de almacenamiento previo en caché son las dos últimas filas y el ícono de ajustes junto a la solicitud indica que el service worker se encargó de la solicitud.

El almacenamiento previo en caché del HTML, CSS y JavaScript de la shell de tu aplicación es posible en casi cualquier flujo de trabajo, incluidos los proyectos que usan agrupadores. A medida que avances por la documentación, aprenderás a usar Workbox directamente para configurar tu cadena de herramientas y compilar un service worker que funcione mejor para tu proyecto, independientemente de si se trata de una SPA.

Conclusión

La combinación del modelo de shell de aplicación con un service worker es excelente para el almacenamiento en caché sin conexión, sobre todo si combinas su funcionalidad de almacenamiento previo en caché con una estrategia centrada en la red y recurre a la caché para el lenguaje de marcado o las respuestas de la API. El resultado es una experiencia rápida y confiable que renderizará instantáneamente el shell de tu aplicación en visitas repetidas, incluso en condiciones sin conexión.