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