Online WebApp
Facciamo un esempio pratico...
...comande per la cucina!
Offline nel 2016
Quando mai accade?
Nel ferrarese abbiamo molti palazzi storici...
...muri di mattone pieno da 55cm non sono amici del 4G!
I preconcetti delle WebApp
- La connessione è attendibile
- La latenza è nulla
- La banda è infinita
- La rete è sicura
- La topologia della rete non cambia
- Il costo del viaggio del dato è nullo
Ok, no problem!
Installiamo un router e un server locale!
Online WebApp... Lie-Fi
???
???
Come testare in locale
una connessione Lie-Fi?
I browser funzionano così,
possiamo farci poco!
E se non avessimo bisogno di connetterci ad internet per usare la nostra app?
E se potessimo salvare la nostra app
in locale sul dispositivo?
Offline-First!
AppCache
Offline-first è l'unico modo di raggiungere un esperienza 100%* connessa per l'utente
*A patto che il dispositivo sia attendibile.
Meet the ServiceWorker!
Il ServiceWorker è uno script che il browser esegue in background, separato dalla pagina web, aprendo la porta a nuove funzionalità che non richiedono l'interazione dell'utente come caching, background sync e push notifications.
...solo Chrome e FireFox... :'(
...alla peggio possiamo usare il vecchio standard!
AppCache manifest
<html manifest="example.appcache">
...
</html>
CACHE MANIFEST
# v1 2011-08-14
# This is another comment
index.html
cache.html
style.css
image1.png
# Use from network if available
NETWORK:
network.html
# Fallback content
FALLBACK:
/ fallback.html
...puoi scriverlo tu...
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
}
...puoi scriverlo tu...
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
...o lasciarlo fare a
SW-PRECACHE
SW-Precache è un modulo per generare un Service Worker che precacha le risorse statiche, assegnando un hash per ogni file per gestirne il versioning. Si integra con il build tool come webpack o gulp, o può essere eseguito da command line.
SWPrecache si occupa inoltre di scaricare in automatico versioni aggiornate dei contenuti statici e di servirli tramite una logica cache-first.
TIP: Chrome Offline!
TIP: navigator.onLine
- False quando non sono disponibili connessioni internet
- True quando POTREBBE essere disponibile, o in casi di Lie-Fi
...e per i dati?
Un database locale?
- IndexedDB
- WebSQL
- LocalStorage
LocalForage!
Seleziona in automatico il driver disponibile tra quelli elencati e fornisce delle API comuni simili a quelle offerte dal LocalStorage
localforage.setItem('key', 'value', function (err) {
// if err is non-null, we got an error
localforage.getItem('key', function (err, value) {
// if err is non-null, we got an error. otherwise, value is the value
});
});
Lavorare con un database locale ci permette di non preoccuparci dei problemi di connettività...
Le API del LocalStorage sono persino sincrone...
...e memorizzo tutti i dati necessari per usufruire dell'app...
Redux!
Redux per l'Offline First?
- Lo stato è facilmente serializzabile*
- Lo stato è facilmente deserializzabile*
- Lo stato è centralizzato e facilmente accessibile
- Il reducer si occupa di verificare che lo stato in ingresso sia valido, tutto è quindi ben separato
*a meno di perversioni non "Dan Abramov Approved".
...anche mobx-state-tree va bene!
Sono i vantaggi di uno state tree qualsiasi.
Redux aggiunge la facilità di save-restore senza dover serializzare/deserializzare.
redux-persist
Ad ogni cambiamento, salva in database locale il ramo dello stato desiderato, ed al successivo avvio dell'app lo ripristina dal database locale.
import {persistStore, autoRehydrate} from 'redux-persist'
const store = createStore(reducer, undefined, autoRehydrate())
persistStore(store)
TIP: ServiceWorker Sync
Tramite la ServiceWorker sync posso eseguire il task di sincronizzazione anche in background!
// Register your service worker:
navigator.serviceWorker.register('/sw.js');
// Then later, request a one-off sync:
navigator.serviceWorker.ready.then(function(swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
self.addEventListener('sync', function(event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
E l'inserimento di nuovi record?
Inserisco e aggiorno lo store locale!
- L'operazione non risente di problemi di rete
- E' molto più veloce
- Migliore esperienza per l'utente
...ma?
- Niente id progressivi, solo univoci random
(stringhe) - Se voglio gestire un progressivo, dovrò farlo in fase di sincronizzazione
- Devo effettuare la validazione offline (best practice ma doppio lavoro!)
- Validazioni online-first problematiche
(e.g. slug univoco)
TIP: shortid
Genera ID univoci randomici molto corti, url friendly "alla YouTube".
var shortid = require('shortid');
console.log(shortid.generate());
//PPBqWA9
PouchDB?
E' una soluzione production-ready che fornisce un database per il client con sincronizzazione tra database locale e remoto in maniera semplice e standardizzata.
var db = new PouchDB('dbname');
db.put({
_id: 'dave@gmail.com',
name: 'David',
age: 69
});
db.changes().on('change', function() {
console.log('Ch-Ch-Changes');
});
db.replicate.to('http://example.com/mydb');
[...] I think that sometimes it’s useful to “reinvent the wheel” on your own. [...] This is also the reason because I love the JavaScript ecosystem. It’s true that we have a new ‘shiny’ framework every week, but it’s not a fatigue, it’s a great opportunity to learn new ways to write and organize our code.
@francescostrazzullo
Thanks!
@mattiamanzati
Offline First Application
By mattiamanzati
Offline First Application
- 2,968