Tu primera app web progresiva

Última actualización: 30/04/2019

¿Qué hace que una aplicación web sea una aplicación web progresiva?

Las apps web progresivas ofrecen una experiencia instalable similar a la de las apps en computadoras y dispositivos móviles, que se compila y entrega directamente a través de la Web. Son aplicaciones web que son rápidas y confiables. Y lo más importante es que son aplicaciones web que funcionan en cualquier navegador. Si estás compilando una aplicación web hoy, te encuentras en el camino hacia la compilación de una app web progresiva.

Rapidez y confiabilidad

Cada experiencia web debe ser rápida, sobre todo para las apps web progresivas. Rápido se refiere al tiempo que tarda el contenido en aparecer en pantalla y brindar una experiencia interactiva.

Además, debe ser confiable y rápido. Es difícil destacar lo mejor que es el rendimiento confiable. Piénsalo así: la primera carga de una app nativa es frustrante. Está restringido por una tienda de aplicaciones y una gran descarga, pero una vez que se instala la app, ese costo se amortiza desde el inicio de la app y ninguna de ellas comienza con un retraso variable. El inicio de cada solicitud es tan rápido como el último, sin variaciones. Una app web progresiva debe ofrecer este rendimiento confiable que los usuarios esperan de cualquier experiencia instalada.

Instalable

Las apps web progresivas pueden ejecutarse en una pestaña del navegador, pero también se pueden instalar. Para agregar un sitio web a favoritos, solo tienes que agregar un acceso directo. Sin embargo, una app web progresiva (AWP) instalada tiene el mismo aspecto que las demás apps instaladas. Se inicia desde el mismo lugar que otras apps. Puedes controlar la experiencia de inicio, como una pantalla de presentación personalizada, íconos y mucho más. Se ejecuta como una aplicación en una ventana de la aplicación sin una barra de direcciones ni ninguna otra IU del navegador. Al igual que todas las demás apps instaladas, es una app de nivel superior en el selector de tareas.

Recuerda que es fundamental que una AWP instalable sea rápida y confiable. Los usuarios que instalan una AWP esperan que sus aplicaciones funcionen, sin importar el tipo de conexión de red que usen. Es la expectativa mínima que deben cumplir todas las apps instaladas.

Computadoras y dispositivos móviles

Las AWP funcionan con técnicas de diseño responsivos que funcionan tanto en dispositivos móviles como en computadoras de escritorio, con un único código base entre plataformas. Si consideras escribir una app nativa, consulta los beneficios que ofrece una AWP.

Qué compilarás

En este codelab, compilarás una app web de clima usando técnicas de AWP. Tu app hará lo siguiente:

  • Usa un diseño responsivo, por lo que funciona en computadoras de escritorio o dispositivos móviles.
  • Usa un service worker para almacenar en caché con anticipación los recursos de la app (HTML, CSS, JavaScript, imágenes) necesarios para ejecutar y almacenar en caché los datos del tiempo de ejecución a fin de mejorar el rendimiento. Sé rápido.
  • Se pueden instalar con un manifiesto de app web y el evento beforeinstallprompt para notificar al usuario que se puede instalar.

Qué aprenderás

  • Cómo crear y agregar un manifiesto de aplicación web
  • Cómo brindar una experiencia simple sin conexión
  • Cómo brindar una experiencia completa sin conexión
  • Cómo instalar tu app

Este codelab se enfoca en las apps web progresivas. Los conceptos y los bloques de código no relevantes se pasan por alto y se proporcionan para que simplemente los copie y pegue.

Requisitos

  • Una versión reciente de Chrome (74 o posterior). Las AWP funcionan en todos los navegadores, pero usaremos algunas funciones de las herramientas para desarrolladores de Chrome a fin de comprender mejor lo que sucede en el navegador y usarlas para probar la experiencia de instalación.
  • Conocimientos sobre HTML, CSS, JavaScript y las Herramientas para desarrolladores de Chrome

Obtén una clave para la API de Dark Sky

Nuestros datos meteorológicos provienen de la API de Dark Sky. Para usarla, debes solicitar una clave de API. Es fácil de usar y gratis para proyectos no comerciales.

Regístrate para obtener una clave de API

Verifique que su clave de API funcione correctamente

Para probar que tu clave de API funcione correctamente, realiza una solicitud HTTP a la API de DarkSky. Actualiza la URL que aparece a continuación para reemplazar DARKSKY_API_KEY por tu clave de API. Si todo funciona, deberías ver el pronóstico del tiempo más reciente para Nueva York.

https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319

Obtén el código

Todo lo que necesitas con relación a este proyecto se encuentra en un repositorio de Git. Para comenzar, toma el código y ábrelo en tu entorno de desarrollo favorito. Para este codelab, te recomendamos que uses Glitch.

Recomendación: Usa Glitch para importar el repositorio.

El método recomendado para trabajar en este codelab es usar Glitch.

  1. Abre una nueva pestaña del navegador y ve a https://glitch.com.
  2. Si no tienes una cuenta, deberás registrarte.
  3. Haz clic en New Project y, luego, en Clone from Git Repo.
  4. Clona https://github.com/googlecodelabs/your-first-pwapp.git y haz clic en Aceptar.
  5. Una vez que se haya cargado el repositorio, edita el archivo .env y actualízalo con tu clave de la API de DarkSky.
  6. Haga clic en el botón Show y, luego, elija In a New Window, para ver la AWP en acción.

Alternativa: Descarga el código y trabaja de forma local

Si quieres descargar el código y trabajar de forma local, debes tener una versión reciente de Node.js y una configuración de editor de código lista para usarse.

