Cómo compilar una AWP en Google (parte 1)

Qué aprendió el equipo de Boletín sobre los service workers mientras desarrollaba una AWP.

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

Esta es la primera de una serie de entradas de blog sobre las lecciones que aprendió el equipo de Google Boletín mientras compilas una AWP externa. En estas publicaciones, compartiremos algunos de los desafíos que enfrentamos, los enfoques que adoptamos para superarlos y consejos generales para evitar dificultades. Esto no es significa una descripción general completa de las AWP. El objetivo es compartir los hallazgos de la experiencia de nuestro equipo.

En esta primera publicación, veremos un poco de información contextual y, luego, lo que aprendimos sobre los service workers.

Información general

Boletín estuvo en desarrollo activo desde mediados de 2017 hasta mediados de 2019.

Por qué elegimos compilar una AWP

Antes de adentrarnos en el proceso de desarrollo, veamos por qué la creación de una AWP fue atractiva para este proyecto:

  • Capacidad para iterar rápidamente. Esto es especialmente valioso, ya que se realizaría una prueba piloto de Boletín en en varios mercados.
  • Base de código única. Nuestros usuarios estaban divididos de manera bastante equitativa entre Android y iOS. Una AWP significaba podríamos compilar una sola aplicación web que funcione en ambas plataformas. Esto aumentó la velocidad y el impacto del equipo.
  • Se actualizan rápidamente, sin importar el comportamiento del usuario. Las AWP pueden actualizar automáticamente cuáles reduce la cantidad de clientes desactualizados en el entorno. Pudimos desplegar el backend con errores con muy poco tiempo de migración para los clientes.
  • Se integra fácilmente en apps propias y de terceros. Esas integraciones eran un requisito para la aplicación. Con una AWP, a menudo significaba simplemente abrir una URL.
  • Se eliminó la fricción que implicaba instalar una aplicación.

Nuestro marco de trabajo

En Boletín, usamos Polymer, pero todas las plataformas modernas y compatibles de Terraform funcionará.

Qué aprendimos sobre los service workers

No puedes tener una AWP sin un servicio trabajador a la nube. Trabajadores de servicio te brindan mucha potencia, como estrategias avanzadas de almacenamiento en caché, capacidades sin conexión, sincronización en segundo plano, Si bien los service workers añaden cierta complejidad, descubrimos que sus beneficios superan los beneficios y complejidad.

Generarla si puedes

Evita escribir una secuencia de comandos del service worker de forma manual. La escritura manual de service workers requiere manualmente administrar recursos almacenados en caché y reescribir la lógica que es común en la mayoría de las bibliotecas de service workers, como como Workbox.

Dicho esto, debido a nuestra pila tecnológica interna no podríamos usar una biblioteca para generar y administrar nuestro service worker. En ocasiones, lo que aprendimos a continuación reflejará eso. Consulta Errores para service workers no generados para obtener más información.

No todas las bibliotecas son compatibles con service worker

Algunas bibliotecas de JS hacen suposiciones que no funcionan como se espera cuando las ejecuta un service worker. Para suponiendo que window o document estén disponibles, o bien usar una API no disponible para el servicio trabajadores (XMLHttpRequest, almacenamiento local, etcétera). Asegúrate de que todas las bibliotecas esenciales que necesites son compatibles con service workers. Para esta AWP en particular, queríamos usar gapi.js para la autenticación, pero se no era posible porque no admitía service workers. Los autores de las bibliotecas también deben reducir o quitar suposiciones innecesarias sobre el contexto de JavaScript cuando sea posible para admitir el uso del service worker. como evitar las API incompatibles con los service workers y evitar servicios estado.

Evita acceder a IndexedDB durante la inicialización

No leas IndexedDB cuando si inicializas la secuencia de comandos de tu service worker. De lo contrario, podrías caer en esta situación no deseada:

  1. El usuario tiene una aplicación web con la versión N de IndexedDB (IDB).
  2. Se envía una nueva aplicación web con la versión N+1 de IDB
  3. El usuario visita la AWP, que activa la descarga de un nuevo service worker
  4. Un nuevo service worker lee de IDB antes de registrar el controlador de eventos install. Esto activa un Ciclo de actualización de IDB para pasar de N a N+1
  5. Dado que el usuario tiene un cliente anterior con la versión N, el proceso de actualización del service worker se bloquea como activo siguen abiertas en la versión anterior de la base de datos
  6. El service worker se bloquea y nunca se instala

En nuestro caso, la caché se invalidó al instalar el service worker, instalada, los usuarios nunca recibieron la app actualizada.

Haz que sea resiliente

Aunque las secuencias de comandos del service worker se ejecutan en segundo plano, también pueden finalizarse en cualquier momento, incluso cuando se encuentra en medio de operaciones de E/S (red, IDB, etcétera). Cualquier proceso de larga duración debe reanudables en cualquier momento.

En el caso de un proceso de sincronización que subió archivos grandes al servidor y los guardó en IDB, nuestra solución de las cargas parciales interrumpidas fue aprovechar el espacio de , guardar la URL de carga reanudable en IDB antes de subirla y usar esa URL para reanudar una si no se completó la primera vez. Además, antes de cualquier operación de E/S de larga duración, se guardó en el IDB para indicar en qué parte del proceso estuvimos para cada registro.

No depender del estado global

Debido a que los service workers existen en un contexto diferente, muchos de los símbolos que podría esperar que existan presente. Gran parte de nuestro código se ejecutó en un contexto de window y en un contexto de service worker (como como registro, marcas, sincronización, etcétera). El código debe estar a la defensiva sobre los servicios que usa, como el almacenamiento local o las cookies. Puedes usar globalThis al objeto global de una manera que funcionará en todos los contextos. También usar los datos almacenados en variables globales con moderación, ya que no hay garantía de cuándo finalizará la secuencia de comandos el estado expulsado.

Desarrollo local

Uno de los componentes principales de los service workers es almacenar recursos en caché de forma local. Sin embargo, durante el desarrollo, es exactamente lo opuesto a lo que quieres, en especial cuando las actualizaciones se realizan de forma diferida. Aún quieres el trabajador del servidor instalado para que puedas depurar problemas con él o trabajar con otras APIs como la sincronización en segundo plano o las notificaciones. En Chrome, puedes lograr esto con las Herramientas para desarrolladores de Chrome, habilita la casilla de verificación Bypass for network (panel Application > Service workers) en además de habilitar la casilla de verificación Inhabilitar la caché en el panel Red inhabilitar la memoria caché. Para abarcar más navegadores, optamos por una solución diferente incluida una marca para inhabilitar el almacenamiento en caché en nuestro service worker, que está habilitada de forma predeterminada en el compilaciones. Esto garantiza que los desarrolladores siempre obtengan sus cambios más recientes sin problemas de almacenamiento en caché. Es también es importante incluir el encabezado Cache-Control: no-cache para evitar que el navegador almacenar recursos en caché.

Faro

Lighthouse proporciona varias opciones de depuración útiles para las AWP. Analiza un sitio y genera informes sobre las AWP, el rendimiento accesibilidad, SEO y otras prácticas recomendadas. Te recomendamos ejecutar Lighthouse en entornos integrada para alertarte si rompes uno de los para ser una AWP. Esto nos ocurrió una vez, cuando el service worker no se instalaba y no nos dimos cuenta antes del lanzamiento de producción. Tener Lighthouse como parte de nuestra CI lo evitó.

Adopta la entrega continua

Debido a que los service workers pueden actualizarse automáticamente, los usuarios no tienen la capacidad de limitar las actualizaciones. Esta reduce significativamente la cantidad de clientes desactualizados. Cuando el usuario abrió nuestra aplicación, el service worker serviría al cliente anterior mientras descargaba de forma diferida el nuevo cliente. Una vez que un cliente nuevo descargado, le pediría al usuario que actualice la página para acceder a nuevas funciones. Incluso si el usuario ignoró esta solicitud, la próxima vez que actualice la página, recibirá la nueva solicitud. más reciente del cliente. Por ello, es bastante difícil para un usuario rechazar actualizaciones en el mismo que en las aplicaciones de iOS y Android.

