Progressive Web Applications take advantage of new technologies to bring the best of mobile sites and native applications to users. They're reliable, fast, and engaging.
基礎知識
用戶首次訪問service worker控制的網站或頁面時,service worker會立刻被下載。
ServiceWorker(web worker 的一種)接口
Cache:表示對request/response對象的存儲,一個域可以有多個 Cache 對象. 你將在你的代碼中處理和更新緩存 . 在 Cache 除非顯示地更新緩存, 否則緩存將不會被更新; 緩存數據不會過期, 除非刪除它
Cache.match(request, options)返回一個Promise,查找cache中匹配的request
Cache.match(request, options)匹配一個數組對象中的request
Cache.add(request)發送請求並將請求放入cache中,
Cache.put(request, response)將request和response都添加到cache中
Cache.delete(request, options) 才cache中查找鄉音的值,並刪除返回一個promise,resoleve為true,如果找不到返回false
Cache,keys(request, options)返回一個promise,resolve為所有的cache鍵值
CacheStorage: 對Cache對象的存儲,提供命名緩存的主目錄,sw可以通過訪問並維護名字字符串到Cache對象的映射
caches.open(cacheName).then(names){};//打開一個cache對象
Client: 表示sw client的作用域。
sw.js中的self:這個關鍵字表示的是一個service worker 的執行上下文的一個全局屬性(ServiceWorkerGlobalScope),類似於window對象,不過這個self是作用於service worker的全局作用域中。
sw生命周期
覆蓋率
注意點
- 基於https
可以使用http-server+ngrok配合,當然更簡單的使用github。
2.Service worker是一個注冊在指定源和路徑下的事件驅動worker。實際上 SW 在你網頁加載完成同樣也能捕獲已經發出的請求,所以,為了減少性能損耗,我們一般直接在 onload 事件里面注冊 SW 即可。 - 作用域問題
SW 的作用域不同,監聽的 fetch 請求也是不一樣的。 例如,我們將注冊路由換成: /example/sw.js,那么,SW 后面只會監聽 /example 路由下的所有 fetch 請求,而不會去監聽其他
register
if(navigator.serviceWorker){
navigator.serviceWorder.register('sw.js')
.then(registration => {
console.log(`registered event at scope:${registration.scope}`);
})
.cache(err => {
throw err;
})
}
install
self.addEventListener('install', function(event) {
// Perform install steps
});
緩存文件
const cacheVersion = 'v1';
const cacheList = [
'/',
'index.html',
'logo.png',
'manifest.json',
'/dist/build.js'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(cacheVersion).then(function(cache) {
return cache.addAll([cacheList]);
})
);
});
event.waitUntil()參數必須為promise,它可以延長一個事件的作用時間,因為我們在打開緩存或者更新的時候很有可能會有延遲,而event.waitUntil()可以防止事件終端。另外它會監聽所有的異步promise,一旦有一個reject那么該次event便是失敗的,也就是說sw啟動失敗。當然如果有些文件比較大不好緩存的話別讓它返回就好了:
cache.addAll([cachelist1]);
return cache.addAll([cachelist2]);
fetchEvent
緩存捕獲,當發起請求的時候將request和response緩存下來(緩存一開始定義的緩存列表)。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => {
if(response){
return reponse;
}
return fetch(event.request);
})
)
})
這是個比較簡單的格式,event.respondWith(r),包含請求響應代碼,可以設置一個參數r,r是一個promise,resolve之后是一個response對象。整段代碼意思就是當請求一個文件時,如果緩存中已經有了,那么就直接返回緩存結果,否則發起請求。
問題:如果沒有緩存我們怎么處理?
- 等下次sw根據路由去緩存;
- 手動緩存
手動緩存
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if(response){
return response;
}
//fetch請求的request、response都定義為stream對象,所以只能讀一次這里需要clone一個新的
let requestObj = event.request.clone();
return fetch(requestObj)
.then(response => {
//檢測是否成功
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
//如果請求成功,第一要去渲染,第二要緩存
//cache.put()也使用stream,所以這里也需要復制一份
let responseObj = response.clone();
caches.open(cacheVersion)
.then(cache => {
cache.put(event.request, responseObj);
});
return response;
})
})
)
})
為什么stream只能讀一次?
當可讀流讀取一次之后可能已經讀到stream結尾或者stream已經close了,這里request和response都實現了clone接口來復制一份,所以在需要二次使用stream的時候就需要用副本來實現了。
刪除舊的緩存
self.addEventListener('activate', evnet => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cachename => {
if(cachename == cacheVersion){
return caches.delete(cachename);
}
})
).then(() => {
return self.clients.claim()
})
})
)
})
我們檢查之前保存的sw緩存,還要注意一點就是Promise.all()中不能有undefined,所以我們對於相同的版本要過濾,因而不使用map,避免返回undefined。
通過調用 self.clients.claim() 取得頁面的控制權, 這樣之后打開頁面都會使用版本更新的緩存。
更新
當你更新了你的sw文件,並修改了cacheVersion之后,刷新瀏覽器,期待的變化並沒有發生,因為雖然你改變了緩存版本,但是此時舊的sw還在控制整個應用,新的sw並沒有生效。這時就需要更新一下sw,有以下方法
- registration.update() ,也就是在注冊的時候選擇合適方式更新
navigator.serviceWorker.register('/sw.js').then(reg => {
// sometime later…
reg.update();
});
- 使用self.skipWaiting();
在install階段使用這個可以使得新的sw立即生效。
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// caching
);
});
- 調試手動更新
直接點擊update即可。
注意,我們更新了某些文件的時候也要同時更新sw中的緩存版本(cacheVersion)
manifest文件
這個文件主要是配置添加到桌面的一些基本信息,比如圖標啟動頁等。詳細可以看這個https://developer.mozilla.org/zh-CN/docs/Web/Manifest
下面是我寫的一個示例https://github.com/Stevenzwzhai/vue2.0-elementUI-axios-vueRouter/blob/master/pwa/sw.js
或者拉取這個項目https://github.com/Stevenzwzhai/PWA-demo
最近寫了個知乎日報的pwa,有興趣可以看下一篇文章
項目地址https://github.com/Stevenzwzhai/zhihu-daily
演示地址https://stevenzwzhai.github.io/zhihu-daily/