Download source code

  1. Descomprime el archivo zip descargado.
  2. Ejecuta npm install a fin de instalar las dependencias necesarias para ejecutar el servidor.
  3. Edita server.js y configura tu clave de API de DarkSky.
  4. Ejecuta node server.js para iniciar el servidor en el puerto 8000.
  5. Abra una pestaña del navegador en http://localhost:8000

¿Cuál es nuestro punto de partida?

Nuestro punto de partida es una app básica de clima, diseñada para este codelab. El código se simplificó para mostrar los conceptos en este codelab y tiene poco control de errores. Si decides reutilizar algún código en una app de producción, asegúrate de controlar los errores y probar todo el código por completo.

Algunas cosas que puedes probar...

  1. Agrega una ciudad nueva con el botón azul + ubicado en la esquina inferior derecha.
  2. Actualiza los datos con el botón de actualización que se encuentra en la esquina superior derecha.
  3. Borra una ciudad mediante la x en la parte superior derecha de cada tarjeta de ciudad.
  4. Usa la barra de herramientas de activación de Chrome DevTools para ver cómo funciona en computadoras de escritorio y dispositivos móviles.
  5. Con el panel Red de las Herramientas para desarrolladores de Chrome, puedes ver qué sucede cuando te desconectas.
  6. Mediante el panel Red de las Herramientas para desarrolladores de Chrome, consulta lo que sucede cuando la red se limita a 3G lento.
  7. Para agregar un retraso al servidor de previsión, cambie el valor de FORECAST_DELAY en server.js

Auditoría con Lighthouse

Lighthouse es una herramienta fácil de usar que ayuda a mejorar la calidad de sus sitios y páginas. Lighthouse ejecuta auditorías de rendimiento, accesibilidad, apps web progresivas y mucho más. Cada auditoría tiene un documento de referencia que explica por qué la auditoría es importante y cómo solucionar problemas.

Usaremos Lighthouse para auditar nuestra app de clima y verificar los cambios que hicimos.

Ejecutemos Lighthouse

  1. Abra su proyecto en una pestaña nueva.
  2. Abra las Herramientas para desarrolladores de Chrome y cambie al panel Audits. Deje habilitados todos los tipos de auditoría.
  3. Haga clic en Ejecutar auditorías. Después de un tiempo, Lighthouse te enviará un informe en la página.

Auditoría de apps web progresivas

Nos enfocaremos en los resultados de la auditoría de las apps web progresivas.

Además, hay muchos rojos para enfocarse en lo siguiente:

  • ❗ERROR:La página actual no responde con un código de estado HTTP 200 cuando no hay conexión.
  • ❗ERROR: start_url no responde con un código de estado HTTP 200 cuando no hay conexión.
  • ❗fail:No registra un service worker que controle la página y start_url.
  • ❗ERROR: El manifiesto de la app web no cumple con los requisitos de instalación.
  • ❗ERROR:No se configuró para una pantalla de presentación personalizada.
  • ❗ERROR: No establece un color de tema para la barra de direcciones.

¡Comencemos y soluciona algunos de estos problemas!

Al final de esta sección, nuestra app de clima aprobará las siguientes auditorías:

  • El manifiesto de la app web no cumple con los requisitos de instalación.
  • No se configuró para una pantalla de presentación personalizada.
  • No establece un color de tema para la barra de direcciones.

Cómo crear el manifiesto de la app web

El manifiesto de aplicaciones web es un archivo JSON simple que le permite a usted, el desarrollador, controlar cómo se muestra su aplicación al usuario.

Con el manifiesto de aplicación web, su aplicación web puede hacer lo siguiente:

  • Indica al navegador que quieres que tu app se abra en una ventana independiente (display).
  • Define qué página se abre cuando se inicia la app por primera vez (start_url).
  • Define cómo debería verse la app en el conector o el selector de aplicaciones (short_name, icons).
  • Crea una pantalla de presentación (name, icons, colors).
  • Dile al navegador que abra la ventana en modo horizontal o vertical (orientation).
  • Y muchos más.

Crea un archivo llamado public/manifest.json en tu proyecto. Copia y pega el siguiente contenido:

public/manifest.json