Pudimos implementar cambios rotundos en nuestro backend con muy poco tiempo de migración para clientes. Normalmente, hay un mes para que los usuarios se actualicen a los clientes más nuevos cambios rotundos. Como la app se publicaría mientras estaba inactiva, en realidad era posible que los clientes más antiguos existe en el entorno si el usuario no abre la aplicación durante mucho tiempo. En iOS, los service workers son expulsados después de un par de semanas por lo que este caso no sucede. Para Android, este problema podría mitigarse si no se publica mientras que el contenido quede inactivo o que venza manualmente después de algunas semanas. En la práctica, nunca nos encontramos problemas de clientes inactivos. Qué tan estricto quiere ser un equipo determinado en función de su uso específico caso, pero las AWP brindan una flexibilidad significativamente mayor que las aplicaciones para iOS/Android.

Cómo obtener valores de cookies en un service worker

A veces, es necesario acceder a los valores de las cookies en un contexto de service worker. En este caso, necesario para acceder a los valores de las cookies y generar un token que autentique las solicitudes propias a la API. En una service worker, las APIs síncronas como document.cookies no están disponibles. Siempre puedes enviar un a los clientes activos (con ventanas) del service worker para solicitar los valores de las cookies, es posible que el service worker se ejecute en segundo plano sin ningún cliente con ventanas disponibles, como durante una sincronización en segundo plano. Para solucionar esto, creamos un extremo en nuestro de frontend que solo le transmitía el valor de la cookie al cliente. El service worker creó a este extremo y leerá la respuesta para obtener los valores de la cookie.

Con el lanzamiento de la API de Cookie Store, esta solución alternativa ya no debería ser necesaria para los navegadores compatibles, ya que proporciona el acceso asíncrono a las cookies del navegador, y el service worker puede usarlo.

Errores de service workers no generados

Asegúrate de que la secuencia de comandos del service worker cambie si cambia algún archivo estático almacenado en caché

Un patrón común de AWP es que un service worker instale todos los archivos estáticos de aplicaciones durante su install, que permite a los clientes acceder directamente a la caché de la API de Cache Storage para todos visitas posteriores . Los service workers solo se instalan cuando el navegador detecta que el servicio cambió de alguna forma, así que tuvimos que asegurarnos de que el archivo de secuencia de comandos del service worker cambiaba de alguna manera cuando cambiaba un archivo almacenado en caché. Lo hicimos manualmente incorporando un hash del de recursos estáticos dentro de la secuencia de comandos del service worker, de manera que cada lanzamiento produjo un error archivo JavaScript del service worker. Las bibliotecas de service worker, Workbox automatiza este proceso por ti.

Pruebas de unidades

Las APIs de service worker funcionan agregando objetos de escucha de eventos al objeto global. Por ejemplo:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

Esto puede ser difícil de probar, ya que debes simular el activador del evento, el objeto del evento, la devolución de llamada respondWith() y, luego, esperar a la promesa antes de confirmar el resultado Los y la forma más sencilla de estructurarlo es delegar toda la implementación a otro archivo, lo que es más fácil y cómo se entrenó y probó el modelo.

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

Debido a las dificultades de la prueba de unidades de la secuencia de comandos del service worker, mantuvimos el service worker principal tan básica como sea posible, dividiendo la mayor parte de la implementación en otros módulos. Desde esos archivos eran solo módulos de JS estándar, se podían hacer pruebas de unidades con mayor facilidad mediante pruebas estándar bibliotecas.

No te pierdas las partes 2 y 3

En las partes 2 y 3 de esta serie, hablaremos sobre la administración de medios y los problemas específicos de iOS. Si para saber más sobre cómo compilar una AWP en Google, visita nuestros perfiles de autores para averiguar cómo contactarnos: