Memorizzazione delle risorse nella cache durante il runtime

Alcuni asset della tua applicazione web potrebbero essere utilizzati di rado, essere molto grandi o variare in base al dispositivo (ad esempio, le immagini adattabili) o alla lingua dell'utente. In questi casi la pre-memorizzazione nella cache può essere un anti-pattern ed è quindi necessario fare affidamento sulla memorizzazione nella cache di runtime.

In Workbox puoi gestire la memorizzazione nella cache di runtime per gli asset utilizzando il modulo workbox-routing per creare corrispondenze tra le route e gestire le relative strategie di memorizzazione nella cache con il modulo workbox-strategies.

Strategie di memorizzazione nella cache

Puoi gestire la maggior parte delle route per gli asset con una delle strategie di memorizzazione nella cache integrate. Sono stati trattati in dettaglio in precedenza in questa documentazione, ma eccone alcuni che vale la pena riassumere:

  • Inattiva durante la riconvalida utilizza una risposta memorizzata nella cache per una richiesta, se disponibile, e aggiorna la cache in background con una risposta dalla rete. Pertanto, se l'asset non viene memorizzato nella cache, attenderà la risposta della rete e la utilizzerà. È una strategia abbastanza sicura, poiché aggiorna regolarmente le voci della cache che la utilizzano. Lo svantaggio è che richiede sempre un asset alla rete in background.
  • Network First tenta prima di ottenere una risposta dalla rete. Se viene ricevuta una risposta, la passa al browser e la salva in una cache. Se la richiesta di rete non va a buon fine, verrà utilizzata l'ultima risposta memorizzata nella cache, consentendo l'accesso offline all'asset.
  • Prima cache verifica se nella cache è presente una risposta e la utilizza, se disponibile. Se la richiesta non è presente nella cache, viene utilizzata la rete e qualsiasi risposta valida viene aggiunta alla cache prima di essere trasmessa al browser.
  • Solo rete impone che la risposta provenga dalla rete.
  • Solo cache impone che la risposta provenga dalla cache.

Puoi applicare queste strategie per selezionare le richieste utilizzando i metodi offerti da workbox-routing.

Applicazione di strategie di memorizzazione nella cache con corrispondenza delle route

workbox-routing espone un metodo registerRoute per abbinare le route e gestirle con una strategia di memorizzazione nella cache. registerRoute accetta un oggetto Route che a sua volta accetta due argomenti:

  1. Una stringa, un'espressione regolare o un callback di corrispondenza per specificare i criteri di corrispondenza del percorso.
  2. Un gestore del percorso, in genere una strategia fornita da workbox-strategies.

I callback di corrispondenza sono preferiti per trovare corrispondenze con le route, in quanto forniscono un oggetto di contesto che include l'oggetto Request, la stringa URL della richiesta, l'evento di recupero e un valore booleano che indica se la richiesta è una richiesta della stessa origine.

Il gestore gestisce quindi la route corrispondente. Nell'esempio seguente, viene creata una nuova route che corrisponde alle richieste di immagine della stessa origine in arrivo, applicando prima la cache, quindi tornando alla strategia di rete.

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';

// A new route that matches same-origin image requests and handles
// them with the cache-first, falling back to network strategy:
const imageRoute = new Route(({ request, sameOrigin }) => {
  return sameOrigin && request.destination === 'image'
}, new CacheFirst());

// Register the new route
registerRoute(imageRoute);

Utilizzo di più cache

Workbox ti consente di raggruppare le risposte memorizzate nella cache in istanze Cache separate utilizzando l'opzione cacheName disponibile nelle strategie in bundle.

Nell'esempio seguente, le immagini utilizzano una strategia di riconvalida mentre è inattiva, mentre gli asset CSS e JavaScript usano una strategia di fallback di rete con priorità alla cache. Il routing di ogni asset inserisce le risposte in cache separate, aggiungendo la proprietà cacheName.

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';

// Handle images:
const imageRoute = new Route(({ request }) => {
  return request.destination === 'image'
}, new StaleWhileRevalidate({
  cacheName: 'images'
}));

// Handle scripts:
const scriptsRoute = new Route(({ request }) => {
  return request.destination === 'script';
}, new CacheFirst({
  cacheName: 'scripts'
}));

// Handle styles:
const stylesRoute = new Route(({ request }) => {
  return request.destination === 'style';
}, new CacheFirst({
  cacheName: 'styles'
}));

// Register routes
registerRoute(imageRoute);
registerRoute(scriptsRoute);
registerRoute(stylesRoute);
Uno screenshot di un elenco di istanze Cache nella scheda dell'applicazione dei DevTools di Chrome. Vengono mostrate tre cache distinte: una denominata "script", un'altra "stili" e l'ultima "immagini".
Il visualizzatore dello spazio di archiviazione della cache nel riquadro Applicazione di Chrome DevTools. Le risposte per i diversi tipi di asset vengono memorizzate in cache separate.

Impostazione di una scadenza per le voci della cache

Tieni presente le quote di archiviazione quando gestisci le cache dei service worker. ExpirationPlugin semplifica la manutenzione della cache ed è esposto da workbox-expiration. Per utilizzarlo, specificalo nella configurazione di una strategia di memorizzazione nella cache:

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';