{
  "name": "Weather",
  "short_name": "Weather",
  "icons": [{
    "src": "/images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

El manifiesto admite una variedad de íconos diseñados para diferentes tamaños de pantalla. En este codelab, incluimos algunos más para la integración de iOS.

A continuación, debemos informarle al navegador sobre nuestro manifiesto agregando un <link rel="manifest"... a cada página de nuestra app. Agrega la siguiente línea al elemento <head> en tu archivo index.html.

public/index.html.

<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">

Desvío de las Herramientas para desarrolladores

Las Herramientas para desarrolladores proporcionan una manera rápida y fácil de verificar tu archivo manifest.json. Abre el panel Manifest en el panel Application. Si agregaste correctamente la información del manifiesto, podrás ver que se analiza y muestra en un formato legible en este panel.

Agrega íconos y metaetiquetas de iOS

Safari en iOS no es compatible con el manifiesto de la aplicación web (aún), por lo que deberás agregar las etiquetas meta tradicionales a <head> de tu archivo index.html:

public/index.html.

<!-- CODELAB: Add iOS meta tags and icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png">

Opción adicional: Correcciones fáciles de Lighthouse

En nuestra auditoría de Lighthouse, se destacaban algunas soluciones fáciles de corregir, así que cuidémoslos cuando estemos.

Configura la metadescripción

En la auditoría de SEO, Lighthouse observó que el documento no tiene una metadescripción." Las descripciones se pueden mostrar en los resultados de la Búsqueda de Google. Las descripciones únicas y de alta calidad pueden hacer que los resultados sean más relevantes para los usuarios de búsqueda y pueden aumentar el tráfico de búsqueda.

Para agregar una descripción, agrega la siguiente etiqueta meta al <head> de tu documento:

public/index.html.

<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">

Cómo establecer el color del tema para la barra de direcciones

En la auditoría de la AWP, Lighthouse observó que la aplicación No establece un color de tema para la barra de direcciones. Si agregas temas a la barra de direcciones del navegador para que coincida con los colores de tu marca, los usuarios disfrutarán de una experiencia más envolvente.

Para configurar el color del tema en dispositivos móviles, agrega la siguiente etiqueta meta en el <head> de tu documento:

public/index.html.

<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />

Verifica los cambios con Lighthouse

Vuelve a ejecutar Lighthouse (haz clic en el signo + en la esquina superior izquierda del panel Auditos) y verifica los cambios.

Auditoría de SEO

  • ✅ APROBADA: El documento tiene una metadescripción.

Auditoría de apps web progresivas

  • ❗ERROR:La página actual no responde con un código de estado HTTP 200 cuando no hay conexión.
  • ❗ERROR: start_url no responde con un código de estado HTTP 200 cuando no hay conexión.
  • ❗fail:No registra un service worker que controle la página y start_url.
  • ✅ APROBADA: El manifiesto de la app web cumple con los requisitos de instalación.
  • ✅ APROBADA: Configurada para una pantalla de presentación personalizada.
  • ✅ APROBADA: Establece un color de tema para la barra de direcciones.

Se espera que los usuarios que instalaron las aplicaciones siempre tengan una experiencia de referencia si no tienen conexión. Por eso, es fundamental que las aplicaciones web instalables nunca muestren el juego de dinosaurios sin conexión de Chrome. La experiencia sin conexión puede ir desde una página sin conexión simple a una experiencia de solo lectura con datos almacenados previamente en caché hasta una experiencia sin conexión completamente funcional que se sincroniza automáticamente cuando se restablece la conexión de red.

En esta sección, agregaremos una página sin conexión simple a nuestra app del clima. Si el usuario intenta cargar la app sin conexión, se mostrará nuestra página personalizada, en lugar de la página típica sin conexión que muestra el navegador. Al final de esta sección, nuestra app de clima aprobará las siguientes auditorías:

  • La página actual no responde con un código de estado HTTP 200 cuando no hay conexión.
  • start_url no responde con un código de estado HTTP 200 cuando no hay conexión.
  • No registra un service worker que controle la página y start_url.

En la próxima sección, reemplazaremos nuestra página personalizada sin conexión por una experiencia sin conexión completa. Esto mejorará la experiencia sin conexión, pero, lo que es más importante, mejorará significativamente nuestro rendimiento, ya que la mayoría de nuestros recursos (HTML, CSS y JavaScript) se almacenarán y entregarán a nivel local, lo que elimina la red como un cuello de botella potencial.

Trabajadores de servicio al rescate

Si no conoces los service workers, puedes obtener información básica sobre lo que pueden hacer, cómo funciona su ciclo de vida y mucho más en Introducción a los service workers.

Las funciones que se proporcionan mediante los service workers deben considerarse como mejoras progresivas y solo deben agregarse si son compatibles con el navegador. Por ejemplo, con los service workers puedes almacenar en caché el Shell de la app y los datos de tu app para que estén disponibles incluso cuando la red no esté disponible. Cuando no se admitan service workers, no se llamará al código sin conexión y el usuario obtendrá una experiencia básica. El uso de la detección de funciones para proporcionar una mejora progresiva tiene poca sobrecarga y no fallará en navegadores más antiguos que no admitan esa función.

Registra el service worker

El primer paso es registrar el service worker. Agrega el siguiente código a tu archivo index.html:

public/index.html.

// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}

Este código verifica si la API del service worker está disponible. Si está disponible, se registra el service worker de /service-worker.js una vez que se carga la página.

Ten en cuenta que el service worker se entrega desde el directorio raíz, no desde el directorio /scripts/. Esta es la forma más fácil de configurar el scope de tu service worker. El scope del service worker determina qué archivos controla el service worker, es decir, desde qué ruta de acceso interceptará las solicitudes el service worker. El valor predeterminado scope es la ubicación del archivo del service worker y se extiende a todos los directorios que aparecen a continuación. Por lo tanto, si service-worker.js se encuentra en el directorio raíz, el service worker controlará las solicitudes de todas las páginas web de este dominio.

Prealmacenar la página sin conexión

En primer lugar, debemos indicarle al service worker qué contenido debe almacenar en caché. Ya creamos una página sin conexión simple (public/offline.html) que mostraremos en cualquier momento sin conexión de red.

En tu service-worker.js, agrega '/offline.html', al array FILES_TO_CACHE. El resultado final debería verse de la siguiente manera:

public/service-worker.js

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/offline.html',
];

A continuación, debemos agregar el siguiente código al evento install para indicarle al service worker que guarde en caché la página sin conexión:

public/service-worker.js

// CODELAB: Precache static resources here.
evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page');
      return cache.addAll(FILES_TO_CACHE);
    })
);

Nuestro evento install ahora abre la caché con caches.open() y proporciona un nombre de caché. Proporcionar un nombre de caché nos permite controlar versiones de archivos o separar datos de los recursos almacenados en caché a fin de que podamos actualizar uno con facilidad sin afectar al otro.

Una vez que se abre la caché, podremos llamar a cache.addAll(), que toma una lista de URL, las obtiene del servidor y agrega la respuesta a la caché. Ten en cuenta que cache.addAll() falla si falla alguna de las solicitudes individuales. Es decir, se garantiza que, si el paso de instalación se realiza correctamente, la caché tendrá un estado coherente. Sin embargo, si falla por algún motivo, se volverá a intentar automáticamente la próxima vez que se inicie el service worker.

Desvío de las Herramientas para desarrolladores

