Creare una PWA in Google, parte 1

Ciò che il team di bollettino ha imparato sui lavoratori dei servizi durante lo sviluppo di una PWA.

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

Questo è il primo di una serie di post del blog sulle lezioni apprese dal team di Google bollettino. durante la creazione di una PWA rivolta all'esterno. In questi post condivideremo alcune sfide che abbiamo affrontato, gli approcci che abbiamo adottato per superarli e i consigli generali per evitare le insidie. Non è affatto per una panoramica completa delle PWA. L'obiettivo è condividere quanto appreso dall'esperienza del nostro team.

In questo primo post, daremo prima alcune informazioni di base, poi illustreremo in dettaglio tutti i cosa abbiamo imparato sui service worker.

Sfondo

Il bollettino era in fase di sviluppo da metà 2017 a metà del 2019.

Perché abbiamo scelto di creare una PWA

Prima di approfondire il processo di sviluppo, vediamo perché lo sviluppo di una PWA è stato interessante per questo progetto:

  • Capacità di iterazione rapida. Particolarmente importante perché bollettino sarebbe stato pilotato in in più mercati.
  • Singolo codebase. I nostri utenti erano divisi quasi equamente tra Android e iOS. Una PWA intendeva potevamo creare un'unica app web che funzionasse su entrambe le piattaforme. Ciò ha aumentato la velocità e l'impatto del team.
  • Aggiornamento rapido e indipendentemente dal comportamento degli utenti. le PWA possono aggiornare automaticamente riduce la quantità di client obsoleti in circolazione. Siamo riusciti a eseguire il push del backend di interruzione con un tempo di migrazione molto breve per i client.
  • Facilmente integrato con app proprietarie e di terze parti. Queste integrazioni erano un requisito per l'app. Con una PWA spesso significava semplicemente aprire un URL.
  • Hai rimosso le difficoltà legate all'installazione di un'app.

Il nostro framework

Per bollettino, abbiamo usato Polymer, ma qualsiasi modello moderno e ben supportato il modello di machine learning.

Cosa abbiamo imparato sui service worker

Non puoi avere una PWA senza un servizio worker. Service worker offrono molta potenza, ad esempio strategie avanzate di memorizzazione nella cache, funzionalità offline, sincronizzazione in background E così via. Anche se i service worker aggiungono un po' di complessità, abbiamo scoperto che i loro vantaggi superavano complessità.

Se puoi, genera

Evita di scrivere uno script del service worker a mano. La scrittura a mano dei service worker richiede l'uso manuale la gestione delle risorse memorizzate nella cache e la logica di riscrittura comuni alla maggior parte delle librerie dei Service worker, come Workbox.

Detto questo, a causa del nostro stack tecnico interno non abbiamo potuto utilizzare una libreria per generare e gestire al nostro service worker. Le informazioni apprese di seguito rispecchiano a volte questo aspetto. Vai su Insidie per Service worker non generati per ulteriori informazioni.

Non tutte le librerie sono compatibili con il service worker

Alcune librerie JS partono da ipotesi che non funzionano come previsto quando vengono eseguite da un service worker. Per supponendo che window o document siano disponibili oppure utilizzando un'API non disponibile per il servizio worker (XMLHttpRequest, spazio di archiviazione locale ecc.). Assicurati che tutte le librerie critiche di cui hai bisogno sono compatibili con il service worker. Per questa particolare PWA, abbiamo voluto usare gapi.js per l'autenticazione, ma erano non è in grado di farlo perché non supportava i service worker. Gli autori delle biblioteche devono anche ridurre o rimuovere ipotesi inutili sul contesto JavaScript ove possibile per supportare l'uso del service worker in altri casi, ad esempio evitando API incompatibili con i service worker ed evitando le .

Evita di accedere a IndexedDB durante l'inizializzazione

