Reputation: 4828
I'm trying to load a service worker before all subresource requests on the page so I can apply some optimizations to the way subresources are loaded (e.g. lazy-loading, loading minified versions of assets instead of full assets). However, I cannot find a way load my SW before other subresource requests begin.
I created a simple proof-of-concept of what I'm trying to do to 401 any requests handled by my Service Worker (just to make it easier to find when my SW begins handling requests).
Here's my HTML:
<!doctype html>
<head>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/dl-wps-sw.js', { scope: '/' }).then(function (registration) {
console.log('Service Worker registration successful with scope: ', registration.scope);
}, function (err) {
console.error(err);
});
}
</script>
...
and here's my Service Worker:
self.addEventListener('install', function (event) {
self.skipWaiting();
});
self.addEventListener('activate', () => {
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
const init = {status: 401, statusText: 'Blocked!'};
event.respondWith(new Response(null, init));
});
This is what happens in my browser:
As you can see in the screenshot, even though my code to register the Service Worker is at the very top of the page, it doesn't activate and begin handling requests until a bit later, and by then a large number of critical requests I need to catch have already fired.
I found someone else who seemed to be trying to do what I'm trying to accomplish (2 years earlier) in a Github issue for the Service Worker spec: https://github.com/w3c/ServiceWorker/issues/1282
It seems they suggested using a <link rel="serviceworker"...
tag to do this, but it appears this link type has since been removed from Chrome for some reason: https://www.chromestatus.com/feature/5682681044008960
I've tried several other ideas to attempt to load my SW first:
<link rel="preload"...
tagfetch
/XMLHttpRequest
Any ideas or strategies I'm missing? My Google-fu has failed me on coming up with a solution.
Upvotes: 8
Views: 4604
Reputation: 187
While the default behaviour for a new service worker is to wait until the next page load to take over from the previous one, a service worker can in fact begin controlling existing pages immediately using the Clients.claim()
method.
So, by including something like this in your service worker, you should be able to avoid the need to refresh:
self.addEventListener('activate', event => {
event.waitUntil(clients.claim());
});
self.addEventListener('install', event => {
self.skipWaiting();
});
You can read more about Clients.claim()
on MDN.
Upvotes: 2
Reputation: 9072
You'll need to install the SW alone first, then refresh the page. The SW can then serve content for a SW-enabled page and intercept all other requests. Example: fetch-progress.anthum.com
index.html
<p>Installing Service Worker, please wait...</p>
<script>
navigator.serviceWorker.register('sw.js')
.then(reg => {
if (reg.installing) {
const sw = reg.installing || reg.waiting;
sw.onstatechange = function() {
if (sw.state === 'installed') {
// SW installed. Refresh page so SW can respond with SW-enabled page.
window.location.reload();
}
};
} else if (reg.active) {
// something's not right or SW is bypassed. previously-installed SW should have redirected this request to different page
handleError(new Error('Service Worker is installed and not redirecting.'))
}
})
.catch(handleError)
function handleError(error) {}
</script>
sw.js
self.addEventListener('fetch', event => {
const url = event.request.url;
const scope = self.registration.scope;
// serve index.html with service-worker-enabled page
if (url === scope || url === scope+'index.html') {
const newUrl = scope+'index-sw-enabled.html';
event.respondWith(fetch(newUrl))
} else {
// process other files here
}
});
index-sw-enabled.html
<!--
This page shows after SW installs.
Put your main app content on this page.
-->
Upvotes: 8