Skip to content

Instantly share code, notes, and snippets.

@GianlucaGuarini
Last active May 2, 2017 12:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save GianlucaGuarini/669265541009eca1ad728eb7f28504bb to your computer and use it in GitHub Desktop.
Save GianlucaGuarini/669265541009eca1ad728eb7f28504bb to your computer and use it in GitHub Desktop.
Cache a whole SPA website and its assets via app cache, service workers and localstorage
{
"secret-message": "hello"
}
CACHE MANIFEST
# 2017-04-04 v1.0.0
/style.css
/index.js
NETWORK:
*
<!DOCTYPE html>
<html manifest='demo.appcache'>
<head>
<title>demo</title>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<h1>Hello</h1>
<p></p>
<img src='https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg'>
<script src='index.js'></script>
</body>
</html>
/**
* Get a json file via ajax request, the `fetch` was not used on purpose
* @param { String } url - json url
* @returns { Promise } deferred return of the json file contents
*/
function getJSON(url) {
var request = new XMLHttpRequest()
request.open('GET', url, true)
return new Promise((resolve, reject) => {
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// Success!
var data = JSON.parse(request.responseText)
resolve(data)
} else {
// We reached our target server, but it returned an error
reject('unkown error')
}
}
request.onerror = reject
request.send()
})
}
/**
* Try to start the service worker if supported
* @returns { Promise } - resolve in any case
*/
function startServiceWorker() {
return new Promise((resolve, reject) => {
if ('serviceWorker' in navigator && navigator.onLine) {
navigator.serviceWorker.register('service-worker.js', {
scope: './'
})
navigator.serviceWorker.ready.then(resolve)
} else {
resolve()
}
})
}
/**
* Application boot event
* @param { Object } data - application data
*/
function onReady(data) {
document.querySelector('p').innerHTML = `${data['secret-message']} ${new Date()}`
}
/**
* Boot the app using fallback data
*/
function fallbackData() {
console.log('loading cached fallback data')
onReady(JSON.parse(localStorage.getItem('json')))
}
/**
* Try to start the service worker
*/
startServiceWorker().then(() => {
// if not online it's not even worth to try to fetch new data
if (navigator.onLine) {
console.log('fetching new data')
getJSON('/data.json').then(json => {
localStorage.setItem('json', JSON.stringify(json))
onReady(json)
}).catch(fallbackData)
} else {
console.log('loading offline')
fallbackData()
}
})
// forked from https://serviceworke.rs/strategy-network-or-cache.html
var CACHE = 'network-or-cache'
// On install, cache some resource.
self.addEventListener('install', function(evt) {
console.log('The service worker is being installed.')
// Ask the service worker to keep installing until the returning promise
// resolves.
evt.waitUntil(precache())
})
// On fetch, use cache but update the entry with the latest contents
// from the server.
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.')
// Try network and if it fails, go for the cached copy.
evt.respondWith(fromNetwork(evt.request, 400).catch(function () {
return fromCache(evt.request)
}))
})
// Open a cache and use `addAll()` with an array of assets to add all of them
// to the cache. Return a promise resolving when all the assets are added.
function precache() {
return caches.open(CACHE).then(function (cache) {
return cache.addAll([
'./index.html',
'./index.js',
'./style.css'
])
})
}
// Time limited network request. If the network fails or the response is not
// served before timeout, the promise is rejected.
function fromNetwork(request, timeout) {
return new Promise(function (fulfill, reject) {
// Reject in case of timeout.
var timeoutId = setTimeout(reject, timeout)
// Fulfill in case of success.
fetch(request).then(function (response) {
clearTimeout(timeoutId)
fulfill(response)
// Reject also if network fetch rejects.
}, reject)
})
}
// Open the cache where the assets were stored and search for the requested
// resource. Notice that in case of no matching, the promise still resolves
// but it does with `undefined` as value.
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
return matching || Promise.reject('no-match')
})
})
}
body {
background: #222;
color: white;
}
img {
width: 100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment