Service Worker
Кеширование
Регистрация
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then((registration) => {
// success
})
.catch((error) => {
// error
});
}
service-worker.js
npm i serviceworker-webpack-plugin --save-dev
new ServiceWorkerWebpackPlugin({
entry: root('src/app/service-worker'),
excludes: ['sw.*.js', 'online.html'],
includes: ['**/*.css', '**/*.js', '**/*.json', '**/*.png', '**/*.svg']
});
import runtime from "serviceworker-webpack-plugin/lib/runtime.js";
// Register SW
if ("serviceWorker" in navigator) {
runtime.register();
}
Стратегии кеширования
- Cache-first для статики
- Network-first для API
- Только GET-запросы и другие органичения
self.addEventListener('fetch', (event) => {
const request = event.request;
if (!shouldHandle(request)) {
event.respondWith(fetch(request));
return;
}
if (isStatic(request)) {
// cache-first
event.respondWith(
fetchFromCache(event)
.catch(() => fetch(request))
.then(response => addToCache(cacheKey, request, response))
.catch(() => offlineResponse(resourceType, opts))
);
} else {
// network-first
event.respondWith(
fetch(request)
.then(response => addToCache(cacheKey, request, response))
.catch(() => fetchFromCache(event))
.catch(() => offlineResponse(opts))
);
}
});
function isStatic(request) {
const path = getPath(request.url);
return serviceWorkerOption.assets.some(p => p === path);
}
Демо
Нюансы
Офлайн режим
window.addEventListener('online',
() => alert('online!'));
window.addEventListener('offline',
() => alert('offline!'));
sw.js
event.respondWith(
fetch(request)
.then(response => broadcastOnline())
.catch(() => broadcastOffline())
);
function broadcastOnline() {
clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage({action: 'onlineStatus', online: true});
});
});
}
App
window.addEventListener('offline', () => notifyOffline());
window.addEventListener('online', () => {
fetch("online.html");
});
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', (event) => {
switch(event.data.action) {
case 'onlineStatus':
event.data.online ? notifyOnline() : notifyOffline();
break;
}
});
}
Code splitting
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(
[
'/css/bootstrap.css',
'/css/main.css',
'/js/bootstrap.min.js',
'/js/jquery.min.js',
'/offline.html'
]
);
})
);
});
Очистка кеша
caches.keys().then((keyList) => {
keyList.map((key, i) => {
if (key !== appCachenName) {
caches.delete(keyList[i])
}
})
})
operation is insecure
set(key: Request | string, value: Response): Promise {
if (!isSupport()) {
return Promise.resolve();
}
return caches.open(this.cacheNAme)
.then(cache => cache.put(key, value))
// avoid security errors
.catch(() => { /* do nothing */ });
}
Cache then network
Cache then network
var networkDataReceived = false;
startSpinner();
// fetch fresh data
var networkUpdate = fetch('/data.json').then((response) => {
return response.json();
}).then((data) => {
networkDataReceived = true;
updatePage(data);
});
// fetch cached data
caches.match('/data.json').then(function(response) {
if (!response) throw Error("No data");
return response.json();
}).then(function(data) {
// don't overwrite newer network data
if (!networkDataReceived) {
updatePage(data);
}
}).catch(function() {
// we didn't get cached data, the network is our last hope:
return networkUpdate;
}).catch(showErrorMessage).then(stopSpinner());
Cache then network
+ "Нулевая" задержка
+ Относительно низкая стоимость
Демо
Вопросы?
sw
By Viacheslav Bukharin
sw
- 729