Service Worker:如何缓存第一个(动态的)页面

3

我有一个动态URL的单页应用程序,它使用令牌构建,例如example.com/XV252GTH,还有各种资源,如css、favicon等。

这是我注册Service Worker的方法:

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

在名为sw.js的文件中,我在安装过程中预缓存了资产:

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());
});

请注意,注册Service Worker的index.html页面只是一个模板,在发送到客户端之前会在服务器上填充;因此,在这个预缓存阶段,我只缓存模板而不是页面。
现在,在fetch事件中,任何未在缓存中的请求资源都会被复制到其中:
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));
    }());
});

使用此更新功能。
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;
        });
    });
}

在这个阶段,所有的资源都在缓存中,但动态生成的页面不在其中。只有在重新加载后,我才能看到缓存中的另一个条目:/XV252GTH。现在,应用程序已经具备离线功能;但是重新加载页面有点违背了整个Service Worker的目的。
问题:我该如何从客户端(注册worker的页面)将请求(/XV252GTH)发送给SW?我猜我可以在sw.js中设置监听器。
self.addEventListener('message', function(event){
    updateCache(event.request)
});

但是我怎么确定它会在时间上得到尊重,也就是说:客户端在软件安装完成后发送?在这种情况下,有什么好的做法呢?


1
你不能使用浏览器的历史记录API吗? - Tschallacka
2个回答

5

好的,我从这个页面得到了答案:为了缓存在激活时注册工作器的页面,只需列出所有SW的客户端,并获取其URLhref属性)。

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

1
我想引起注意,因为我认为这是一种更清晰的解决方案,并且更常见/最佳实践。 - Aaron3219

3

如果我理解有误,请纠正!
你在这里预加载你的文件:

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

function precache() {
    return caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheAssets);
    });
}
应该清楚,您缓存模板是因为在构建网站之前缓存它,这种方法并不是错误的,至少对于所有类型的文件都不是。
例如,您的favicon.ico是您可能考虑为静态的文件。此外,它不会经常更改或根本不会更改,并且不像您的index.html那样动态。
Schema 源代码

当您有一个更新函数时,重新加载页面后为什么会有正确的版本也应该清楚。

解决此问题的答案就是您的问题的答案:

如何将客户端(注册工作程序的页面)发送的请求(/XV252GTH)发送到SW?

与其在安装service-worker之前缓存它,不如在后端构建您的网页时缓存它。所以它是如何工作的:

  1. 您的缓存为空,或者至少没有您的index.html
  2. 通常会向服务器发送请求以获取index.html。相反,我们向缓存发送请求,并检查index.html是否在缓存中,至少在您第一次加载页面时是这样。
  3. 由于缓存中没有匹配项,请向服务器发出请求以获取它。这与页面正常加载页面时所做的请求相同。因此,服务器构建您的index.html并将其发送回页面。
  4. 在接收到index.html后,将其加载到页面并将其存储在缓存中。

一个示例方法是Stale-while-revalidate: enter image description here

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;
      })
    })
  );
});

来源

这些是解决你问题的基础知识。现在你有很多可以选择的选项,它们使用相同的方法但具有一些附加功能。你可以根据自己的项目需求来选择其中之一,没有人能告诉你应该选择哪一个。你也不限于只选择一个选项。在某些情况下,你可能会将两个或更多选项组合在一起。

谷歌编写了一份指南,介绍了所有可用选项,并为每个选项提供了代码示例。他们还解释了你的当前版本。并非每个选项都对你有趣和相关,但我建议你全部阅读并仔细阅读。

这是正确的方式。


嗨,亚伦,你似乎是PWA方面的专家,我遇到了类似的问题,我可以设置并缓存shell,但无法更新。你能快速看一下吗?https://stackoverflow.com/questions/55797611/tying-to-update-pwa-swupdate-isenabled-is-true-but-not-calling-the-subscribed-m - ishandutta2007
谢谢你的赞美。不幸的是,我对Angular不熟悉。我已经使用过其他前端框架,但还没有接触过这个。 - Aaron3219
我认为问题不在于Angular集成,那部分工作正常。我遇到的困难是如何升级,问题出在我对SwUpdate的理解上。如果您可以看一下PwaService的构造函数并告诉我是否有任何问题,那就足够了。 - ishandutta2007
好的,至少我看不到任何问题。这可能是你迄今为止没有收到任何答案的原因。我不知道你对 PWA(Portable Web Applications) 的熟悉程度如何。如果你相对较新,你可能会忘记更新服务工作人员所需的步骤。注销服务工作人员,删除缓存(特别是来自你页面的缓存),启用“重新加载时更新”...否则,可能只是旧的服务工作人员正在运行并且有旧缓存。如果您更改服务工作者的脚本,则默认情况下服务工作者不会自动更新。 - Aaron3219
谢谢Aaron。是的,我是PWA的新手,不太了解取消注册、删除缓存以及重新加载更新等方面。我想我没做过这些操作。让我先了解一下。我原本预期更改 ngsw-config.json 将会触发 this.swUpdate.available,但即使 this.swUpdate.isEnabled 为true,它也没有触发。 - ishandutta2007
我没有测试过这个,但它看起来比我的hack更加简洁,所以我接受了它;谢谢! - yPhil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接