Veamos cómo puedes usar las herramientas para desarrolladores a fin de comprender y depurar los service workers. Antes de volver a cargar la página, abre DevTools y ve al panel Service Workers del panel Application. Debe tener el siguiente aspecto:

Cuando veas una página en blanco como esta, significa que la página actualmente abierta no tiene ningún service worker registrado.

Ahora, vuelve a cargar la página. El panel Service Workers ahora debería tener el siguiente aspecto:

Si ves información como esta, significa que la página tiene un service worker en ejecución.

Junto a la etiqueta de estado, hay un número (en este caso, 34251). Mantente atento a ese número mientras trabajas con service workers. Es una manera fácil de saber si se actualizó el service worker.

Cómo borrar páginas antiguas sin conexión

Usaremos el evento activate para limpiar los datos antiguos de la caché. Este código garantiza que tu service worker actualice su caché cada vez que cambie alguno de los archivos de shell de la app. Para que esto funcione, debes aumentar la variable CACHE_NAME en la parte superior del archivo de service worker.

Agrega el siguiente código a tu evento activate:

public/service-worker.js

// CODELAB: Remove previous cached data from disk.
evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (key !== CACHE_NAME) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
);

Desvío de las Herramientas para desarrolladores

Con el panel de service workers abierto, actualiza la página. Verás que el nuevo service worker instalado y el número de estado se incrementa.

El service worker actualizado toma el control de inmediato porque el evento install termina con self.skipWaiting() y el evento activate finaliza con self.clients.claim(). Sin ellas, el service worker anterior seguirá controlando la página, siempre y cuando haya una pestaña abierta en la página.

Cómo controlar las solicitudes de red con errores

Por último, debemos controlar los eventos fetch. Utilizaremos una estrategia de Red que recurre a la caché. El service worker primero intenta recuperar el recurso de la red. Si eso falla, el service worker muestra la página sin conexión de la caché.

public/service-worker.js

// CODELAB: Add fetch event handler here.
if (evt.request.mode !== 'navigate') {
  // Not a page navigation, bail.
  return;
}
evt.respondWith(
    fetch(evt.request)
        .catch(() => {
          return caches.open(CACHE_NAME)
              .then((cache) => {
                return cache.match('offline.html');
              });
        })
);

El controlador fetch solo necesita controlar las navegaciones de páginas, por lo que otras solicitudes se pueden descartar del controlador y el navegador puede manejarlas normalmente. Sin embargo, si la solicitud .mode es navigate, usa fetch para intentar obtener el elemento de la red. Si falla, el controlador catch abrirá la caché con caches.open(CACHE_NAME) y utilizará cache.match('offline.html') para obtener la página sin conexión almacenada previamente en caché. Luego, el resultado se devuelve al navegador mediante evt.respondWith().

Desvío de las Herramientas para desarrolladores

Veamos si podemos asegurarnos de que todo funciona según lo esperado. Con el panel Service Workers abierto, actualiza la página. Verás que el nuevo service worker instalado y el número de estado se incrementa.

También podemos revisar qué se almacenó en caché. Ve al panel Cache Storage en el panel Application de DevTools. Haz clic con el botón derecho en Almacenamiento en caché, selecciona Actualizar cachés y expande la sección. Deberías ver el nombre de tu caché estática en el lado izquierdo. Haz clic en el nombre de la caché para ver todos los archivos almacenados en caché.

Ahora, probemos el modo sin conexión. Vuelve al panel Service Workers del panel Application de DevTools y marca la casilla de verificación Offline. Después de comprobarlo, deberías ver un pequeño ícono amarillo de advertencia junto a la pestaña del panel Red. Este ícono indica que no tienes conexión.

Vuelve a cargar la página y... ¡funciona! Tenemos nuestro panda sin conexión, en lugar del dinosaurio sin conexión de Chrome.

Sugerencias para probar service workers

La depuración de service workers puede ser un desafío, y cuando implica el almacenamiento en caché, todo se puede convertir en una pesadilla si la caché no se actualiza cuando tú lo esperas. Entre el ciclo de vida típico de un service worker y un error en tu código, podrías frustrarte rápidamente. Pero no lo haga.

Cómo usar las Herramientas para desarrolladores

En el panel Service Workers del panel Application, hay algunas casillas de verificación que te facilitarán la vida.

  • Sin conexión: Cuando está marcada, simula una experiencia sin conexión y evita que se envíen solicitudes a la red.
  • Actualización al volver a cargar: Cuando esté marcada, se obtendrá el service worker más reciente, se instalará y se activará de inmediato.
  • Omisión para la red: Cuando está marcada, las solicitudes omiten el service worker y se envían directamente a la red.

Empezar de nuevo

En algunos casos, es posible que cargues datos almacenados en caché o que los datos no se actualicen como esperas. Para borrar todos los datos guardados (localStorage, datos indexDB, archivos almacenados en caché) y quitar los service workers, usa el panel Clear storage del panel Application. Como alternativa, también puedes trabajar en una ventana de incógnito.

Sugerencias adicionales:

  • Una vez que se haya cancelado el registro del service worker, es posible que continúe apareciendo en la lista hasta que se cierre la ventana del navegador que lo contiene.
  • Si hay varias ventanas de tu app abiertas, un nuevo service worker no tendrá efecto hasta que todas las ventanas se vuelvan a cargar y se actualicen al service worker más reciente.
  • Anular el registro de un service worker no borra la caché.
  • Si existe un service worker y se registra un nuevo service worker, este no tomará el control hasta que se vuelva a cargar la página, a menos que tomes el control de inmediato.

Verifica los cambios con Lighthouse

Vuelve a ejecutar Lighthouse y verifica los cambios. No olvides desmarcar la casilla de verificación Sin conexión antes de verificar los cambios.

