@GDG DevFest Beijing Nov 2016
⚡️
{
"name": ["黄玄", "Hux", "@huxpro"],
"icons": [{
}],
"start_url": " ",
"social_network": [" ", " ", " "],
"work_experience": ["@alibaba", "@wepiao"]
}
Network
Download & Run on the fly
<html manifest="cache.appcache">
CACHE MANIFEST
CACHE:
style/default.css
images/sound-icon.png
images/background.png
NETWORK:
comm.cgi
HTTP caching Race Condition
Unrecoverable Errors (Cache the manifest)
Success only If ALL resources are cached
Not a Progressive Enhancement
Caching Dynamic Contents needs hack & re-architecture
Full of Assumption
The Cache is organized by "Manifest"
Manifest changes trigger ALL cached files redownload.
NO clean up mechanism for cache
Deficit of Flexibility
Unprogrammable
Very Limited Fallback/Routing
Network
Round Trip is Expensive
Dependency Graph
Network
Service Worker
Cache
Network
// register Service Worker in index.html
if('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then( registration => {
console.log('Service Worker Registered');
}).catch( error => {
console.log('Registration failed with' + error);
})
}
/* sw.js */
self.onfetch = (e) => {
e.respondWith(new Response('Hello World from SW!'))
}
Service Worker
Cache
Network
[SecureContext, Exposed=(Window,Worker)]
interface ServiceWorker : EventTarget {
readonly attribute USVString scriptURL;
readonly attribute ServiceWorkerState state;
void postMessage(any message, optional sequence<object> transfer = []);
// event
attribute EventHandler onstatechange;
};
ServiceWorker implements AbstractWorker;
// sw.js
self.oninstall = (e) => {
e.waitUntil(
caches.open('installation')
.then(cache => cache.addAll([
'/styles.css',
'/script.js'
]))
)
});
self.onfetch = (e) => {
const fetched = fetch(e.request)
const cached = caches.match(e.request)
const sorry = caches.match('offline.html')
e.respondWith(
fetched.catch(_ => cached).then(res => res || sorry)
)
}
Network First (not ideal)
self.onfetch = (e) => {
// chrome would disk-cache any res with no 'cache-control',
// which broke the assumption here we want
// fetching index.html failed on offline
const fetched = fetch(`${e.request.url}?${Date.now()}`)
// but would also break revalidation depends on your CDN
const cached = caches.match(e.request)
const sorry = caches.match('offline.html')
e.respondWith(
fetched.catch(_ => cached).then(res => res || sorry)
)
}
interface ExtendableEvent : Event {
void waitUntil(Promise<any> f);
};
interface FetchEvent : ExtendableEvent {
[SameObject] readonly attribute Request request;
void respondWith(Promise<Response> r);
};
The First Service Worker
https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle
[Exposed=ServiceWorker]
interface Clients {
// Jake: take control of uncontrolled clients
[NewObject] Promise<void> claim();
};
Override Default (Racing)
Generic Fallback
self.onfetch = (e) => {
const cached = caches.match(e.request)
const fallback = caches.match('offline.html')
const fetched = fetch(`${e.request.url}?${Date.now()}`)
e.respondWith(
cached
.then(res => res || fetched)
.catch(_ => fallback)
)
}
Cache and Network Race (Fastest)
self.onfetch = (e) => {
const cached = caches.match(e.request)
const fallback = caches.match('offline.html')
const fetched = fetch(`${e.request.url}?${Date.now()}`)
e.respondWith(
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => fallback)
)
}
Stale While Revalidate
self.onfetch = (e) => {
const cached = caches.match(e.request)
const fallback = caches.match('offline.html')
const fetched = fetch(`${e.request.url}?${Date.now()}`)
const fetchedCopy = fetched.then(_ => _.clone())
e.respondWith(
cached
.then(res => res || fetched)
.catch(_ => fallback)
)
e.waitUntil(
Promise.all([fetchedCopy, caches.open('runtime')])
.then(([resp, cache]) => resp.ok && cache.put(e.request, resp))
.catch(_ => {/* swallow */})
)
}
self.onfetch = (e) => {
const cached = caches.match(e.request)
const fallback = caches.match('offline.html')
const fetched = fetch(`${e.request.url}?${Date.now()}`)
const fetchedCopy = fetched.then(_ => _.clone())
e.respondWith(
Promise.race([fetched.catch(_ => cached), cached])
.then(resp => resp || fetched)
.catch(_ => fallback)
)
e.waitUntil(
Promise.all([fetchedCopy, caches.open('runtime')])
.then(([resp, cache]) => resp.ok && cache.put(e.request, resp))
.catch(_ => {/* swallow */})
)
}
Cache then Network (from Pages)
Updating the Service Worker
https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle
interface ServiceWorkerRegistration : EventTarget {
readonly attribute ServiceWorker? installing;
readonly attribute ServiceWorker? waiting;
readonly attribute ServiceWorker? active;
}
Override Default (Cautions)
self.onactivate = (event) => {
// delete any caches that aren't in expectedCaches
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) {
return caches.delete(key);
}
})
)).then(console.log("V2 is ready!");)
);
});
Clean-up & Migration.
(without bypassing GFW)
huangxuan.me / Weibo / GitHub / 知乎
自豪的采用 @演说.io
huangxuan.me / Weibo / GitHub / 知乎