Skip to main content




What the Bulletin team learned about service workers while developing a PWA.

Esta es la primera de una serie de publicaciones de Blog sobre las lecciones que aprendió el equipo de Google Bulletin al crear una PWA externa. En estas publicaciones compartiremos algunos de los desafíos que enfrentamos, los enfoques que tomamos para superarlos y consejos generales para evitar trampas. Esta no es de ninguna manera una descripción general completa de las PWA. El target es compartir los aprendizajes de la experiencia de nuestro equipo.

For this first post, we'll cover a little general information first, and then dive into everything we learned about service workers.

Bulletin was closed in 2019 due to a lack of product / market adjustment. We still learned a lot about PWAs along the way!

Background

Bulletin was in active development from mid-2017 to mid-2019.

Why we chose to build a PWA

Before delving into the development process, let's examine why creating a PWA was an attractive option for this project:

  • Ability to quickly iterate. Especially valuable as the Bulletin would be tested in multiple markets.
  • Single code base. Nuestros usuarios se dividieron aproximadamente en partes iguales entre Android e iOS. Una PWA significaba que podíamos crear una única aplicación Web que funcionara en ambas plataformas. Esto aumentó la velocidad y el impacto del equipo.
  • Actualizado rápidamente e independientemente del comportamiento del Username. PWAs can be automatically updated, reducing the number of outdated clients in the wild. We were able to drive major changes to the backend with a very short migration time for clients.
  • It is easily integrated with your own and third-party applications. Tales integraciones eran un requisito para la aplicación. Con una PWA, a menudo significaba simplemente abrir una Url.
  • The friction of installing an app has been removed.

Our framework

For Bulletin, we use Polymer, but any modern, well-supported framework will do.

What we learned about service workers

You can't have a PWA without one service worker. Los trabajadores de servicios le brindan mucho poder, como estrategias avanzadas de almacenamiento en cache, capacidades fuera de línea, sincronización en segundo plano, etc. Si bien los trabajadores de servicios agregan algo de complejidad, descubrimos que sus beneficios superan la complejidad adicional.

Generate it if you can

Avoid writing a service worker script by hand. Writing service workers by hand requires manually managing cached resources and rewriting the logic that is common to most service worker libraries, such as Workbox.

That said, due to our internal tech stack, we were unable to use a library to generate and manage our service worker. Our learnings below will at times reflect that. Go to Issues for non-generated service workers to read more.

Not all libraries are compatible with service workers

Some JS libraries make assumptions that don't work as expected when run by a service worker. For example, assuming window or document están disponibles, o usando una API no disponible para los trabajadores del servicio (XMLHttpRequest, local storage, etc.). Make sure that the critical libraries you need for your application are supported by service workers. For this particular PWA, we wanted to use
gapi.js para la autenticación, pero no pudieron hacerlo porque no admitía a los trabajadores del servicio. Los autores de bibliotecas también deben reducir o eliminar suposiciones innecesarias sobre el contexto de JavaScript siempre que be posible para respaldar los casos de uso de trabajadores de servicios, como evitar API incompatibles con trabajadores de servicios y evitar el estado global.

Avoid accessing IndexedDB during initialization

Do not read IndexedDB while initializing your service worker script, or you may get into this unwanted situation:

  1. The user has a web application with IndexedDB (IDB) version N
  2. A new web application is included with the N + 1 version of the IDB
  3. User visits PWA, which triggers the download of a new service worker
  4. The new service worker reads from the IDB before registering install event handler, activating a BID update cycle to go from N to N + 1
  5. Dado que el usuario tiene un client antiguo con la versión N, el proceso de actualización del trabajador del servicio se bloquea ya que las conexiones activas todavía están abiertas a la versión anterior de la base de datos
  6. Service worker hangs and never installs

In our case, the cache is invalidated on the service worker installation, so if the service worker was never installed, the users never received the updated application.

Make it tough

Aunque los scripts de los trabajadores de servicios se ejecutan en segundo plano, también se pueden finalizar en cualquier momento, incluso cuando se encuentran en medio de operaciones de E / S (red, IDB, etc.). Cualquier proceso de larga duración debería poder reanudarse en cualquier momento.

En el caso de un proceso de sincronización que cargó archivos grandes al server y se guardó en el BID, nuestra solución para las cargas parciales interrumpidas fue aprovechar el sistema reanudable de nuestra biblioteca de carga interna, guardando la URL de carga reanudable en el BID antes de cargar y usar esa URL para reanudar una carga si no se completó la primera vez. Además, antes de cualquier operación de E / S de larga duración, el estado se guardaba en IDB para indicar en qué parte del proceso estábamos para cada registro.

Don't depend on the global state

Because service workers exist in a different context, many symbols that you might expect to exist are not present. Much of our code ran on both window context, as well as a service worker context (such as registration, flags, sync, etc.). The code should be defensive about the services you use, such as local storage or cookies. You can use
globalThis