Auditoría de SEO

  • ✅ APROBADA: El documento tiene una metadescripción.

Auditoría de apps web progresivas

  • ✅ APROBADA: La página actual responde con un código de estado HTTP 200 cuando no hay conexión.
  • ✅ APROBADA: start_url responde con un 200 cuando está sin conexión.
  • ✅ APROBADA: Registra un service worker que controla la página y start_url.
  • ✅ APROBADA: El manifiesto de la app web cumple con los requisitos de instalación.
  • ✅ APROBADA: Configurada para una pantalla de presentación personalizada.
  • ✅ APROBADA: Establece un color de tema para la barra de direcciones.

Tómate un momento para activar el modo de avión y prueba a ejecutar algunas de las aplicaciones favoritas. En la mayoría de los casos, proporcionan una experiencia sin conexión bastante robusta. Los usuarios esperan tener una experiencia sólida en sus apps. Y la Web no debe ser la misma. Las apps web progresivas deben diseñarse sin conexión como situación principal.

Ciclo de vida del service worker

El ciclo de vida del service worker es la parte más complicada. Si no sabes qué es lo que se está haciendo y cuáles son sus beneficios, puede parecer que estás luchando contra ti. Sin embargo, una vez que sabes cómo funciona, puedes ofrecer actualizaciones fluidas y discretas a los usuarios, mezclando lo mejor de la Web y de los patrones nativos.

install evento

El primer evento que recibe un service worker es install. Se activa en cuanto se ejecuta el trabajador y solo se llama una vez por cada service worker. Si modificas la secuencia de comandos del service worker, el navegador la considera un service worker diferente y recibirá su propio evento install.

Por lo general, el evento install se usa para almacenar en caché todo lo que necesitas para ejecutar tu app.

activate evento

El service worker recibirá un evento activate cada vez que se inicie. El propósito principal del evento activate es configurar el comportamiento del service worker, limpiar cualquier recurso que haya quedado atrás en las ejecuciones anteriores (p.ej., cachés antiguas) y preparar el service worker para que pueda controlar las solicitudes de red (por ejemplo, el evento fetch que se describe a continuación).

fetch evento

El evento de recuperación permite que el service worker intercepte cualquier solicitud de red y controle las solicitudes. Puede ir a la red para obtener el recurso, extraerlo de su propia caché, generar una respuesta personalizada o varias opciones. Consulta la Guía de soluciones sin conexión para conocer las diferentes estrategias que puedes usar.

Actualiza un service worker

El navegador comprueba si hay una nueva versión del service worker en cada carga de página. Si encuentra una nueva versión, esta se descargará e instalará en segundo plano, pero no se activará. La nueva versión del service worker se mantiene en estado de espera hasta que ya no haya ninguna página abierta que use el service worker anterior. Una vez que se cierran todas las ventanas que usan el service worker anterior, se activa el nuevo service worker y puede tomar el control. Para obtener más información, consulta la sección Actualización del service worker en el documento Ciclo de vida del service worker.

Cómo elegir la estrategia de almacenamiento en caché adecuada

La elección de la estrategia de almacenamiento en caché correcta depende del tipo de recurso que intentas almacenar en caché y de cómo podrías tener que acceder a él más adelante. En nuestra app de clima, dividiremos los recursos que necesitamos almacenar en caché en dos categorías: los que queremos almacenar en caché previamente y los que almacenaremos en caché en el tiempo de ejecución.

Almacenamiento en caché de recursos estáticos

El almacenamiento previo en caché de tus recursos es un concepto similar a lo que sucede cuando un usuario instala una app de escritorio o móvil. Los recursos clave necesarios para que la app se ejecute se instalan o almacenan en caché en el dispositivo a fin de que puedan cargarse más tarde, ya sea que haya una conexión de red o no.

Para nuestra app, almacenaremos en caché previamente todos nuestros recursos estáticos cuando nuestro service worker se instale de modo que todo lo que necesitemos para ejecutar nuestra app se almacene en el dispositivo del usuario. Para garantizar que nuestra app se cargue rápido, usaremos la estrategia primero en caché; en lugar de ir a la red para obtener los recursos, los extraeremos de la caché local. Solo si no está disponible allí, intentaremos obtenerla de la red.

Extraer de la caché local elimina cualquier variabilidad de la red. Independientemente del tipo de red que utilice el usuario (Wi-Fi, 5G, 3G o incluso 2G), los recursos clave que necesitamos para ejecutar están disponibles casi de inmediato.

Cómo almacenar los datos de la app en caché

La estrategia stale-while-revalidate es ideal para ciertos tipos de datos y funciona bien con nuestra app. Muestra datos en pantalla lo más rápido posible y luego los actualiza cuando la red muestra los datos más recientes. Reinicializar significa que debemos iniciar dos solicitudes asíncronas: una a la caché y otra a la red.

En circunstancias normales, los datos almacenados en caché se mostrarán casi de inmediato y le proporcionarán a la app datos recientes que puede usar. Luego, cuando se muestre la solicitud de red, se actualizará la app con los datos más recientes de la red.

Para nuestra app, esto proporciona una mejor experiencia que la red y recurre a la estrategia de caché porque el usuario no tiene que esperar hasta que se agote el tiempo de espera de la solicitud de red para ver algo en la pantalla. Es posible que al principio vean datos más antiguos, pero una vez que se muestre la solicitud de red, la app se actualizará con los datos más recientes.

Cómo actualizar la lógica de la app

Como se mencionó anteriormente, la app debe iniciar dos solicitudes asíncronas: una a la caché y otra a la red. La app usa el objeto caches disponible en window para acceder a la caché y recuperar los datos más recientes. Este es un excelente ejemplo de mejora progresiva, ya que el objeto caches podría no estar disponible en todos los navegadores. De lo contrario, la solicitud de red debería funcionar de todos modos.

Actualiza la función getForecastFromCache() a fin de verificar si el objeto caches está disponible en el objeto window global y, si lo está, solicitar los datos de la caché.

public/scripts/app.js.

// CODELAB: Add code to get weather forecast from the caches object.
if (!('caches' in window)) {
  return null;
}
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
    .then((response) => {
      if (response) {
        return response.json();
      }
      return null;
    })
    .catch((err) => {
      console.error('Error getting data from cache', err);
      return null;
    });

Luego, debemos modificar updateData() a fin de que realice dos llamadas: una a getForecastFromNetwork() para obtener la previsión de la red y otra a getForecastFromCache() a fin de obtener la última previsión almacenada en caché:

public/scripts/app.js.

// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });

Nuestra app de estado del tiempo ahora hace dos solicitudes asíncronas de datos: una de la caché y otra una fetch. Si hay datos en la caché, se mostrarán y renderizarán rápidamente (decenas de milisegundos). Luego, cuando fetch responda, se actualizará la tarjeta con los datos más recientes de la API de estado del tiempo.

Observa cómo la solicitud de caché y la solicitud fetch finalizan con una llamada a fin de actualizar la tarjeta de previsión. ¿Cómo sabe la app si muestra los datos más recientes? Esto se controla en el siguiente código de renderForecast():

public/scripts/app.js.

// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
  return;
}

Cada vez que se actualiza una tarjeta, la app almacena la marca de tiempo de los datos en un atributo oculto de la tarjeta. La app se retira si la marca de tiempo que ya existe en la tarjeta es más reciente que los datos que se pasaron a la función.

Almacena previamente en caché nuestros recursos de apps

En el service worker, agregaremos un elemento DATA_CACHE_NAME para que podamos separar los datos de nuestra aplicación del shell de la app. Cuando se actualiza la shell de la app y se borran definitivamente las cachés más antiguas, nuestros datos permanecen intactos y están listos para una carga muy rápida. Ten en cuenta que, si cambia el formato de datos en el futuro, necesitarás un método para controlar esto y asegurarte de que el shell y el contenido de la app permanezcan sincronizados.

public/service-worker.js

// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v2';
const DATA_CACHE_NAME = 'data-cache-v1';

No olvides actualizar también los CACHE_NAME; también cambiaremos todos nuestros recursos estáticos.

Para que nuestra app funcione sin conexión, debemos almacenar en caché todos los recursos que necesita antes. Esto también ayudará a mejorar nuestro rendimiento. En lugar de tener que obtener todos los recursos de la red, la app podrá cargarlos desde la caché local, lo que elimina la inestabilidad de la red.

Actualiza el array de FILES_TO_CACHE con la lista de archivos:

public/service-worker.js

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/scripts/install.js',
  '/scripts/luxon-1.11.4.js',
  '/styles/inline.css',
  '/images/add.svg',
  '/images/clear-day.svg',
  '/images/clear-night.svg',
  '/images/cloudy.svg',
  '/images/fog.svg',
  '/images/hail.svg',
  '/images/install.svg',
  '/images/partly-cloudy-day.svg',
  '/images/partly-cloudy-night.svg',
  '/images/rain.svg',
  '/images/refresh.svg',
  '/images/sleet.svg',
  '/images/snow.svg',
  '/images/thunderstorm.svg',
  '/images/tornado.svg',
  '/images/wind.svg',
];

Dado que generamos manualmente la lista de archivos para almacenar en caché, cada vez que actualizamos un archivo, debemos actualizar el CACHE_NAME. Pudimos quitar offline.html de nuestra lista de archivos almacenados en caché porque nuestra app ahora tiene todos los recursos necesarios para funcionar sin conexión y no volverá a mostrar la página sin conexión.

Actualiza el controlador de activación de eventos

Para garantizar que nuestro evento activate no borre nuestros datos accidentalmente, en el evento activate de service-worker.js, reemplaza if (key !== CACHE_NAME) { por lo siguiente:

public/service-worker.js

if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {

Actualiza el controlador de eventos fetch

Necesitamos modificar el service worker para interceptar solicitudes a la API de clima y almacenar sus respuestas en la caché a fin de poder acceder a ellas fácilmente más adelante. En la estrategia obsoleta, esperamos que la respuesta de la red sea la "fuente de confianza" y que siempre nos brinde la información más reciente. Si la red no puede, está bien fallar porque ya recuperamos los datos almacenados en caché más recientes de nuestra app.

Actualiza el controlador de eventos fetch para controlar las solicitudes a la API de datos en forma separada de otras solicitudes.

public/service-worker.js

// CODELAB: Add fetch event handler here.
if (evt.request.url.includes('/forecast/')) {
  console.log('[Service Worker] Fetch (data)', evt.request.url);
  evt.respondWith(
      caches.open(DATA_CACHE_NAME).then((cache) => {
        return fetch(evt.request)
            .then((response) => {
              // If the response was good, clone it and store it in the cache.
              if (response.status === 200) {
                cache.put(evt.request.url, response.clone());
              }
              return response;
            }).catch((err) => {
              // Network request failed, try to get it from the cache.
              return cache.match(evt.request);
            });
      }));
  return;
}
evt.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(evt.request)
          .then((response) => {
            return response || fetch(evt.request);
          });
    })
);

El código intercepta la solicitud y verifica si es de un pronóstico del tiempo. Si es así, usa fetch para realizar la solicitud. Una vez que se muestre la respuesta, abre la caché, clona la respuesta, almacénala en la caché y muéstrala la respuesta al solicitante original.

