原文连接:The offline cookbook 做者:Jake Archibaldcss
[译]前端离线指南(下)html
使用AppCache能够为咱们提供几种支持内容离线工做的模式。若是这些模式正是你所须要的,那么恭喜你,你中了APPCache的大奖(尽管头等奖依然无人认领),但咱们这些其他的人都挤在角落里来回摇摆(译者注:做者指的是因为设计上的缘由,AppCache逐渐地被Web标准移除,虽然如今依然有浏览器支持这个功能,但最好不要再使用它了)前端
对于ServiceWorker(介绍),咱们放弃尝试去解决离线问题,而且给开发者们提供灵活的组件从而让他们本身去解决离线问题。它为您提供了控制缓存和处理请求的方式。这就意味着您能够建立您本身的模式。接下来让咱们来看一下几个隔离环境下的可行模式,可是在实践中,您可能会根据URL和context以串联方式用到其中的多个模式。html5
目前,除非另有说明,全部的示例代码均可以运行在Chrome和Firefox浏览器中。关于ServiceWorker支持程度的完整详情,请查阅"Is Service Worker Ready?"。git
有关对于其中部分模式的运行演示,请查阅Trained-to-thrill,而且此处的视频将向您展现性能影响。github
您能够经过ServiceWorker来独立地从缓存中处理请求,因此咱们要先单独地研究一下它们。首先,咱们啥时候应该进行缓存呢?json
ServiceWorker提供给您一个install
事件,您可使用它把资源准备好,即在处理其余事件以前必需要提早准备好的东西。可是当这些操做正在进行中的时候,任何旧版本的ServiceWorker仍旧在运行而且提供给页面,所以您在此处进行的操做必定不能中断它们。promise
适用于: CSS、图片、字体、JS文件、模板等,基本包含了你认为网站在当前“版本”中应该须要的全部静态资源。浏览器
若是未能获取上述资源,那么您的网站彻底没法运行,对应的本机应用会将这些对象包含在初始下载中。缓存
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('mysite-static-v3').then(function(cache) {
return cache.addAll([
'/css/whatever-v3.css',
'/css/imgs/sprites-v6.png',
'/css/fonts/whatever-v8.woff',
'/js/all-min-v4.js'
// etc
]);
})
);
});
复制代码
event.waitUntil
接受一个promise对象做参数,来定义安装时长和安装是否成功,若是promise状态为rejected,则认为这次安装失败,而且抛弃ServiceWorker(若是一个旧版本的ServiceWorker正在运行,则它将保持不变)。caches.open
和caches.addAll
都返回promise对象,若是其中有任何一个资源获取失败,则caches.addAll
会调用reject。 在trained-to-thrill 上,我使用此方法缓存静态资源。
此方式与上述类似,但区别是:即便缓存失败,既不会延迟安装也不会致使安装失败。
适用于: 体积较大的,且暂时用不到的资源,好比用于游戏的较高级别的资源。
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('mygame-core-v1').then(function(cache) {
cache.addAll(
// levels 11-20
);
return cache.addAll(
// core assets & levels 1-10
);
})
);
});
复制代码
咱们没有将levels 11-20的cache.addAll
promise对象,返回给event.waitUntil
,因此事件即便失败,游戏在离线的时候依然可使用。固然,您必须考虑到缺乏这些level的状况,若是缺乏它们,则尝试从新缓存它们。
在当level 11-20正在下载的时候,ServiceWorker可能会终止,由于它已经完成处理事件。这就意味着它们就不会被缓存下来。将来,咱们计划添加一个在后台下载的API以处理相似这样的状况,以及下载像电影同样的大致积文件。
适用于: 清理和迁移
在新的ServiceWorker已经被安装,而且较早版本的sw没有在使用的状况下,则新的ServiceWorker会被激活,您就会获得一个activate事件。因为旧版本的退出,因此此时很是适合处理 IndexedDB 中的架构迁移和删除未使用的缓存。
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
// 若是您想删除缓存,则返回true,
// 可是请记住缓存在该域名内的全部页面之间
// 是共享的
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
复制代码
在激活的过程当中,诸如fetch
等事件会被放置在一个队列中,因此一个长时间的激活可能会阻塞页面加载。保证您的激活尽量地简洁,仅用于旧版本处于活动状态时没法执行的操做。
在trained-to-thrill上,我使用此方法移除旧缓存。
适用于: 若是整个站点没法离线工做,您能够容许用户选择须要离线的可用内容,好比,YouTube上的某个视频,维基百科上的某篇文章,Flickr上的某张图片等等。
为用户提供一个“稍后阅读”或者“离线保存”的按钮。当点击按钮,从网络中获取您所须要的内容并把它放进缓存中。
document.querySelector('.cache-article').addEventListener('click', function(event) {
event.preventDefault();
var id = this.dataset.articleId;
caches.open('mysite-article-' + id).then(function(cache) {
fetch('/get-article-urls?id=' + id).then(function(response) {
// /get-article-urls returns a JSON-encoded array of
// resource URLs that a given article depends on
return response.json();
}).then(function(urls) {
cache.addAll(urls);
});
});
});
复制代码
cache
API在页面既能够在ServiceWorker中获取到,也能够在页面中获取到,这就意味着你没必要必定要经过ServiceWorker来向缓存中添加内容。
适用于: 频繁更新的资源,好比用户收件箱,或者文章内容。一样适用于不重要但须要谨慎处理的内容,好比用户头像。
若是请求的资源与缓存中的任何资源均不匹配,则从网络中获取,将其发送到页面中,同时将其添加到缓存中。
若是您针对一系列网址执行此操做,如头像,那么您须要谨慎,不要使域名下的存储变得臃肿,若是用户须要回收磁盘空间,您不会想成为主要候选对象。请确保将缓存中再也不须要的项目删除。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
复制代码
为了高效使用内存,只容许读取一次response
或request
的body
,在上面的代码中,使用.clone
来建立可以单独地读取数据的额外副本。
在trained-to-thrill上,我使用此方法缓存Flickr图像。
适用于: 频繁更新,但却不必获取最新的资源。用户头像就属于此类。
若是缓存中已经有一个可用的版本,直接使用该版本,可是会为了下一次的请求而获取一个更新版本。
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;
})
})
);
});
复制代码
它和 HTTP 的 stale-while-revalidate 很是类似。
注意: Chrome暂时还不支持Push。(译者注:Chrome 50及以后的版本开始支持,更多信息请参考 can i use)
Push API是基于ServiceWorker构建的另外一个功能。它容许唤醒ServiceWorker以响应来自系统服务的消息,即便用户没有为您的站点打开一个标签,Push API也一样能够工做。只有ServiceWorker被唤醒。您从页面请求执行此操做权限的同时,用户也将收到提示。
适用于: 与通知有关的内容,好比聊天消息,突发新闻,或者Email等。一样适用于不常常更改的可当即同步的内容,例如待办事项更新或者日程表的更改。
用户常见的页面表现是,出现一个通知,当点击它的时候,会打开或者聚焦到相关的页面,可是在点击它以前,务必要更新缓存。显然,用户在收到推送消息的时候,必定是在线的,可是,当他们最终与通知交互时可能已经离线,所以,容许离线访问此内容很是重要。Twitter原生应用在大多数状况下都是很是好的离线优先例子,但在这点上却有点小问题。
若是没有网络链接,Twitter没法提供与推送消息相关的内容。可是点按通知会移除通,从而使用户获取的信息比点按通知以前还要少。不要这么作!
下面的代码会在展现通知以前更新缓存。
self.addEventListener('push', (event) => {
if (event.data.text() == 'new-email') {
event.waitUntil(async function() {
const cache = await caches.open('mysite-dynamic');
const response = await fetch('/inbox.json');
await cache.put('/inbox.json', response.clone());
const emails = await response.json();
registration.showNotification("New email", {
body: "From " + emails[0].from.name
tag: "new-email"
});
}());
}
});
self.addEventListener('notificationclick', function(event) {
if (event.notification.tag == 'new-email') {
// Assume that all of the resources needed to render
// /inbox/ have previously been cached, e.g. as part
// of the install handler.
new WindowClient('/inbox/');
}
});
复制代码
注意: 后台同步还没有加入到Chrome稳定版本中。(译者注:Chrome 49及以后的版本中开始支持,但FireFox、Safari还没有支持,更多信息请参考 can i use)
后台同步是基于ServiceWorker来构建的另外一个功能。它容许您一次性地,或者按照(很是具备启发性的)时间间隔来请求后台数据同步。即便用户没有为您的站点打开一个标签,后台同步也一样能够工做。只有ServiceWorker被唤醒。您从页面请求执行此操做权限的同时,用户也将收到提示。
适用于: 不紧急的更新,尤为是那些按期进行的更新,每次更新都发送一个推送消息显得太频繁,好比社交时间表和新闻资讯。
请继续阅读: [译]前端离线指南(下)