// Evict image cache entries older thirty days:
const imageRoute = new Route(({ request }) => {
  return request.destination === 'image';
}, new CacheFirst({
  cacheName: 'images',
  plugins: [
    new ExpirationPlugin({
      maxAgeSeconds: 60 * 60 * 24 * 30,
    })
  ]
}));

// Evict the least-used script cache entries when
// the cache has more than 50 entries:
const scriptsRoute = new Route(({ request }) => {
  return request.destination === 'script';
}, new CacheFirst({
  cacheName: 'scripts',
  plugins: [
    new ExpirationPlugin({
      maxEntries: 50,
    })
  ]
}));

// Register routes
registerRoute(imageRoute);
registerRoute(scriptsRoute);

Rispettare le quote di archiviazione può essere complicato. È buona norma prendere in considerazione gli utenti che potrebbero avere difficoltà a utilizzare lo spazio di archiviazione o che vogliono utilizzare al meglio il proprio spazio di archiviazione. Le coppie di ExpirationPlugin di Workbox possono essere utili per raggiungere questo obiettivo.

Considerazioni multiorigine

L'interazione tra il service worker e gli asset multiorigine è notevolmente diversa rispetto agli asset della stessa origine. La condivisione delle risorse tra origini (CORS) è complicata e questa complessità si estende al modo in cui gestisci le risorse multiorigine in un service worker.

Risposte opache

Quando effettui una richiesta multiorigine in modalità no-cors, la risposta può essere archiviata nella cache di un service worker e persino utilizzata direttamente dal browser. Tuttavia, il corpo della risposta non può essere letto tramite JavaScript. Questo processo è noto come risposta opaca.

Le risposte opache sono una misura di sicurezza destinata a impedire l'ispezione di un asset multiorigine. Puoi comunque effettuare richieste di asset multiorigine e persino memorizzarli nella cache, ma non puoi leggere il corpo della risposta o nemmeno leggere il codice di stato.

Ricordati di attivare la modalità CORS

Anche se carichi asset multiorigine che impostano intestazioni CORS permissive che consentono di leggere le risposte, il corpo della risposta multiorigine potrebbe essere ancora opaco. Ad esempio, il codice HTML riportato di seguito attiverà richieste no-cors che genereranno risposte opache indipendentemente dalle intestazioni CORS impostate:

<link rel="stylesheet" href="https://example.com/path/to/style.css">
<img src="https://example.com/path/to/image.png">

Per attivare esplicitamente una richiesta cors che genererà una risposta non opaca, devi attivare esplicitamente la modalità CORS aggiungendo l'attributo crossorigin al codice HTML:

<link crossorigin="anonymous" rel="stylesheet" href="https://example.com/path/to/style.css">
<img crossorigin="anonymous" src="https://example.com/path/to/image.png">

Questo è importante da ricordare quando le route nel tuo service worker memorizzano le sottorisorse nella cache caricate in fase di runtime.

La casella di lavoro potrebbe non memorizzare nella cache le risposte opache

Per impostazione predefinita, Workbox adotta un approccio prudente nella memorizzazione nella cache delle risposte opache. Poiché è impossibile esaminare il codice di risposta alla ricerca di risposte opache, la memorizzazione nella cache di una risposta di errore può causare un'interruzione persistente dell'esperienza se si utilizza una strategia cache-first o cache-only.

Se devi memorizzare nella cache una risposta opaca in Workbox, devi utilizzare una strategia network-first o stale-while-validate per gestirla. Sì, significa che l'asset verrà comunque richiesto dalla rete ogni volta, ma garantisce che le risposte in errore non vengano mantenute e alla fine saranno sostituite da risposte utilizzabili.

Se utilizzi un'altra strategia di memorizzazione nella cache e viene restituita una risposta opaca, Workbox ti avvisa che la risposta non è stata memorizzata nella cache in modalità di sviluppo.

Forza la memorizzazione nella cache delle risposte opache

Se sei assolutamente sicuro di voler memorizzare nella cache una risposta opaca utilizzando una strategia cache-first o solo cache, puoi forzare Workbox a farlo con il modulo workbox-cacheable-response:

import {Route, registerRoute} from 'workbox-routing';
import {NetworkFirst, StaleWhileRevalidate} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';

const cdnRoute = new Route(({url}) => {
  return url === 'https://cdn.google.com/example-script.min.js';
}, new CacheFirst({
  plugins: [
    new CacheableResponsePlugin({
      statuses: [0, 200]
    })
  ]
}))

registerRoute(cdnRoute);

Risposte opache e API navigator.storage

Per evitare fughe di informazioni tra domini, è stata aggiunta una spaziatura interna significativa alle dimensioni di una risposta opaca utilizzata per calcolare i limiti della quota di archiviazione. Questo influisce sul modo in cui l'API navigator.storage segnala le quote di spazio di archiviazione.

La spaziatura interna varia a seconda del browser ma, per Chrome, la dimensione minima che ogni singola risposta opaca memorizzata nella cache contribuisce allo spazio di archiviazione complessivo utilizzato è di circa 7 megabyte. Tieni presente questo aspetto quando determini il numero di risposte opache da memorizzare nella cache, poiché potresti facilmente superare le quote di archiviazione molto prima di quanto ti aspetteresti.