我正在使用Workbox 3运行非常简单的PWA,主要用于缓存和脱机目的。该网页是用户可以安装PWA的论坛。计划升级是因为当前的工作箱3在测试时出现一些错误,因此我不得不重新构建服务人员。想给工作箱5一个机会。
下面的代码是我今天要测试的,目的应该是(非常简单):
为用户提供一个使用按钮“安装”新服务人员的机会(摘自:https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68)
缓存静态资产,但不缓存html(offline.html除外)。
提供导航预载了提高性能的机会。
基于Workbox 5创建服务工作者,该服务工作者将来很容易更新(推送消息)。
service-worker.js:
// Load WB locally, skip preload of googleapis in header. (Version 5.1.3)
importScripts('/workbox/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: '/workbox/'
});
// How are we doing?
if (workbox) {
console.log('Workbox loaded correctly');
} else {
console.log('Workbox did not load, check log');
}
/*
// Debug on or off, off in production
workbox.setConfig({
debug: true
});
*/
// A new SW is waiting, user clicks button that activates the new SW
addEventListener('message', e => {
if (e.data === 'skipWaiting') {
skipWaiting();
clientsClaim();
}
});
// Cache Offline page
const CACHE_NAME = 'offline-html';
const FALLBACK_HTML_URL = '/offline.html';
addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.add(FALLBACK_HTML_URL))
);
});
// Start navigation preload to speed things up a bit.
workbox.navigationPreload.enable();
const networkOnly = new workbox.strategies.NetworkOnly();
const navigationHandler = async (params) => {
try {
// Attempt a network request.
return await networkOnly.handle(params);
} catch (error) {
// If it fails, return the cached HTML and log the error
console.log(error);
return caches.match(FALLBACK_HTML_URL, {
cacheName: CACHE_NAME,
});
}
};
// Register this strategy to handle all navigations.
const navigationRoute = new workbox.routing.NavigationRoute(navigationHandler);
workbox.routing.registerRoute(navigationRoute);
// Cache static assets
const {StaleWhileRevalidate} = workbox.strategies;
const {CacheFirst} = workbox.strategies;
const {CacheableResponsePlugin} = workbox.cacheableResponse;
workbox.routing.registerRoute(
({request}) => request.destination === 'script' || request.destination === 'style' || request.destination === 'font' || request.destination === 'manifest',
new StaleWhileRevalidate({
// Use a custom cache name.
cacheName: 'static-cache2',
})
);
// Cache image files.
workbox.routing.registerRoute(
({request}) => request.destination === 'image',
// Use the cache if it's available.
new CacheFirst({
// Use a custom cache name.
cacheName: 'image-cache2',
plugins: [
new workbox.expiration.ExpirationPlugin({
// Cache only 100 images.
maxEntries: 100,
// Cache for a maximum of two weeks.
maxAgeSeconds: 14 * 24 * 60 * 60,
purgeOnQuotaError: true,
})
],
})
);
// Try to cache opaque from CDN
workbox.routing.registerRoute(
({url}) => url.origin === 'https://cdn.mycdn.com' &&
url.pathname.startsWith('/static/'),
new CacheFirst({
cacheName: 'cdn-cache',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
})
]
})
);
客户端JS:
function showRefreshUI(registration) {
// TODO: Display a toast or refresh UI.
// This demo creates and injects a button.
var button = document.createElement('button');
button.style.position = 'absolute';
button.style.bottom = '24px';
button.style.left = '24px';
button.textContent = 'A new version of the web app is waiting, click here to install';
button.addEventListener('click', function() {
if (!registration.waiting) {
// Just to ensure registration.waiting is available before
// calling postMessage()
return;
}
button.disabled = true;
registration.waiting.postMessage('skipWaiting');
});
document.body.appendChild(button);
};
function onNewServiceWorker(registration, callback) {
if (registration.waiting) {
// SW is waiting to activate. Can occur if multiple clients open and
// one of the clients is refreshed.
return callback();
}
function listenInstalledStateChange() {
registration.installing.addEventListener('statechange', function(event) {
if (event.target.state === 'installed') {
// A new service worker is available, inform the user
callback();
}
});
};
if (registration.installing) {
return listenInstalledStateChange();
}
// We are currently controlled so a new SW may be found...
// Add a listener in case a new SW is found,
registration.addEventListener('updatefound', listenInstalledStateChange);
}
window.addEventListener('load', function() {
var refreshing;
// When the user asks to refresh the UI, we'll need to reload the window
navigator.serviceWorker.addEventListener('controllerchange', function(event) {
if (refreshing) return; // prevent infinite refresh loop when you use "Update on Reload"
refreshing = true;
console.log('Controller loaded');
window.location.reload();
});
navigator.serviceWorker.register('/service-worker.js')
.then(function (registration) {
// Track updates to the Service Worker.
if (!navigator.serviceWorker.controller) {
// The window client isn't currently controlled so it's a new service
// worker that will activate immediately
return;
}
registration.update();
onNewServiceWorker(registration, function() {
showRefreshUI(registration);
});
});
});
此代码可以在我的开发服务器上工作,我对SO:s工作箱专家的问题是:它是否有任何陷阱,也许有人可以分享如何改进它的建议? >
Service Worker和Workbox很复杂,我主要担心的是,我已经构建了Service Worker,因此它无法针对Workbox进行优化,并且可能使用错误或错误的代码/功能/功能顺序。