Управление кэшем работника службы


Код работника службы, с которым я сейчас экспериментирую, выглядит примерно так:

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 обновляется-как заставить работника службы обновить этот файл, но только и как я могу гарантировать, что в следующий раз, когда пользователь перейдет на мою страницу, они не заберет ли устаревшую версию файла у работника службы?

Лучшее, что я могу себе представить, - это добавить к этим URL-адресам кэш-Бастер, например

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

Затем обновите любой загрузчик модулей, который я использую, чтобы запросить эти файлы с тем же самым, текущим хэшем / Cache buster, а затем в событии установки SW повторите текущие ключи кэша и удалите все, что устарело, и добавьте все, что отсутствует.

Что будет решите обе проблемы: когда файл обновляется, сетевой запрос, который отправляется, не будет соответствовать ничему в устаревшем сервисном работнике, и поэтому произойдет тот же сетевой резерв; и выборочная вставка кэша в событие установки сервисного работника не будет пытаться добавить в кэш то, что уже есть и является текущим.

И, конечно, код работника службы будет меняться по мере изменения этих хэш-значений (автоматически из процесса сборки) и, таким образом, получение SW для повторной установки, когда файлы изменение может произойти, а также.

Но я не могу не думать, что есть более простой способ. А что, есть?
2 6

2 ответа:

Ваше понимание того, что должно произойти в идеале, и трудности в обеспечении эффективного и надежного обновления кэшированных ресурсов, являются точными.

Хотя вы можете использовать свой собственный подход, существуют инструменты, которые автоматизируют процесс снятия отпечатков пальцев с каждого файла и последующего создания файла service worker, который управляет вашими кэшированными активами. Я разработал один из них., sw-precache. offline-plugin есть еще одна альтернатива, которая охватывает аналогичную почву.

В итоге я написал код именно для того, что вы сказали, Вот код для тех, у кого возникли трудности с написанием его самостоятельно:

Во-первых, нам нужно написать код для добавления метки времени/хэша к 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 и повторно извлекает и заменяет ресурс в кэше в случае изменения, я попытался объяснить работу в комментариях:

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

Как упоминал Джефф Посник в комментарии к другим ответам обычно эти типы методов требуют N + 1 посещений, чтобы увидеть обновленный ресурс, но это не так, поскольку ресурс является повторно взятый затем подается клиенту и заменяется в кэше в то же время.