最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Service Worker: How to cache the first (dynamic) page - Stack Overflow

programmeradmin7浏览0评论

I have this one-page app with a dynamic URL built with a token, like example/XV252GTH and various assets, like css, favicon and such.

Here is how I register the Service Worker:

navigator.serviceWorker.register('sw.js');

And in said sw.js, I pre-cache the assets while installing:

var cacheName = 'v1';

var cacheAssets = [
    'index.html',
    'app.js',
    'style.css',
    'favicon.ico'
];

function precache() {
    return caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheAssets);
    });
}

self.addEventListener('install', function(event) {
    event.waitUntil(precache());
});

Note that the index.html (that registers the Service Worker) page is just a template, that gets populated on the server before being sent to the client ; so in this pre-caching phase, I'm only caching the template, not the page.

Now, in the fetch event, any requested resource that is not in the cache gets copied to it:

addEventListener('fetch', event => {
    event.respondWith(async function() {
        const cachedResponse = await caches.match(event.request);
        if (cachedResponse) return cachedResponse;       
        return fetch(event.request).then(updateCache(event.request));
    }());
});

Using this update function

function updateCache(request) {
    return caches.open(cacheName).then(cache => {
        return fetch(request).then(response => {
            const resClone = response.clone();
            if (response.status < 400)
                return cache.put(request, resClone);
            return response;
        });
    });
}

At this stage, all the assets are in the cache, but not the dynamically generated page. Only after a reload, can I see another entry in the cache: /XV252GTH. Now, the app is offline-ready ; But this reloading of the page kind of defeats the whole Service Worker purpose.

Question: How can I send the request (/XV252GTH) from the client (the page that registers the worker) to the SW? I guess I can set up a listener in sw.js

self.addEventListener('message', function(event){
    updateCache(event.request)
});

But how can I be sure that it will be honored in time, ie: sent by the client after the SW has finished installing? What is a good practice in this case?

I have this one-page app with a dynamic URL built with a token, like example./XV252GTH and various assets, like css, favicon and such.

Here is how I register the Service Worker:

navigator.serviceWorker.register('sw.js');

And in said sw.js, I pre-cache the assets while installing:

var cacheName = 'v1';

var cacheAssets = [
    'index.html',
    'app.js',
    'style.css',
    'favicon.ico'
];

function precache() {
    return caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheAssets);
    });
}

self.addEventListener('install', function(event) {
    event.waitUntil(precache());
});

Note that the index.html (that registers the Service Worker) page is just a template, that gets populated on the server before being sent to the client ; so in this pre-caching phase, I'm only caching the template, not the page.

Now, in the fetch event, any requested resource that is not in the cache gets copied to it:

addEventListener('fetch', event => {
    event.respondWith(async function() {
        const cachedResponse = await caches.match(event.request);
        if (cachedResponse) return cachedResponse;       
        return fetch(event.request).then(updateCache(event.request));
    }());
});

Using this update function

function updateCache(request) {
    return caches.open(cacheName).then(cache => {
        return fetch(request).then(response => {
            const resClone = response.clone();
            if (response.status < 400)
                return cache.put(request, resClone);
            return response;
        });
    });
}

At this stage, all the assets are in the cache, but not the dynamically generated page. Only after a reload, can I see another entry in the cache: /XV252GTH. Now, the app is offline-ready ; But this reloading of the page kind of defeats the whole Service Worker purpose.

Question: How can I send the request (/XV252GTH) from the client (the page that registers the worker) to the SW? I guess I can set up a listener in sw.js

self.addEventListener('message', function(event){
    updateCache(event.request)
});

But how can I be sure that it will be honored in time, ie: sent by the client after the SW has finished installing? What is a good practice in this case?

Share Improve this question edited Apr 18, 2019 at 13:00 yPhil asked Apr 18, 2019 at 12:57 yPhilyPhil 8,3875 gold badges61 silver badges91 bronze badges 1
  • 1 Can't you use the browsers history api? – Tschallacka Commented Apr 18, 2019 at 12:58
Add a ment  | 

2 Answers 2

Reset to default 5

OK, I got the answer from this page: To cache the very page that registers the worker at activation time, just list all the SW's clients, and get their URL (href attribute).

self.clients.matchAll({includeUncontrolled: true}).then(clients => {
    for (const client of clients) {
        updateCache(new URL(client.url).href);
    }
});

Correct me if I understood you wrong!
You precache your files right here:

var cacheAssets = [
    'index.html',
    'app.js',
    'style.css',
    'favicon.ico'
];

function precache() {
    return caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheAssets);
    });
}

It should be clear that you cache the template since you cache it before the site gets build and this approach is not wrong, at least not for all types of files.
Your favicon.ico for example is a file that you would probably consider as static. Also, it does not change very often or not at all and it isn't dynamic like your index.html.
Source

It should also be clear why you have the correct version after reloading the page since you have an update function.

The solution to this problem is the answer to your question:

How can I send the request (/XV252GTH) from the client (the page that registers the worker) to the SW?

Instead of caching it before the service-worker is installed you want to cache it if the back end built your web page. So here is how it works:

  1. You have an empty cache or at least a cache without your index.html.
  2. Normally a request would be sent to the server to get the index.html. Instead, we do a request to the cache and check if the index.html is in the cache, at least if you load the page for the first time.
  3. Since there is no match in the cache, do a request to the server to fetch it. This is the same request the page would do if it would load the page normally. So the server builds your index.html and sends it back to the page.
  4. After receiving the index.html load it to the page and store it in the cache.

An example method would be Stale-while-revalidate:

If there's a cached version available, use it, but fetch an update for next time.

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});

Source

Those are the basics for your problem. Now you got a wide variety of options you can choose from that use the same method but have some additional features. Which one you choose is up to you and without knowing your project in detail no one can tell you which one to choose. You are also not limited to one option. In some cases you might bine two or more options together.

Google wrote a great guide about all the options you have and provided code examples for everything. They also explained your current version. Not every option will be interesting and relevant for you but I remend you to read them all and read them thoroughly.

This is the way to go.

发布评论

评论列表(0)

  1. 暂无评论