Non leggere IndexedDB quando inizializzare lo script del service worker, altrimenti potresti riscontrare questa situazione indesiderata:

  1. L'utente ha un'app web con IndexedDB (IDB) versione N
  2. Viene eseguito il push della nuova app web con IDB versione N+1
  3. L'utente visita la PWA e questo attiva il download del nuovo service worker
  4. Il nuovo service worker legge da IDB prima di registrare il gestore di eventi install, attivando un Ciclo di upgrade dell'IDB per passare da N a N+1
  5. Poiché l'utente ha un client precedente con la versione N, il processo di upgrade del service worker si blocca come attivo sono ancora aperte alla versione precedente del database
  6. Il Service worker si blocca e non esegue mai l'installazione

Nel nostro caso, la cache è stata invalidata al momento dell'installazione del service worker, quindi se il service worker non ha mai gli utenti non hanno mai ricevuto l'app aggiornata.

Rendilo resiliente

Sebbene gli script dei service worker vengano eseguiti in background, possono anche essere terminati in qualsiasi momento, nel bel mezzo delle operazioni di I/O (rete, IDB e così via). Qualsiasi processo a lunga esecuzione dovrebbe ripristinabile in qualsiasi momento.

Nel caso di un processo di sincronizzazione che caricava file di grandi dimensioni sul server e salvati su IDB, la nostra soluzione per i caricamenti parziali interrotti è stato l'uso del ripristinabile dalla nostra libreria di caricamento interna salvando l'URL di caricamento ripristinabile in IDB prima di caricarlo e usandolo per ripristinare se non è stato completato la prima volta. Inoltre, prima di qualsiasi operazione di I/O a lunga esecuzione, è stato salvato nell'IDB per indicare il punto del processo in cui ci trovavamo per ogni record.

Non dipendono dallo stato globale

Poiché i service worker esistono in un contesto diverso, molti simboli che potresti aspettarti non lo sono presenti. Gran parte del nostro codice è stato eseguito sia in un contesto window che in un contesto di service worker (come come logging, flag, sincronizzazione e così via). Il codice deve essere difensivo nei confronti dei servizi che utilizza, ad esempio archiviazione locale o cookie. Puoi utilizzare globalThis fare riferimento all'oggetto globale in modo da funzionare in tutti i contesti. Usa anche i dati memorizzati in variabili globali con parsimonia, in quanto non vi è alcuna garanzia su quando lo script verrà terminato lo stato sfrattato.

Sviluppo locale

Una componente importante dei service worker è la memorizzazione nella cache delle risorse localmente. Tuttavia, durante lo sviluppo, sia l'opposto di ciò che vuoi, in particolare quando gli aggiornamenti vengono eseguiti lentamente. Vuoi ancora il worker del server installato, in modo da poter eseguire il debug dei problemi o utilizzare altre API come la sincronizzazione in background o le notifiche. Su Chrome puoi farlo con Chrome DevTools attivando la casella di controllo Bypass per la rete (riquadro Application > Service worker) in oltre ad attivare la casella di controllo Disabilita cache nel riquadro Rete per disattivare la cache in memoria. Per includere più browser, abbiamo optato per un'altra soluzione incluso un flag per disabilitare la memorizzazione nella cache nel nostro service worker che è abilitato per impostazione predefinita le build. Ciò garantisce che gli sviluppatori ricevano sempre le modifiche più recenti senza problemi di memorizzazione nella cache. È è importante includere l'intestazione Cache-Control: no-cache per impedire al browser di memorizzando gli asset nella cache.

Faro

Lighthouse offre diverse funzionalità di debug utili per le PWA. Scansiona un sito e genera report relativi a PWA, rendimento accessibilità, SEO e altre best practice. Ti consigliamo di eseguire Lighthouse in modalità continua integrazione per ricevere un avviso se si interrompe una delle per essere una PWA. In realtà è successo una volta, quando il service worker non stava installando e non ce l'abbiamo realizzato prima di una spinta in produzione. Avere Lighthouse nella nostra CI avrebbe lo ha impedito.

Sfrutta la distribuzione continua