Debemos quitar la verificación evt.request.mode !== 'navigate' porque queremos que nuestro service worker maneje todas las solicitudes (incluidas las imágenes, las secuencias de comandos, los archivos CSS, etc.), no solo las navegaciones. Si dejamos ese registro, solo se entregaría el código HTML desde la caché del service worker. Todo lo demás se solicitaría a la red.

Pruébalo

Ahora, la app debería funcionar por completo sin conexión. Actualiza la página para asegurarte de tener el último service worker instalado. Luego, guarda algunas ciudades y presiona el botón de actualización en la app para obtener datos actualizados sobre el clima.

A continuación, vaya al panel Cache Storage en el panel Application de Herramientas para desarrolladores. Expande la sección. Deberías ver el nombre de la caché estática y la caché de datos en el lado izquierdo. Al abrir la caché de datos, deberían aparecer los datos almacenados de cada ciudad.

Cambia al panel Service workers y marca la casilla de verificación Offline. Intenta volver a cargar la página, conéctate y vuelve a cargarla.

Si usas una red rápida y quieres ver cómo se actualizan los datos del pronóstico del tiempo en una conexión lenta, establece la propiedad FORECAST_DELAY de server.js en 5000. Todas las solicitudes a la API de previsión se retrasarán 5,000 ms.

Verifica los cambios con Lighthouse

También se recomienda volver a ejecutar Lighthouse.

Auditoría de SEO

  • ✅ APROBADA: El documento tiene una metadescripción.

Auditoría de apps web progresivas

  • ✅ APROBADA: La página actual responde con un código de estado HTTP 200 cuando no hay conexión.
  • ✅ APROBADA: start_url responde con un 200 cuando está sin conexión.
  • ✅ APROBADA: Registra un service worker que controla la página y start_url.
  • ✅ APROBADA: El manifiesto de la app web cumple con los requisitos de instalación.
  • ✅ APROBADA: Configurada para una pantalla de presentación personalizada.
  • ✅ APROBADA: Establece un color de tema para la barra de direcciones.

Cuando se instala una app web progresiva, se ve y se comporta como las otras apps instaladas. Se inicia desde el mismo lugar en el que se inician otras apps. Se ejecuta en una app sin una barra de direcciones ni otra IU del navegador. Y, como todas las demás apps instaladas, es una app de nivel superior que permite cambiar de tarea.

En Chrome, una app web progresiva se puede instalar mediante el menú contextual de tres puntos o puedes proporcionar un botón u otro componente de la IU al usuario que le pedirá que instale tu app.

Auditoría con Lighthouse

Para que un usuario pueda instalar tu app web progresiva, la app debe cumplir con ciertos criterios. La manera más sencilla de comprobarlo es usar Lighthouse y asegurarte de que cumpla con los criterios de instalación.

Si hiciste este codelab, tu AWP ya debería cumplir con estos criterios.

Agrega install.js a index.html.

Primero, agreguemos el install.js a nuestro archivo index.html.

public/index.html.

<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>

Escuchar el evento beforeinstallprompt

Si se cumplen los criterios de agregado a la pantalla principal, Chrome activará un evento beforeinstallprompt que puedes usar para indicar que tu app puede instalarse y, luego, le solicitará al usuario que la instale. Agrega el siguiente código para escuchar el evento beforeinstallprompt:

public/scripts/install.js

// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);

Guardar evento y mostrar botón de instalación

En nuestra función saveBeforeInstallPromptEvent, guardaremos una referencia al evento beforeinstallprompt para que luego podamos llamar a prompt() y actualizaremos nuestra IU a fin de mostrar el botón de instalación.

public/scripts/install.js

// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');

Muestra el mensaje y oculta el botón

Cuando el usuario hace clic en el botón de instalación, debemos llamar a .prompt() en el evento beforeinstallprompt guardado. También debemos ocultar el botón de instalación, ya que solo se puede llamar a .prompt() una vez en cada evento guardado.

public/scripts/install.js

// CODELAB: Add code show install prompt & hide the install button.
deferredInstallPrompt.prompt();
// Hide the install button, it can't be called twice.
evt.srcElement.setAttribute('hidden', true);

Llamar a .prompt() le mostrará un diálogo modal al usuario en el que se le pedirá que agregue tu app a la pantalla principal.

Registre los resultados

Puedes verificar cómo respondió el usuario al diálogo de instalación escuchando la promesa que mostró la propiedad userChoice del evento beforeinstallprompt guardado. La promesa muestra un objeto con una propiedad outcome después de que se muestra el mensaje y el usuario responde.

public/scripts/install.js

// CODELAB: Log user response to prompt.
deferredInstallPrompt.userChoice
    .then((choice) => {
      if (choice.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt', choice);
      } else {
        console.log('User dismissed the A2HS prompt', choice);
      }
      deferredInstallPrompt = null;
    });

Un comentario sobre userChoice: la especificación la define como una propiedad, no una función como se espera.

Cómo registrar todos los eventos de instalación

Además de agregar cualquier IU para instalar la app, los usuarios también pueden instalar la AWP por medio de otros métodos, como el menú de tres puntos de Chrome. Para hacer un seguimiento de estos eventos, busca el evento appappapp.

public/scripts/install.js

// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);

Luego, deberemos actualizar la función logAppInstalled. Para este codelab, simplemente usaremos console.log, pero en una app de producción, probablemente desees registrarlo como un evento con tu software de estadísticas.

public/scripts/install.js

// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);

Actualiza el service worker

No olvides actualizar CACHE_NAME en tu archivo service-worker.js, ya que hiciste cambios en los archivos que ya están almacenados en caché. Habilitar la casilla de verificación Bypass for network en el panel Service Workers del panel Application en DevTools funcionará en desarrollo, pero no ayudará en el mundo real.