to refer to the global object in a way that works in all contexts. Also use data stored in global variables sparingly, as there is no guarantee when the script will terminate and state will be evicted.

Local development

An important component of service workers is caching resources locally. However, during development this is the opposite than you want, especially when updates are done lazily. You still want to install the server worker so you can debug problems with it or work with other APIs such as background sync or notifications. In Chrome, you can achieve this through Chrome DevTools by enabling the Bypass for network checkboxRequest panel> Service workers panel) in addition to enabling the Disable cache check box in the Net panel para deshabilitar también la memoria caché. Para cubrir más browsers, optamos por una solución diferente al incluir una marca para deshabilitar el almacenamiento en caché en nuestro trabajador de servicio, que está habilitado de forma predeterminada en las compilaciones de desarrolladores. Esto asegura que los desarrolladores siempre obtengan sus cambios más recientes sin problemas de almacenamiento en caché. Es importante incluir el Cache-Control: no-cache encabezado también para evitar que el browser almacene en caché cualquier activo.

Lighthouse

Lighthouse proporciona una serie de herramientas de depuración útiles para las PWA. Escanea un sitio y genera informes que cubren PWA, rendimiento, accessibility, SEO y otras mejores prácticas.
We recommend running Lighthouse in continuous integration to let you know if you don't meet any of the criteria for being a PWA. This actually happened to us once, where the service worker wasn't installing and we didn't notice a production boost before. Having Lighthouse as part of our CI would have prevented it.

Embrace continuous delivery

Because service workers can update automatically, users lack the ability to limit updates. This significantly reduces the number of outdated customers in the wild. When the user opened our app, the service worker served the old customer while lazily downloading the new customer. Once the new client is downloaded, it will ask the user to refresh the page to access new features. Even if the user ignored this request, the next time he refreshed the page, he would receive the new version of the client. As a result, it is quite difficult for a user to reject updates in the same way that it can for native applications.

We were able to drive major changes to the backend with a very short migration time for clients. We generally give users one month to upgrade to newer clients before making major changes. Since the application would work while it was out of date, it was possible that older clients would exist in the wild if the user had not opened the application for a long time. On iOS, service workers are
evicted after a couple of weeks
por lo que este caso no sucede. Para Android, este problema podría mitigarse si no se publica mientras está obsoleto o si el contents caduca manualmente después de algunas semanas. En la práctica, nunca encontramos problemas con clientes obsoletos. Cuán estricto quiere ser un equipo dado aquí depende de su caso de uso específico, pero las PWA brindan una flexibilidad significativamente mayor que las aplicaciones nativas.

Get cookie values in a service worker

Sometimes it is necessary to access cookie values in the context of a service worker. In our case, we needed to access the cookie values to generate a token to authenticate the originating API requests. In a service worker, synchronous APIs like document.cookies no están disponibles. Siempre puede enviar un mensaje a los clientes activos (con ventana) desde el trabajador del servicio para solicitar los valores de las cookies, aunque es posible que el trabajador del servicio se ejecute en segundo plano sin ningún cliente con ventana disponible, como durante una sincronización en segundo plano. Para solucionar esto, creamos un punto final en nuestro servidor frontend que simplemente devolvía el valor de la cookie al cliente. El trabajador del servicio realizó una solicitud de red a este punto final y leyó la respuesta para obtener los valores de las cookies.

With the launch of the
Cookie store API, this workaround should no longer be necessary for browsers that support it as it provides asynchronous access to browser cookies and can be used directly by the service worker.

Pitfalls for non-generated service workers

Make sure the service worker script changes if you change any static cached files

A common PWA pattern is for a service worker to install all static application files during their
install phase, which allows clients to directly access the caching API cache for all subsequent visits. Service workers are only installed when the browser detects that the service worker script has changed in some way, so we had to make sure that the service worker script file changed in some way when changed a cached file. We did this manually by embedding a hash of the set of static resource files within our service worker script, so each version produced a different service worker JavaScript file. Service worker libraries such as
Workbox Automate this process for you.

Unit Exam

The service worker APIs work by adding event listeners to the global object. For example:

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

Esto puede ser difícil de probar porque necesita simular el disparador del event, el objeto del evento, esperar el respondWith () callback, and then wait for the promise, before finally asserting the result. An easier way to structure this is to delegate the entire implementation to another file, which is easier to test.

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

Due to the difficulties of unit testing a service worker script, we kept the core service worker script as basic as possible, dividing most of the implementation into other modules. Since those files were just standard JS modules, they could be more easily unit tested with standard test libraries.

Stay tuned for parts 2 and 3

In parts 2 and 3 of this series we will talk about media management and issues specific to iOS. If you want to ask us more about creating a PWA on Google, visit our author profiles to find out how to contact us: