我正在尝试的服务工作者代码在部分
中看起来像这样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
更新 - 如何让服务工作者更新该文件,但仅 那个文件;以及如何确保用户下次浏览我的页面时,他们不会从服务工作者那里拉出现在陈旧的文件版本?
我能想到的最好的方法是在这些文件网址中添加缓存破坏程序,例如
'/react-redux/node_modules/react/dist/react-with-addons.js?hash=1MWRF3...'
然后更新我正在使用的任何模块加载器来使用相同的当前散列/缓存buster请求这些文件,然后在SW安装事件中迭代当前缓存键并删除任何陈旧的东西,并添加任何缺少的东西
似乎解决了这两个问题:当文件更新时,发送的网络请求将与现在陈旧的Service Worker中的任何内容都不匹配,因此会发生相同的网络回退;并且Service Worker的安装事件中的选择性缓存插入不会尝试将事物添加到已存在且当前的缓存中。
当然,Service Worker代码会随着这些哈希值的变化而变化(自动从构建过程中),因此当文件发生变化时,SW也会重新安装。
但我不禁想到有一种更简单的方法。有吗?
答案 0 :(得分:6)
您对理想情况应该发生的事情的理解,以及确保缓存资产得到有效和可靠更新的困难,都是可靠的。
虽然您可以推出自己的方法,但现有的工具可以自动执行指纹识别每个文件的过程,然后生成管理缓存资产的服务工作文件。我开发了其中一个,sw-precache
。 offline-plugin
是涵盖类似理由的另一种选择。
答案 1 :(得分:1)
我最后编写的代码正是你所说的,这里的代码是任何有困难的人写的:
首先,我们需要编写代码,以便每次捆绑更改时将时间戳/哈希添加到捆绑文件的URL中。
我们大多数人都使用webpack将应用程序捆绑在一起,每次执行webpack配置文件时,bundle都会发生变化,所以我们将在这里插入hash中的hash / timestamp。我有一个名为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
};
现在这里是服务工作者的代码,它检测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;
}
));
});
});
});
}
As mentioned by Jeff Posnick in the comment of other answers通常这些类型的方法需要N + 1次访问才能查看更新的资源,但这一次不会因为重新获取资源然后提供给客户端并同时在缓存中替换