【问题标题】:Managing Service Worker cache管理 Service Worker 缓存
【发布时间】:2017-05-27 18:47:18
【问题描述】:

我目前正在试验的服务工作者代码部分看起来像这样

self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open('v1').then(function(cache) {
            return cache.addAll([
                '/react-redux/node_modules/react/dist/react-with-addons.js',
                '/react-redux/node_modules/react-dom/dist/react-dom.js',
                '/react-redux/a.js'
            ]);
        })
    );
});

当然有标准的 fetch 事件监听器,它从缓存返回,或者如果项目不存在则运行网络请求。

但是,如果在上面的示例中,a.js,并且仅更新了 a.js,我该如何让服务人员更新该文件,但 仅强>那个文件;以及如何确保用户下次浏览我的页面时,他们不会从 service worker 中提取文件的过时版本?

我能想象到的最好的办法就是为这些文件的 url 添加一个缓存破坏器,例如

'/react-redux/node_modules/react/dist/react-with-addons.js?hash=1MWRF3...'

然后使用相同的当前哈希/缓存破坏器更新我用来请求这些文件的任何模块加载器,然后在 SW 安装事件中迭代当前缓存键并删除任何过时的内容,并添加任何丢失的内容.

这会似乎解决了这两个问题:当文件被更新时,发送的网络请求将与现在陈旧的 Service Worker 中的任何内容都不匹配,因此会发生相同的网络回退;并且 Service Worker 的安装事件中的选择性缓存插入不会尝试将已经存在且当前的缓存添加到缓存中。

当然,Service Worker 代码会随着这些哈希值的变化(在构建过程中自动发生)而变化,因此也会在文件发生变化时重新安装软件。

但我不禁想到有一种更简单的方法。有吗?

【问题讨论】:

    标签: javascript service-worker


    【解决方案1】:

    您对理想情况下应该发生的事情的理解,以及确保缓存资产得到有效和可靠更新的困难,都是正确的。

    虽然您可以采用自己的方法,但现有工具可以自动识别每个文件的指纹,然后生成一个服务工作人员文件来管理您的缓存资产。我开发了其中一个,sw-precache。 offline-plugin 是另一个覆盖相似领域的替代方案。

    【讨论】:

    • 杰出 - 将查看这些资源。万分感谢。所以……你知道/和杰克·阿奇博尔德一起工作吗? :)
    • 是的,我确实有做杰克同事的身份!
    • 那么 sw-precache - 它到底是如何与客户端/模块加载集成的?您可能正在使用 Webpack、SystemJS 或其他选项 - sw-precache 是否输出正确的 url 以使用,您有责任将它们与您的客户端加载器同步?
    • sw-precache 查看构建完成后本地存在的文件名,并将使用基于这些文件名的 URL 来填充其缓存。不应该特别需要调整您的客户端加载程序。如果您确实遇到问题,请通过github.com/GoogleChrome/sw-precache/issues 告诉我们
    • 一般来说,使用缓存优先策略意味着下次访问时一切都将“陈旧”,并且更新仅在 N+1 次访问时可见。检测 SW 更新,然后显示“请重新加载...”toast 消息是当前的 UX 最佳实践。
    【解决方案2】:

    我最终完全按照你所说的编写了代码,这里是任何有困难的人自己编写的代码:

    首先,我们需要编写代码,以便在每次捆绑包更改时为捆绑包文件的 URL 添加时间戳/哈希。

    我们大多数人都使用 webpack 将应用程序捆绑在一起,每次执行 webpack 配置文件时,捆绑包都会发生变化,因此我们将在 URL 中插入哈希/时间戳。我有一个名为 index.template.html 的文件,我在其中存储提供给用户的文件,以便修改我这样做的 URL:

    // webpack.config.js
    
    const webpack = require('webpack');
    const fs = require('fs');
    
    fs.readFile('./public/index.template.html', function (err, data) {
        if (err) return console.log('Unable to read index.template file', err);
        fs.writeFile('./public/index.template.html',
            // finding and inserting current timestamp in front of the URL for cache busting
            data.toString('utf8').replace(/bundle\.js.*"/g, "bundle\.js\?v=" + Math.floor(Date.now() / 1000) + "\""),
            (err) => {
                if (err) console.log("Unable to write to index.template.html", err);
            });
    });
    
    module.exports = {
        // configuration for webpack
    };
    

    现在这里是 service worker 的代码,它检测 URL 的变化并在发生变化时重新获取和替换缓存中的资源,我试图解释 cmets 中的工作:

    self.addEventListener("fetch", function (event) {
        event.respondWith(
            // intercepting response for bundle.js since bundle.js may change and we need to replace it in our cahce
            event.request.url.indexOf('public/bundle.js') != -1 ?
            checkBundle(event.request) : //if it is the bundle URL then use our custom function for handling the request
            caches.match(event.request) //if its not then do the use service-worker code:
                .then(function(response) {
                    // other requests code
                })
            );
    });
    
    // our custom function which does the magic:
    function checkBundle(request) {
        return new Promise(function(resolve, reject){ // respondWith method expects a Promise
            caches.open(cacheName).then(function(cache) {
                 //first lets check whether its in cache already or not
                 // ignoreSearch parameter will ignore the query parameter while searching in cache, i.e., our cache busting timestmap
                cache.keys(request, { ignoreSearch: true }).then(function(keys) {    
                    if(keys.length == 0) {
                        // its not in cache so fetch it
                        return resolve(fetch(request).then(
                            function (response) {
                                if (!response || (response.status !== 200 && response.status !== 0)) {
                                    return response;
                                }                  
                                cache.put(request, response.clone());                           
                                return response;
                            }
                        ));
                    }
                    //it is in cache, so now we extract timestamp from current and cached URL and compare them
                    const lastVersion = /bundle.js\?v=(.*)$/.exec(keys[0].url)[1],
                        curVersion = /bundle.js\?v=(.*)$/.exec(request.url)[1];
    
                    if(lastVersion == curVersion) // if timestamp is change that means no change in the resource
                        return resolve(cache.match(request)); //return the cached resource
    
                    //bundle file has changed, lets delete it from cache first
                    cache.delete(keys[0]);
                    //now we fetch new bundle and serve it and store in cache
                    var fetchRequest = request.clone();
                    resolve(fetch(fetchRequest).then(
                        function (response) {
                            if (!response || (response.status !== 200 && response.status !== 0)) {
                                return response;
                            }                  
                            cache.put(request, response.clone());                           
                            return response;
                        }
                    ));
                  });
            });
        });
    }
    

    As mentioned by Jeff Posnick in the comment of other answers 通常这些类型的方法需要 N+1 次访问才能看到更新的资源,但这一次不需要,因为资源被重新获取然后提供给客户端并同时在缓存中替换。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-14
      • 1970-01-01
      • 2019-10-08
      • 2020-02-10
      • 2018-02-19
      • 1970-01-01
      • 1970-01-01
      • 2018-12-24
      相关资源
      最近更新 更多