Service worker e modello shell dell'applicazione

Una caratteristica dell'architettura comune delle applicazioni web a pagina singola (SPA) è un insieme minimo di HTML, CSS e JavaScript necessari per potenziare le funzionalità globali di un'applicazione. In pratica, tendono a essere l'intestazione, la navigazione e altri elementi comuni dell'interfaccia utente che rimangono in tutte le pagine. Quando un service worker prememorizza nella cache il codice HTML minimo e gli asset dipendenti di questa UI minima, chiamiamo shell dell'applicazione.

Diagramma della shell di un'applicazione. È lo screenshot di una pagina web con un'intestazione in alto e un'area di contenuti in basso. l'intestazione è "shell dell'applicazione", mentre la parte inferiore è etichettata come "contenuti".

La shell dell'applicazione svolge un ruolo significativo nelle prestazioni percepite di un'applicazione web. È la prima cosa che viene caricata e, pertanto, è anche la prima cosa che gli utenti vedono mentre attendono che i contenuti vengano completati nell'interfaccia utente.

Sebbene la shell dell'applicazione sia veloce da caricare, a condizione che la rete sia disponibile e almeno un po' veloce, un service worker che pre-memorizza nella cache la shell dell'applicazione e gli asset associati offre al modello della shell dell'applicazione questi vantaggi aggiuntivi:

  • Rendimento affidabile e costante nelle visite ripetute. Alla prima visita a un'app senza un service worker installato, il markup dell'applicazione e gli asset associati devono essere caricati dalla rete prima che il service worker possa inserirli nella sua cache. Tuttavia, in caso di visite ripetute, la shell dell'applicazione verrà estratta dalla cache, il che significa che il caricamento e il rendering saranno istantanei.
  • Accesso affidabile alle funzionalità in scenari offline. A volte l'accesso a internet è instabile o assente e il temuto schermo "non riusciamo a trovare quel sito web" si alza. Il modello della shell dell'applicazione risolve questo problema rispondendo a qualsiasi richiesta di navigazione con il markup della shell dell'applicazione dalla cache. Anche se un utente visita un URL nella tua applicazione web che non ha mai visto prima, la shell dell'applicazione viene fornita dalla cache e può essere compilata con contenuti utili.

Quando utilizzare il modello shell dell'applicazione

Una shell dell'applicazione è più comprensibile quando hai elementi comuni dell'interfaccia utente che non cambiano da route a route, ma i contenuti sì. È probabile che la maggior parte delle SPA utilizzi quello che è già un modello shell dell'applicazione.

Se questo descrive il tuo progetto e vuoi aggiungere un service worker per migliorarne l'affidabilità e le prestazioni, la shell dell'applicazione deve:

  • Caricamento veloce.
  • Utilizza asset statici di un'istanza Cache.
  • Includere elementi dell'interfaccia comuni, ad esempio un'intestazione e una barra laterale, separati dai contenuti della pagina.
  • Recuperare e visualizzare contenuti specifici di una pagina.
  • Se necessario, memorizza nella cache i contenuti dinamici per la visualizzazione offline.

La shell dell'applicazione carica in modo dinamico i contenuti specifici delle pagine tramite le API o i contenuti integrati in JavaScript. Dovrebbe inoltre aggiornarsi automaticamente, nel senso che, se il markup della shell dell'applicazione cambia, un aggiornamento del service worker deve recuperare la nuova shell dell'applicazione e memorizzarla automaticamente nella cache.

Creazione della shell dell'applicazione

La shell dell'applicazione deve esistere indipendentemente dai contenuti, ma fornire una base per il completamento dei contenuti al suo interno. Idealmente, dovrebbe essere il più sottile possibile, ma includere nel download iniziale contenuti abbastanza significativi da far capire all'utente che un'esperienza si carica rapidamente.

Il giusto equilibrio dipende dall'app. La shell dell'app Trained To Thrill di Jake Archibald include un'intestazione con un pulsante di aggiornamento per recuperare nuovi contenuti da Flickr.

Uno screenshot dell'app web Trained to Thrill in due stati diversi. A sinistra è visibile solo la shell dell'applicazione memorizzata nella cache, senza contenuto completato. A destra, i contenuti (alcune immagini di alcuni treni) vengono caricati dinamicamente nell'area dei contenuti della shell dell'applicazione.

Il markup della shell dell'applicazione varia da progetto a progetto, ma ecco un esempio di file index.html che fornisce il boilerplate dell'applicazione:

​​<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Application Shell Example
    </title>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="styles/global.css">
  </head>
  <body>
    <header class="header">
      <!-- Application header -->
      <h1 class="header__title">Application Shell Example</h1>
    </header>

    <nav class="nav">
      <!-- Navigation items -->
    </nav>

    <main id="app">
      <!-- Where the application content populates -->
    </main>

    <div class="loader">
      <!-- Spinner/content placeholders -->
    </div>

    <!-- Critical application shell logic -->
    <script src="app.js"></script>

    <!-- Service worker registration script -->
    <script>
      if ('serviceWorker' in navigator) {
        // Register a service worker after the load event
        window.addEventListener('load', () => {
          navigator.serviceWorker.register('/sw.js');
        });
      }
    </script>
  </body>
</html>

Indipendentemente dalla costruzione di una shell dell'applicazione per il progetto, questa deve avere le seguenti caratteristiche:

  • Il codice HTML deve presentare aree chiaramente isolate per i singoli elementi dell'interfaccia utente. Nell'esempio precedente, sono inclusi l'intestazione, la navigazione, l'area dei contenuti principali e lo spazio per uno "spinner" di caricamento che viene visualizzato solo durante il caricamento dei contenuti.
  • Il codice JavaScript e CSS iniziale caricato per la shell dell'applicazione deve essere minimo e riguardare solo la funzionalità della shell dell'applicazione stessa e non i contenuti. In questo modo l'applicazione esegue il rendering della shell il più velocemente possibile e riduce al minimo il lavoro del thread principale fino alla visualizzazione del contenuto.
  • Uno script incorporato che registra un service worker.

Dopo aver creato la shell dell'applicazione, puoi creare un service worker per memorizzare nella cache sia la shell dell'applicazione sia i relativi asset.

Memorizzazione nella cache della shell dell'applicazione

La shell dell'applicazione e gli asset richiesti sono ciò che il service worker deve pre-memorizzare immediatamente nella cache al momento dell'installazione. Supponendo che la shell di un'applicazione come quella dell'esempio precedente, vediamo come si potrebbe procedere in un esempio di base di lavoro usando workbox-build:

// build-sw.js
import {generateSW} from 'workbox-build';

// Where the generated service worker will be written to:
const swDest = './dist/sw.js';

generateSW({
  swDest,
  globDirectory: './dist',
  globPatterns: [
    // The necessary CSS and JS for the app shell
    '**/*.js',
    '**/*.css',
    // The app shell itself
    'shell.html'
  ],
  // All navigations for URLs not precached will use this HTML
  navigateFallback: 'shell.html'
}).then(({count, size}) => {
  console.log(`Generated ${swDest}, which precaches ${count} assets totaling ${size} bytes.`);
});

Questa configurazione archiviata in build-sw.js importa il codice CSS e JavaScript dell'app, incluso il file di markup della shell dell'applicazione contenuto in shell.html. Lo script viene eseguito con Node in questo modo:

node build-sw.js

Il service worker generato viene scritto in ./dist/sw.js e al termine registrerà il seguente messaggio:

Generated ./dist/sw.js, which precaches 5 assets totaling 44375 bytes.

Quando la pagina viene caricata, il service worker pre-memorizza nella cache il markup della shell dell'applicazione e le sue dipendenze:

Uno screenshot del riquadro Rete negli DevTools di Chrome che mostra un elenco di asset scaricati dalla rete. Gli asset prememorizzati nella cache dal service worker vengono distinti dagli altri asset con un ingranaggio sulla sinistra nella riga. Diversi file JavaScript e CSS vengono pre-memorizzati nella cache dal service worker al momento dell&#39;installazione.
Il service worker pre-memorizza nella cache le dipendenze della shell dell'applicazione al momento dell'installazione. Le richieste di pre-memorizzazione nella cache sono le ultime due righe e l'icona a forma di ingranaggio accanto alla richiesta indica che il service worker ha gestito la richiesta.

La pre-memorizzazione del codice HTML, CSS e JavaScript della shell dell'applicazione è possibile in quasi tutti i flussi di lavoro, inclusi i progetti che utilizzano bundler. Man mano che procedi nella documentazione, imparerai a utilizzare direttamente Workbox per configurare la tua Toolchain e creare un service worker ottimale per il tuo progetto, indipendentemente dal fatto che si tratti di un'APS.

Conclusione

La combinazione del modello shell dell'applicazione con un service worker è un'ottima soluzione per la memorizzazione nella cache offline, in particolare se unisci la sua funzionalità di pre-memorizzazione alla cache con una strategia network-first per il fallimento della cache per il markup o le risposte dell'API. Il risultato è un'esperienza veloce in modo affidabile che esegue il rendering immediato della shell dell'applicazione in occasione di visite ripetute, anche in condizioni offline.