A presentation at An Event Apart San Francisco 2019 in in San Francisco, CA, USA by Jeremy Keith
Going Offline
adactio.com
adactio.com
resilientwebdesign.com
2005
JavaScript When someone mouses over a link, make it look different. When someone fills out a form, make sure the inputs are valid.
JavaScript Find stuff and do stuff to it.
JavaScript Find stuff and do stuff to it. jQuery
JavaScript Find stuff and do stuff to it. $(‘.stuff’).click(doStuff); jQuery
JavaScript document.querySelector(‘.stuff’) .addEventListener(‘click’, doStuff); Find stuff and do stuff to it. $(‘.stuff’).click(doStuff); jQuery
JavaScript code Logic.
JavaScript Logic. code
2007
Update part of this page with data from a server. Ajax
DOM Scripting Find stuff on this page and do stuff to it. Update part of this page with data from a server. Ajax
DOM Scripting Find stuff on this page and do stuff to it. Update part of this page with data from a server. Ajax
DOM Scripting web worker Ajax
web worker
service worker
2018
example.com
example.com
example.com
example.com
example.com
example.com
page.html styles.css script.js icon.png example.com
serviceworker.js example.com
serviceworker.js example.com
serviceworker.js example.com
serviceworker.js example.com
serviceworker.js example.com
serviceworker.js example.com
serviceworker.js example.com
serviceworker.js https ://example.com
https
register install active update life cycle
register
install
active
update
update
active
register
register Get the JavaScript file ‘serviceworker.js’
register <script src=”/serviceworker.js”> </script>
register <script src=”/serviceworker.js”> </script>
register <script> navigator.serviceWorker.register(); </script>
register <script> navigator.serviceWorker.register (‘/serviceworker.js’); </script>
register <script> navigator.serviceWorker.register (‘/assets/js/serviceworker.js’); </script>
register <script> navigator.serviceWorker.register (‘/assets/js/serviceworker.js’); </script>
register <script> navigator.serviceWorker.register (‘/serviceworker.js’); </script>
register <script> if (navigator.serviceWorker) { navigator.serviceWorker.register (‘/serviceworker.js’); } </script>
register Get the JavaScript file ‘serviceworker.js’
<script> if (navigator.serviceWorker) { navigator.serviceWorker.register (‘/serviceworker.js’); } </script>register Install the JavaScript file ‘serviceworker.js’
<script> if (navigator.serviceWorker) { navigator.serviceWorker.register (‘/serviceworker.js’); } </script>install
install caching
memory cache browser cache Cache API caching
install precaching
install styles.css script.js icon.png
install styles.css script.js icon.png
install styles.css script.js icon.png
install styles.css script.js icon.png
install styles.css script.js icon.png
install styles.css script.js icon.png
install script.js styles.css icon.png
install
install precaching
precaching When this service worker is installing open a cache and then put a bunch of files in that cache.
precaching When this service worker is installing
precaching When this service worker is installing addEventListener()
precaching When this service worker is installing addEventListener(‘install’)
precaching When this service worker is installing addEventListener(‘install’, function(event) { });
precaching When this service worker is installing addEventListener(‘install’, event => { });
precaching open a cache
precaching open a cache caches.open()
precaching open a cache caches.open(‘files’)
precaching open a cache and then caches.open(‘files’)
precaching open a cache and then caches.open(‘files’).then()
precaching open a cache and then caches.open(‘files’).then( cache => { });
precaching put a bunch of files in that cache.
precaching put a bunch of files in that cache. cache.addAll()
precaching put a bunch of files in that cache. cache.addAll([ ‘/css/styles.css’, ‘/js/script.js’, ‘/img/icon.png’ ]);
precaching
precaching When this service worker is installing addEventListener(‘install’, event => { });
precaching When this service worker is installing open a cache caches.open(‘files’)
precaching When this service worker is installing open a cache and then caches.open(‘files’).then( cache => { });
precaching When this service worker is installing open a cache and then put a bunch of files in that cache. cache.addAll([ ‘/css/styles.css’, ‘/js/script.js’, ‘/img/icon.png’ ]);
precaching When this service worker is installing open a cache and then put a bunch of files in that cache.
precaching addEventListener(‘install’, event => { });
precaching addEventListener(‘install’, event => { caches.open(‘files’) });
precaching addEventListener(‘install’, event => { caches.open(‘files’).then( cache => { }); });
precaching addEventListener(‘install’, event => { caches.open(‘files’).then( cache => { cache.addAll([ ‘/css/styles.css’, ‘/js/script.js’, ‘/img/icon.png’ ]); }); });
precaching addEventListener(‘install’, event => { caches.open(‘files’).then( cache => { cache.addAll([ ‘/css/styles.css’, ‘/js/script.js’, ‘/img/icon.png’ ]); }); });
install
active
cache first When a file is requested try to find the file in the cache first otherwise fetch the file from the network.
cache first When a file is requested
cache first When a file is requested addEventListener()
cache first When a file is requested addEventListener(‘fetch’)
cache first When a file is requested addEventListener(‘fetch’, event => { });
cache first When a file is requested addEventListener(‘fetch’, event => { event.respondWith( ); });
cache first try to find the file in the cache first
cache first try to find the file in the cache first caches.match(event.request)
cache first try to find the file in the cache first caches.match(event.request) .then( response => { })
cache first try to find the file in the cache first caches.match(event.request) .then( response => { return response; })
cache first try to find the file in the cache first otherwise fetch the file from the network. caches.match(event.request) .then( response => { return response; })
cache first try to find the file in the cache first otherwise fetch the file from the network. caches.match(event.request) .then( response => { return response || fetch(event.request); })
cache first When a file is requested try to find the file in the cache first otherwise fetch the file from the network.
cache first addEventListener(‘fetch’, event => { event.respondWith( ); });
cache first addEventListener(‘fetch’, event => { event.respondWith( caches.match(event.request) .then( response => { return response }) ); });
cache first addEventListener(‘fetch’, event => { event.respondWith( caches.match(event.request) .then( response => { return response || fetch(event.request); }) ); });
cache first addEventListener(‘fetch’, event => { event.respondWith( caches.match(event.request) .then( response => { return response || fetch(event.request); }) ); });
cache first
offline first
offline first
offline first
offline first
offline first
offline first
offline first
offline first
offline first precaching cache first install active
css js img precaching cache first
html network first custom offline page
network first
precaching addEventListener(‘install’, event => { caches.open(‘files’).then( cache => { cache.addAll([ ‘/css/styles.css’, ‘/js/script.js’, ‘/img/icon.png’ ]); }); });
precaching addEventListener(‘install’, event => { caches.open(‘files’).then( cache => { cache.addAll([ ‘/css/styles.css’, ‘/js/script.js’, ‘/img/icon.png’, ‘/offline.html’ ]); }); });
network first When a page is requested try to fetch the page from the network first otherwise retrieve the offline page from the cache.
network first When a page is requested addEventListener(‘fetch’, event => { });
network first When a page is requested addEventListener(‘fetch’, event => { if (event.request.headers .get(‘Accept’).includes(‘text/html’) { } });
network first try to fetch the page from the network first event.respondWith( fetch(event.request) .then( response => { return response; }) );
network first otherwise retrieve the offline page from the cache. .catch( error => { return caches.match(‘/offline.html’); })
network first
network first addEventListener(‘fetch’, event => { if (event.request.headers.get(‘Accept’).includes(‘text/html’) { } });
network first addEventListener(‘fetch’, event => { if (event.request.headers.get(‘Accept’).includes(‘text/html’) { event.respondWith( fetch(event.request) .then( response => { return response; }) } }); );
network first addEventListener(‘fetch’, event => { if (event.request.headers.get(‘Accept’).includes(‘text/html’) { event.respondWith( fetch(event.request) .then( response => { return response; }) .catch( error => { return caches.match(‘offline.html’); }) ); } });
network first addEventListener(‘fetch’, event => { if (event.request.headers.get(‘Accept’).includes(‘text/html’) { event.respondWith( fetch(event.request) .then( response => { return response; }) .catch( error => { return caches.match(‘offline.html’); }) ); } });
html network first custom offline page
html network first cache as you go custom offline page
html network cache custom offline page
cache as you go When a page is requested try to fetch the page from the network first and store a copy in the cache otherwise try to retrieve the page from the cache otherwise retrieve the offline page from the cache.
custom offline page offline.html
css js img html cache network network cache fallback
css js img html speed freshness
articles shopping travel games reference
cache on demand articles
cache on demand When the user presses a button put a copy of this page in the cache.
cache on demand page.html
una.im
una.im
una.im
sarasoueidan.com
sarasoueidan.com
archive.dconstruct.org
archive.dconstruct.org
archive.dconstruct.org
archive.dconstruct.org
web app manifest https service worker
web app manifest <link rel=”manifest” href=”/manifest.json”>
web app manifest { } name: “Resilient Web Design”, short_name: “Resilience”, display: “standalone”, start_url: “/”, theme_color: “#5f7995”, icons: { { src: “/icon.png”, sizes: “512x512”, type: “image/png” } }
web app manifest https service worker progressive web app
“ I have a really hard time describing what a progressive web app actually is.” —What the heck is a “Progressive Web App”? Seriously.
“ A progressive web app is an enhanced version of a Single Page App (SPA) with a native app feel.” —Building Progressive Web Apps
“ Before You Build a PWA You Need a SPA” —hackernoon.com
“ I’ve seen too many devs assume PWA is a subset of SPA. We need to improve our messaging” —Jake Archibald
“ A Progressive Web App is a regular website following a progressive enhancement strategy to deliver native-like user experiences by using modern Web standards.” —What is a PWA
“ The name isn’t for you and worrying about it is distraction from just building things that work better for everyone. … It’s marketing, just like HTML5 had very little to do with actual HTML.” —Naming Progressive Web Apps
Site is served over HTTPS All app URLs load while offline Metadata provided for Add to Home screen Pages are responsive First load fast even on 3G Site works cross-browser Each page has a URL Page transitions don’t block on the network
Site is served over HTTPS All app URLs load while offline Metadata provided for Add to Home screen Pages are responsive First load fast even on 3G Site works cross-browser Each page has a URL Page transitions don’t block on the network
HTTPS offline Metadata
HTTPS offline Metadata HTTPS + service worker + web app manifest = progressive web app
progressive
progressive
Thank you
Web design is complicated. Web development is complicated. Everything seems to be constantly changing—there’s so much to keep track of. But there’s one thing we can confidently say for sure: websites need an internet connection in order to work. Right? Well, even that is no longer true. Thanks to the powerful technology of service workers, you can now design and develop websites that work offline. This is a game-changer! And now you’ve got something new you need to learn. But don’t worry—Jeremy is here to talk you through a whole range of offline strategies. By the end of this presentation, you’ll have all the knowledge and code you’ll need to free your website from the tyranny of the network connection.
The following resources were mentioned during the presentation or are useful additional information.
Here’s what was said about this presentation on social media.
If only my instructors had the ability to teach and inform the way @adactio does, I could even be a rocket scientist. #AEASF pic.twitter.com/zKuW45EDez
— Recep Kütük (@recepkutuk) December 11, 2019
Any day that begins with a presentation by @adactio is a good day.
— zeldman (@zeldman) December 11, 2019
Sir: I delight in and am inspired by your natural teaching ability, ease with words, and deep grasp of the web and what it means.#AEASF #ProgressiveWebApp #OfflineFirst #Offline
Sat down for breakfast at #AEASF and was joined by @adactio himself without even realizing who it was. Great presentation on “Going Offline”, sir!
— Rob Erekson (@roberekson) December 11, 2019
There’s a lot of conflicting definitions of what a Progressive Web App actually is. Here’s the technical definition. @adactio’s talk at #aeasf pic.twitter.com/BUvp7wQoLI
— Jeremiah John (@glassdimly) December 11, 2019
I died and came back to life in the same session. Thank you @adactio for talking about "Going Offline." I've had the book on my desk for awhile, but I couldn't get to it. I think it's time to open it up and dig deeper. #aeasf
— tyler medina (@tylermedina) December 11, 2019
Can't wait to check out some websites next time I'm on the bottom of the ocean 😆 Also I finally understand what a progressive web app is.🙏@adactio #aeasf pic.twitter.com/YXUwqcDL00
— Elizabeth Pizzuti (@zipboing) December 11, 2019
Logic is more important than code. @adactio #aeasf
— Marya (@emdot) December 11, 2019