Frontend

Progressive Web App: i concetti di base

Autore

Manuel Ricci

La costante ricerca di engagement con la propria base utenti, il voler offrire il meglio il più in fretta possibile, sono due costanti che caratterizzano le necessità di chi vuole usare il web per poter rafforzare la propria presenza sul mercato.

Il mondo digitale è in perenne evoluzione e il risultato è quello di ottenere tecnologie sempre più performanti e che massimizzino l'esperienza utente riducendo la frizione nell'utilizzo di software e hardware.

Performance e User Experience, sono proprio questi i concetti alla base delle Progressive Web App (in breve PWA).

Cosa sono le Progressive Web App?

Le Progressive Web App sono il connubio perfetto tra le app native e le tecnologie web, le quali, grazie alla loro unione, danno vita ad un nuovo strumento di business, ma soprattutto uno strumento che possa soddisfare le necessità di cui parlavo nel primo paragrafo.

Le PWA sono affidabili, veloci e creano engagement.

Affidabilità

Quando vengono lanciate dalla home screen dell'utente, i service workers abilitano una Progressive Web App a caricare istantaneamente, anche nel caso in cui la connessione ad Internet non sia stabile.

Un service worker, scritto in JavaScript, ci permette di controllare come rispondere alle richieste delle risorse e la cache. Pre-inserendo in quest'ultima le risorse chiave, puoi eliminare la dipendenza dalla rete, assicurando agli utenti un'esperienza di navigazione veloce.

Velocità

Il 53% degli utenti abbandona un sito se impiega più di 3 secondi a caricare. Una volta caricato, l'utente si aspetta una navigazione fluida, invece la maggior parte delle volte lo attende una navigazione molto lenta.

Grazie al lavoro dei service workers e ad una corretta gestione delle risorse in cache sarà possibile offrire un'esperienza di navigazione decisamente veloce e fluida.

Creano engagement

Le Progressive Web App sono installabili e sono raggiungibili dalla home screen del dispositivo dell'utente. Offrono un'esperienza full screen (senza interfaccia del browser) grazie all'aiuto di un file json chiamato manifest.

Il manifest, nome esteso, Web App Manifest, permette di controllare come l'app apparirà quando verrà lanciata. Potrai specificare un'icona per la home screen, la pagina che deve essere caricata quando viene lanciata l'app, l'orientamento dello schermo e se far apparire o meno l'interfaccia di Chrome.

Inoltre grazie all'uso delle notifiche push si potrà attirare l'attenzione dei propri utenti con messaggi e/o offerte personalizzate.

Le Progressive Web App, non occupano quasi niente in termini di spazio, solo qualche KB, anche perché il grosso del lavoro lo fa Chrome, che permette l'esecuzione di questi applicativi.

Tutti i concetti espressi in questi paragrafi introduttivi ti saranno più chiari nel frattempo che leggerai questo articolo. L'obiettivo è quello di offrirti una panoramica completa delle Progressive Web App.

Come avrai potuto evincere dai paragrafi precedenti, le PWA si reggono in sostanza su due componenti cardine: il manifest.json e i service worker. Cerchiamo di conoscerli meglio.

Il Manifest

Il Web App Manifest o semplicemente Manifest o Manifest.json è un semplice file JSON che contiene le informazioni di base sulla web application e come quest'ultima si deve comportare quanto viene installata. Il file manifest è obbligatorio per mostrare il prompt per procedere all'installazione A2HS (Add To Home Screen).

Un manifest di base si presenta così:

manifest.json

1{
2  "name": "Super Cool PWA",
3  "short_name": "SCPWA",
4  "theme_color": "#256bd1",
5  "background_color": "#0fc135",
6  "display": "fullscreen",
7  "Scope": "/",
8  "start_url": "/?utm_source=pwa",
9  "icons": [
10    {
11      "src": "images/icons/icon-72x72.png",
12      "sizes": "72x72",
13      "type": "image/png"
14    },
15    {
16      "src": "images/icons/icon-96x96.png",
17      "sizes": "96x96",
18      "type": "image/png"
19    },
20    {
21      "src": "images/icons/icon-128x128.png",
22      "sizes": "128x128",
23      "type": "image/png"
24    },
25    {
26      "src": "images/icons/icon-144x144.png",
27      "sizes": "144x144",
28      "type": "image/png"
29    },
30    {
31      "src": "images/icons/icon-152x152.png",
32      "sizes": "152x152",
33      "type": "image/png"
34    },
35    {
36      "src": "images/icons/icon-192x192.png",
37      "sizes": "192x192",
38      "type": "image/png"
39    },
40    {
41      "src": "images/icons/icon-384x384.png",
42      "sizes": "384x384",
43      "type": "image/png"
44    },
45    {
46      "src": "images/icons/icon-512x512.png",
47      "sizes": "512x512",
48      "type": "image/png"
49    }
50  ],
51  "splash_pages": null
52}