Pruébalo

Veamos cómo fue el paso de instalación. Para mayor seguridad, usa el botón Borrar datos de sitios del panel Aplicación de Herramientas para desarrolladores para borrar todo y asegurarse de comenzar de cero. Si la instalaste anteriormente, asegúrate de desinstalarla; de lo contrario, no volverá a aparecer el ícono de instalación.

Verifica que el botón de instalación esté visible

Primero verifiquemos que el ícono de instalación aparezca correctamente. Asegúrate de probar esto tanto en computadoras como en dispositivos móviles.

  1. Abre la URL en una nueva pestaña de Chrome.
  2. Abre el menú de tres puntos de Chrome (junto a la barra de direcciones).
    ▢ Verifica que aparezca el mensaje Instalar Weather... en el menú.
  3. Actualiza los datos meteorológicos con el botón Actualizar ubicado en la esquina superior derecha para asegurarte de cumplir con las heurísticas de participación de los usuarios.
    ▢ Verifica que el ícono de instalación sea visible en el encabezado de la app.

Cómo verificar que funciona el botón de instalación

A continuación, vamos a asegurarnos de que todo se instale correctamente y que nuestros eventos se activen correctamente. Puede hacerlo en una computadora o en un dispositivo móvil. Si quieres probar esto en un dispositivo móvil, asegúrate de usar la depuración remota para que puedas ver lo que se registró en la consola.

  1. Abre Chrome y, en una nueva pestaña del navegador, navega a tu AWP del clima.
  2. Abra DevTools y cambie al panel Console.
  3. Haz clic en el botón de instalación en la esquina superior derecha.
    ▢ Verifica que el botón de instalación desaparezca
    ▢ Verifica que se muestre el cuadro de diálogo de instalación.
  4. Haz clic en Cancelar.
    ▢ Verify "El usuario descartó la solicitud de A2HSy se muestra en el resultado de la consola.
    ▢ Verifica que vuelva a aparecer el botón de instalación.
  5. Vuelve a hacer clic en el botón instalar, luego, haz clic en el botón instalar en el cuadro de diálogo modal.
    ▢ Verifica que el usuario acepta el mensaje A2HS que se muestra en el resultado de la consola.
    ▢ Verifica que la aplicación de clima se haya instalado" se muestra en el resultado de la consola.
    ▢ Verifica que la aplicación de clima se agregue al lugar donde sueles encontrar las aplicaciones.
  6. Inicia la AWP Weather.
    ▢ Verifica que la aplicación se abra como una aplicación independiente, ya sea en la ventana de una aplicación en el escritorio o en la pantalla completa de los dispositivos móviles.

.

Verifica que la instalación de iOS funcione correctamente

También verifiquemos el comportamiento en iOS. Si tienes un dispositivo iOS, puedes usarlo. En una Mac, prueba el simulador de iOS disponible con Xcode.

  1. Abre Safari y, en una nueva pestaña del navegador, navega a tu AWP de clima.
  2. Haz clic en el botón Compartir.
  3. Desplázate hacia la derecha y haz clic en el botón Agregar a la pantalla principal.
    ▢ Verifica que el título, la URL y el ícono sean correctos.
  4. Haga clic en Add.
    ▢ Verifique que el ícono de la aplicación se agregue a la pantalla principal.
  5. Abre la AWP de Weather desde la pantalla principal.
    ▢ Verifica que la aplicación inicie en pantalla completa.

Opción adicional: Cómo detectar si la app se inicia desde la pantalla principal

La consulta de medios display-mode permite aplicar estilos según la manera en que se lanzó la app o determinar cómo se lanzó con JavaScript.

@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}

También puedes revisar la búsqueda de contenido multimedia display-mode en JavaScript para verificar si se ejecuta de forma independiente.

Bonificación: Desinstala tu AWP

Recuerda que el beforeinstallevent no se activa si la app ya está instalada, así que, durante el desarrollo, es probable que quieras instalar y desinstalar la app varias veces para asegurarte de que todo funcione según lo esperado.

Android

En Android, las AWP se desinstalan de la misma manera que se desinstalan otras aplicaciones instaladas.

  1. Abre el panel de apps.
  2. Desplázate hacia abajo hasta encontrar el ícono de clima.
  3. Arrastra el ícono de la app a la parte superior de la pantalla.
  4. Elige Desinstalar.

Chrome OS

En Chrome OS, las AWP se desinstalan fácilmente desde el cuadro de búsqueda del Selector.

  1. Abre el Selector.
  2. Escribe Weather en el cuadro de búsqueda. Tu AWP de clima debería aparecer en los resultados.
  3. Haz clic con el botón derecho (alternar) en la AWP de Weather.
  4. Haz clic en Quitar de Chrome...

macOS y Windows

En Mac y Windows, es posible desinstalar las AWP a través de Chrome:

  1. En una nueva pestaña del navegador, abre chrome://apps.
  2. Haz clic con el botón derecho (alternar) en la AWP de Weather.
  3. Haz clic en Quitar de Chrome...

También puedes abrir la AWP instalada, hacer clic en el menú contextual de tres puntos en la esquina superior derecha y elegir la opción Desinstalar AWP de clima....

¡Felicitaciones! Creaste correctamente tu primera app web progresiva.

Agregaste un manifiesto de aplicación web para permitir la instalación y agregaste un service worker para asegurarte de que tu AWP siempre sea rápida y confiable. Aprendiste a usar DevTools para auditar una app y cómo puede ayudarte a mejorar la experiencia del usuario.

Ya conoce los pasos clave necesarios para convertir cualquier aplicación web en una aplicación web progresiva.

¿Qué sigue?

Consulta algunos de estos Codelabs…

Lecturas adicionales

Documentos de referencia