La possibilità di navigare offline è una delle funzioni più interessanti tra quelle permesse dalla sinergia tra PWA e SW.
Introduzione alla navigazione in assenza di connessione.
Vi ho parlato nel mio precedente articolo delle Progressive Web App (PWA), spiegando cosa sono, come funzionano, perché sono chiamate App Ibride e perché rappresentano il futuro del web.
Dopo la teoria, come ai tempi della scuola guida, viene la pratica e in questo articolo vi mostrerò un semplice esercizio attraverso il quale testare le potenzialità della sinergia tra le PWA e il Service Worker.
Per questo esperimento l’unico requisito di cui abbiamo bisogno è la possibilità di raggiungere la cartella dove “hosteremo” i file della PWA attraverso un url sul quale sia attivo un certificato di connessione crittografata SSL; per il resto basterà un hosting con requisiti base in quanto ci limiteremo all’utilizzo di file Javascript, css, json e html.
In questo caso specifico vi mostrerò come progettare un sito che, dopo un primo accesso, possa essere continuato a navigare anche in assenza di rete sfruttando la funzione di Caching in Background del Service Worker.
STEP 1: Architettura file e directory della PWA
I file che andremo a creare sono:
- index.html
- offline.html
- manifest.json
- sw.js
- bootstrap-grid.css
- favicon.png
- online.png
- offline.gif
- Diversi file PNG che rappresenteranno l’icona dell’app
Le cartelle che andremo a creare sono:
- /app_icon/
- /img/
- /css/
La struttura delle Directory e dei File sarà dunque la seguente:
- /Root/
- index.html
- offline.html
- manifest.json
- sw.js
- /app_icon/
- 180.png
- 192.png
- 512.png
- /css/
- bootstrap-grid.css
- /img/
- favicon.png
- online.png
- offline.gif
Per questo esperimento non serve altro.
Non è rilevante la sequenza di creazione dei file, tuttavia cercherò di seguire una logica nell’ordine delle varie fasi di costruzione del progetto.
STEP 2: Il file Manifest
Il primo file su cui lavorare è il manifest.json: nome omen, la sua funzione è quella di dichiarare al browser le informazioni base dell’Applicazione Web e i suoi comportamenti una volta installata su devices mobile o desktop. Vediamo come creare una versione base:
{
"dir" : "ltr",
"lang" : "Italian",
"name" : "PWA prova di navigazione offline",
"scope" : "/",
"display" : "standalone",
"start_url" : "https://alessandrocaprai.it/pwa/",
"short_name" : "PWA Offline",
"theme_color" : "#ee0000",
"description" : "Un test su come far funzionare la navigazione offline di una PWA",
"orientation" : "any",
"background_color" : "#ffffff",
"icons" : [{
"src": "app_icon/180.png",
"type": "image/png",
"sizes": "180x180"
},
{
"src": "app_icon/512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "app_icon/192.png",
"type": "image/png",
"sizes": "192x192"
}]
}
Vediamo cosa significano le voci che troviamo in questo manifest.json:
- dir: Indica la direzione del testo, Left to Right o Right to Left.
- lang: La lingua principale della PWA.
- name: Il Name viene utilizzato nel prompt di installazione della PWA.
- scope: Definisce la root principale nella quale è contenuta tutta la PWA. Questo significa che per funzionare lo start_url dovrà risiedere all’interno della directory indicata nello scope.
- display: Una volta installata su uno smartphone o un’altra devices, la proprietà Standalone della variabile Display, permetterà alla PWA di occupare l’intera area della device.
- start_url: Indica al browser la directory da dove deve essere lanciata la PWA quando questa viene avviata.
- short_name: Lo Short Name verrà visualizzato quando installeremo la PWA in uno smartphone o un’altra device.
- theme_color: Permette di personalizzare il colore di eventuali componenti come (ad esempio) la toolbar, una volta installata la PWA in uno smartphone o un’altra device.
- description: Definisce la descrizione della PWA.
- orientation: Definisce il “verso” dello schermo.
- background_color: Definisce il colore della splashpage, una volta installata la PWA in uno smartphone o un’altra device.
- icons: L’array che definisce l’icona della PWA quando viene installata in uno smartphone o un’altra device. Possono essere declinate diverse icone in diversi formati, ognuno pensato per le diverse tipologie di dispositivi utilizzabili dagli utenti.
STEP 3: Configurare il Service Worker
Dopo aver configurato il Manifest, è il momento lavorare sul codice deputato al dialogo con il Service Worker.
Lo faremo creando con il file sw.js scritto in Javascript. Sarà grazie a questo file che potremmo attivare il Caching in Background del Service Worker e tramite il quale diremo al browser quali file archiviare per permettere la navigazione offline, vero scopo di questo test.
const CACHE = "pwabuilder-precache";
//Utilizzeremo il Service Worker in modalità Cache-first
const precacheFiles = ["index.html", "manifest.json", "sw.js","css/bootstrap-grid.css", "img/favicon.png", "img/online.png", "offline.html","img/offline.gif", "app_icon/180.png", "app_icon/192.png", "app_icon/512.png"];
//In quest’array mettiamo tutti I file che devono essere salvati
//nella cache locale per permettere la navigazione offline.
//Essendo file di piccole dimensioni, non mi preoccupo del peso e
//li carico tutti in cache.
self.addEventListener("install", function (event) {
self.skipWaiting();
event.waitUntil(
caches.open(CACHE).then(function (cache) {
return cache.addAll(precacheFiles);
})
);
});
//Diamo il permesso al Service Worker di poter avere il controllo
//della pagina corrente
self.addEventListener("activate", function (event) {
event.waitUntil(self.clients.claim());
});
//Se il recupero fallisce, cercherà la richiesta nella cache e
//la servirà prima da lì
self.addEventListener("fetch", function (event) {
if (event.request.method !== "GET") return;
event.respondWith(
fromCache(event.request).then(
function (response) {
// La risposta è stata trovata nella cache, quindi rispondiamo e aggiorniamo i dati
// Qui è dove chiamiamo il server per ottenere la versione più recente
// dei file da usare alla prossima visualizzazione
event.waitUntil(
fetch(event.request).then(function (response) {
return updateCache(event.request, response);
})
);
return response;
},
function () {
//La risposta non è stata trovata nella cache, quindi la cerchiamo sul server
return fetch(event.request)
.then(function (response) {
//Se la richiesta ha successo, la aggiungiamo o la aggiorniamo nella cache
event.waitUntil(updateCache(event.request, response.clone()));
return response;
})
.catch(function (error) {
console.log("Richiesta fallita" + error);
});
}
)
);
});
function fromCache(request) {
//Fa un controllo per vedere se c’è nella cache
//Se non c’è, restituisce la risposta
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
if (!matching || matching.status === 404) {
return Promise.reject("no-match");
}
return matching;
});
});
}
function updateCache(request, response) {
return caches.open(CACHE).then(function (cache) {
return cache.put(request, response);
});
}
STEP 4: File Index e Offline + Css
I grosso del lavoro lo abbiamo fatto nei precedenti primi due Step, ora si tratta di assemblare il tutto per far funzionare il progetto, e lo faremo grazie al classico file index.html.
Metteremo in campo quanto basta per far funzionare la prova di navigazione offline, quindi il codice in questione sarà davvero base.
Un passaggio da ricordare è quello di inserire il tag <link rel=”apple-touch-icon”>, necessario per avere una compatibilità totale.
Contenuto del file index.html:
<!doctype html>
<html lang="it" prefix="og: http://ogp.me/ns#">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="author" content="Alesandro Caprai - www.alessandrocaprai.it" />
<meta name="theme-color" content="#ee0000" />
<link rel="icon" type="image/png" href="img/favicon.png" />
<link rel="apple-touch-icon" sizes="180x180" href="app_icon/180.png" />
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" href="css/bootstrap-grid.css" />
<title>[ONLINE] PWA: prova di navigazione Offline</title>
<meta name="description" content="Con questa pagina dimostriamo la funzione Background Syng del Service Worker per la navigazione offline di una PWA " />
<script type="text/javascript">
// Verifichiamo se il browser con cui l’utente sta navigando è
//ha già attivo un Service Worker
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("Service Worker attivo trovato. Nessuna necessità di registrarne uno.");
} else {
// Registriamo il Service Worker se non attivo
navigator.serviceWorker.register("sw.js", {
scope: "./"
})
.then(function (reg) {
console.log("Un nuovo Service Worker registrato per lo scope: " + reg.scope);
});
}
}
</script>
</head>
<body>
<div class="container-fluid container">
<div class="row">
<div class="col-12">
<div class="card col-12">
<div class="card-body">
<div class="col-12 alert alert-danger" role="alert">
<h3>!!! Prima di cliccare sul bottone sottostante assicurati di aver disattivato ogni forma di connessione dati, che sia il 4g, la wiifi, ethernet o via discorrendo.</h3>
</div>
<h5 class="card-title">Naviga Offline</h5>
<p class="card-text">Attraverso una corretta programmazione dimostreremo la possibilità di navigazione offline ottenuta grazie alla sinergia tra una PWA e il Service Worker.</p>
<a href="offline.html" class="btn btn-danger">Stacca tutto e Naviga Offline</a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Ora dobbiamo creare la pagina che andremo a navigare in modalità offline; sarà nella sostanza uguale alla pagina index.html tranne che per la parte del body.
Contenuto del file offline.html:
<!doctype html>
<html lang="it" prefix="og: http://ogp.me/ns#">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="author" content="Alesandro Caprai - www.alessandrocaprai.it" />
<meta name="theme-color" content="#ee0000" />
<link rel="icon" type="image/png" href="img/favicon.png" />
<link rel="apple-touch-icon" sizes="180x180" href="app_icon/180.png" />
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" href="css/bootstrap-grid.css" />
<title>[OFFLINE] PWA: prova di navigazione Offline</title>
<meta name="description" content="Con questa pagina dimostriamo la funzione Background Syng del Service Worker per la navigazione offline di una PWA " />
<script type="text/javascript">
// Verifichiamo se il browser con cui l’utente sta navigando è
//ha già attivo un Service Worker
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("Service Worker attivo trovato. Nessuna necessità di registrarne uno.");
} else {
// Registriamo il Service Worker se non attivo
navigator.serviceWorker.register("sw.js", {
scope: "./"
})
.then(function (reg) {
console.log("Un nuovo Service Worker registrato per lo scope: " + reg.scope);
});
}
}
</script>
</head>
<body>
<div class="container-fluid container">
<div class="row">
<div class="col-12">
<div class="card col-12">
<div class="card-body">
<div class="col-12 alert alert-success" role="alert">
<h3>Stai navigando OFFLINE!!!</h3>
</div>
<h5 class="card-title">Navigazione Offline Attivata</h5>
<p class="card-text">Attraverso una corretta programmazione abbiamo reso possibile la navigazione offline.</p>
<div class="col-12">
<a href="offline.html"><img border="0" src="img/offline.gif" alt="la navigazione offline funziona" style="width:100%;" /></a>
</div>
<div class="pt-3">
<a href="index.html" class="btn btn-success">Torna alla Home</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Per dare un minimo di “dignità” all’interfaccia grafico ho incluso il file bootstrap-grid.css di bootstrap.
STEP 5: Testiamo la PWA
Fatto quanto sopra ora non ci rimane che caricare il tutto seguendo lo schema delle directory e file riportato nello STEP 1.
Se abbiamo fatto tutto correttamente la nostra PWA sarà pronta e potremo effettuare con successo il nostro Test di navigazione in assenza di connessione internet.

L’audit della PWA eseguito con Lighthouse ci da il 100% di compatibilità!
Conclusioni
Come avrete potuto constatare, più che di vera e propria programmazione si tratta quasi di un esercizio di stile tanto basilare quanto i requisiti minimi per poter verificare una tra le funzioni più rilevanti realizzata grazie alla sinergia tra Progressive Web App e Service Worker.
Chiaramente non finisce qui, le funzioni che possiamo creare grazie allo sfruttamento di questo binomio sono enormi e prossimamente andremo a vederne altre come la possibilità di rendere la PWA installabile, creare un sistema di notifiche, accedere alla fotocamera e utilizzare la geolocalizzazione.
Grazie per la lettura, Alessandro Caprai.
ⓘ E tu conoscevi le funzionalità Offline delle PWA? Hai trovato interessante l’articolo?
Lascia un commento e facciamo un po’ di BUZZ!!!
Link Utili
Vi lascio alcuni link utili a strumenti o documentazioni che ho utilizzato per creare questo tst con le Progressive Web App e testare la navigazione offline.