Solitamente per la generazione mi affido a questo comodo generator, ma nulla toglie che possa essere fatto a manina.

Nel manifest.json possono esserci i seguenti valori:

  • name e short_name: almeno uno di questi due parametri deve essere definito. Qualora venissero dichiarati entrambi short_name verrebbe usato nella homescreen, nel launcher e ovunque lo spazio sia limitato, mentre name verrebbe usato nel banner A2HS.
  • icons: quando un utente aggiunge la PWA alla sua homescreen le icone vengono impiegate per l'icona della home screen stessa, nel task manager, nella splash screen, nell'app launcher, ecc. Il parametro icons è un array di oggetti che contengono le proprietà: src, type, sizes. Obbligatorie sono le icone da 192×192 e da 512×512 Chrome si occuperà dell'eventuale resizing. Qualora siano necessarie delle icone personalizzate si raccomandano dimensioni incrementali da 48dp.
  • start_url: il parametro permette di definire un punto di partenza per quando l'app viene avviata. Questo permette di ripartire sempre dalla "home" nel caso la PWA venisse chiusa in seguito alla chiusura della app attive o al riavvio del dispositivo. Una best practice molto utile è quella di usare il parametro utm_source (usato da Google Analytics) per analizzare il traffico proveniente dalla PWA.
  • background_color: usato nella splash screen come colore di sfondo
  • display: il parametro definisce "quanta UI del browser deve essere mostrata", i valori possono essere:
    • fullscreen: non c'è interfaccia del browser, la PWA viene eseguita a pieno schermo. Questo valore permette di mostrare il banner A2HS.
    • standalone: look and feel di un app nativa, viene eseguita in una finestra separata dal browser e non presenta nessun componente della UI.
    • minimal_ui: simile a full screen ma dà accesso ad alcuni componenti del browser come la navigazione della cronologia (avanti e indietro). Non è supportata da Chrome.
    • browser: Esperienza standard con UI del browser completa.
  • orientation: Permette di definire l'orientamento dell'app. Utile nel caso di videogame che prevedono un solo orientamento dello schermo.
  • scope: l'insieme di URL che il browser deve considerare parte della PWA.. Se lo scope non venisse definito verrà usato il percorso nella quale il manifest.json risiede.
  • theme_color: il colore che verrà usato nella toolbar e nello splash screen

La splash screen viene generata in automatico in base ai valori definiti nel manifest. Per avvisare il browser dell'esistenza del documento json bisognerà inserire nella head del documento il tag link come segue:

head del documento html

1<link rel="manifest" href="manifest.json" />

Il banner A2HS

Per poter mostrare il banner per aggiungere alla home screen la progressive web app, bisogna rispettare dei criteri di base, nello specifico:

  • Includere il manifest.json, il quale dovrà avere almeno questi valori:
    • short_name o name
    • icons
    • start_url
    • display impostato a fullscreen, standalone o minimal_ui
  • Deve essere servita in HTTPS
  • La web app non è installata sul dispositivo
  • L'utente deve aver interagito con l'app per almeno 30 secondi
  • prefer_related_applications non è impostata a true
  • C'è un Service Worker registrato con metodo fetch

Quando questi criteri vengono rispettati viene emesso l'evento beforeinstallprompt che mostrerà l'avviso per installare la PWA.

Al momento dell'installazione la maggior parte del lavoro verrà svolto dal browser che creerà una webAPK per garantire un'esperienza più integrata.

Se vuoi creare un pulsante personalizzato per aggiungere alla home screen la PWA dovrai ascoltare con JavaScript beforeinstallprompt.

Quando l'evento viene lanciato bisogna salvare la referenza per usarla successivamente per mostrare all'utente il prompt per installare l'app.

1let deferredPrompt;
2
3window.addEventListener("beforeinstallprompt", (e) => {
4  // Previene in Chrome 67 e precedenti la comparsa del banner.
5  e.preventDefault();
6  // Salva la referenza dell'evento affinché possa essere usato successivamente.
7  deferredPrompt = e;
8});

In seguito bisogna notificare l'utente che la PWA può essere installata. Meglio se attraverso un bottone o un elemento della UI piuttosto che popup o banner invadenti.

1let deferredPrompt;
2
3window.addEventListener("beforeinstallprompt", (e) => {
4  // Previene in Chrome 67 e precedenti la comparsa del banner.
5  e.preventDefault();
6  // Salva la referenza dell'evento affinché possa essere usato successivamente.
7  deferredPrompt = e;
8  // Aggiorna la UI notificando l'utente che possono installare l'app nella home screen.
9  btnAdd.style.display = "block";
10});

Per mostrare il prompt bisogna chiamare il metodo prompt() dall'evento beforeinstallprompt di cui è stata salvata la referenza in precedenza. Il metodo prompt() può essere chiamato solo una volta per evento differito.

Il metodo mostrerà una dialog che chiederà all'utente di aggiungere la PWA alla propria home screen.

Bisognerà rimanere in attesa della promise che la proprietà userChoice restituisce. Tale promise sarà un oggetto che conterrà la proprietà outcome, la quale avrà due valori: accepted o rejected.

1btn.addEventListener("click", (e) => {
2  // Nasconde il bottone.
3  btnAdd.style.display = "none";
4  // Mostra il prompt
5  deferredPrompt.prompt();
6
7  deferredPrompt.userChoice.then((choiceResult) => {
8    if (choiceResult.outcome === "accepted") {
9      console.log("L'utente ha accettato il prompt A2HS");
10    } else {
11      console.log("L'utente ha rifiutato il prompt A2HS");
12    }
13    deferredPrompt = null;
14  });
15});

Il Service Worker

Il Service Worker è una tipologia di web worker eseguito in un thread separato del browser. Tra le tante cose intercetta richieste da parte della rete e gestisce informazioni in ingresso e in uscita dalla cache.

L'essere separato dal thread principale del browser permette al service worker di:

  • Lavorare completamente in asincrono, tant'è che non è possibile usare la localStorage e il metodo sincrono XHR. Essendo completamente asincrono fa largo uso delle promise.
  • Può ricevere informazioni dal server anche quando l'applicazione non è in esecuzione, permettendo quindi l'invio di push notification.
  • Non può comunicare direttamente con il DOM, ma usa il metodo postMessage() per inviare dati alla pagina.

In breve un service worker è un proxy che permette di decidere come gestire le richieste che avvengono nella rete. Funziona esclusivamente via HTTPS perché avendo la possibilità di intercettare le richieste dalla rete e di modificarle, il rischio di un attacco "man-in-the-middle" sarebbe troppo alto con un protocollo non criptato.

Il service worker se non utilizzato diventa inattivo e si riavvia ogni qualvolta se ne ha bisogno. Se bisogna mantenere qualcosa in memoria si può utilizzare l'indexedDB.

Come funziona un service worker?

Il service worker funziona principalmente con due API:

  • Fetch, per recuperare informazioni dalla rete
  • Cache, contenitore persistente di alcune risorse utili per il funzionamento offline o semplicemente per migliorare le performance della web app.

Il ciclo di vita di un service worker

Il ciclo di vita di un service worker è suddiviso in tre fasi: registrazione, installazione e attivazione; vediamoli di seguito.

Registrazione

La registrazione avviene riportando nel proprio file javascript principale dove si trova il file con tutte le direttive.

Registrazione Service Worker

1if ("serviceWorker" in navigator) {
2  navigator.serviceWorker
3    .register("/service-worker.js")
4    .then(function (registration) {
5      console.log("Registration successful, scope is:", registration.scope);
6    })
7    .catch(function (error) {
8      console.log("Service worker registration failed, error:", error);
9    });
10}

Installazione

Una volta registrato il service worker viene tentata l'installazione. Tale installazione emette un evento 'install' che può essere intercettato per eseguire delle task, come ad esempio il precache di alcune risorse per velocizzare l'app agli avvii successivi.

Installazione del Service Worker

1// Ascoltiamo l'evento install e procediamo con una callback
2self.addEventListener("install", function (event) {
3  // Esecuzione di alcune task
4});

Attivazione

Una volta installato il service worker passa in uno stato di attivazione (o waiting se ci fossero istanze del vecchio service worker ancora in esecuzione). L'attivazione emette l'evento 'activate' il momento ideale per pulire cache e per mantenere sempre tutto ottimizzato e in ordine.

Attivazione del Service Worker

1self.addEventListener("activate", function (event) {
2  // Esegui delle task
3});

Fine della parte teorica

Molto bene, tutto quello che bisogna introdurre a livello teorico è stato introdotto, spero in maniera chiara. Più avanti farò un giga tutorial per creare la tua prima Progressive Web App, motivo per cui ti consiglio di seguirmi sui social o di iscriverti alla newsletter per ricevere gli update

Grazie per aver letto!

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.