Poiché i service worker possono eseguire gli aggiornamenti automaticamente, gli utenti non hanno la possibilità di limitare gli upgrade. Questo riduce in modo significativo la quantità di clienti obsoleti. Quando l'utente ha aperto l'app, il service worker supportava il vecchio client mentre scaricava lentamente il nuovo client. Una volta scaricato un nuovo client, all'utente verrebbe chiesto di aggiornare la pagina per accedere alle nuove funzionalità. Anche se l'utente ha ignorato questa richiesta e la volta successiva che aggiornava la pagina avrebbe ricevuto la nuova all'ultima versione del client. Di conseguenza, è abbastanza difficile per un utente rifiutare gli aggiornamenti nello stesso per le app per iOS/Android.

Siamo stati in grado di eseguire il push delle modifiche al backend che provocano errori con un tempo di migrazione molto breve per clienti. In genere, concediamo un mese agli utenti per passare ai nuovi clienti prima di effettuare che interrompono le modifiche. Dal momento che l'app non funzionava, era possibile per i client meno recenti esistenti se l'utente non apre l'app da molto tempo. Su iOS, i Service worker Rimosso dopo un paio di settimane quindi questo caso non accade. Per Android, questo problema potrebbe essere mitigato se non viene pubblicato mentre vecchi o con scadenza manuale dopo alcune settimane. In pratica, non abbiamo mai riscontrato per risolvere i problemi da client inattivi. La severità di un determinato team dipende dal suo utilizzo specifico in questo caso, ma le PWA offrono una flessibilità notevolmente maggiore rispetto alle app per iOS/Android.

Recupero dei valori dei cookie in un service worker

A volte è necessario accedere ai valori dei cookie in un contesto di service worker. Nel nostro caso, necessari per accedere ai valori dei cookie per generare un token per autenticare le richieste API proprietarie. In un service worker, le API sincrone come document.cookies non sono disponibili. Puoi sempre inviare un messaggio ai client attivi (con finestre) dal service worker per richiedere i valori del cookie, tuttavia è possibile che il service worker venga eseguito in background senza client che rientrano in una finestra disponibili, ad esempio durante una sincronizzazione in background. Per ovviare a questo problema, abbiamo creato un endpoint server frontend che si limita a ricondurre il valore del cookie al client. Il service worker ha effettuato un una richiesta di rete a questo endpoint e leggere la risposta per ottenere i valori dei cookie.

Con il rilascio API Cookie Store, questa soluzione alternativa non dovrebbe più essere necessaria per i browser che la supportano, in quanto accesso asincrono ai cookie del browser e può essere utilizzato direttamente dal service worker.

Insidie per i service worker non generati

Assicurati che lo script del service worker venga modificato se vengono apportate modifiche a un file statico memorizzato nella cache

Un pattern PWA comune per un service worker prevede l'installazione di tutti i file statici dell'applicazione durante la sua fase install, che consente ai client di accedere alla cache dell'API Cache Storage direttamente per tutti le visite successive . I service worker vengono installati solo quando il browser rileva che il servizio lo script worker è cambiato in qualche modo, quindi abbiamo dovuto assicurarci che lo stesso file di script del service worker modificato in qualche modo quando un file memorizzato nella cache è stato modificato. Per farlo manualmente, incorporando un hash un set di file di risorse statico all'interno del nostro script del service worker, per cui ogni release generava un del Service worker. Librerie dei service worker come Workbox automatizza questo processo.

Test delle unità

Le API dei service worker funzionano aggiungendo listener di eventi all'oggetto globale. Ad esempio:

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

Può essere complicato testare questa operazione, perché devi simulare l'attivatore di evento, l'oggetto evento, il callback respondWith(), quindi attendi la promessa, prima di asserire infine il risultato. Un il modo più semplice per strutturare questo aspetto è delegare tutta l'implementazione a un altro file, che è più facile viene testato.

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

A causa delle difficoltà legate al test delle unità di uno script del service worker, abbiamo mantenuto il service worker principale uno script il più semplice possibile, suddividendo la maggior parte dell'implementazione in altri moduli. Dal giorno questi file erano solo moduli JS standard, potrebbero essere più facilmente testati nelle unità con test standard librerie.

Non perderti le parti 2 e 3

Nelle parti 2 e 3 di questa serie parleremo della gestione dei media e di problemi specifici di iOS. Se Vuoi chiederci di più sulla creazione di una PWA su Google, visita i nostri profili dell'autore per scoprirlo come contattarci: