PWA入門之路

因爲項目中有個問題涉及到了Service Worker,因此找了時間去研究了一下PWA。也趁此寫一篇文章總結一下。javascript

前言:PWA做爲今年最火熱的技術概念之一,對提高Web應用的安全、性能和體驗有着很大的意義,很是值得咱們去了解與學習。css

什麼是PWA

PWA,全稱Progressive Web App,即漸進式WEB應用, 是提高 Web App 的體驗的一種新方法,能給用戶原生應用的體驗。它的優點主要體如今:html

  • 可在離線網絡較差的環境下正常打開頁面。
  • 安全(HTTPS)。
  • 保持最新(及時更新)。
  • 支持安裝(添加到主屏幕)和消息推送
  • 向下兼容,在不支持相關技術的瀏覽器中仍可正常訪問。

PWA自己實際上是一個概念集合,它不是指某一項技術,而是經過一系列的Web技術與Web標準來優化Web App的安全、性能和體驗。其中涉及到的一些技術概念包括但不限於:java

  • Web App Manifest
  • Service Worker
  • Cache API 緩存
  • Push&Notification 推送與通知
  • Background Sync 後臺同步

本文主要講一下Service Worker相關的東西。git

Service Worker

1. 什麼是Service Worker?

Service worker是一個註冊在指定源和路徑下的事件驅動worker。它採用JavaScript控制關聯的頁面或者網站,攔截並修改訪問和資源請求,細粒度地緩存資源。你能夠徹底控制應用在特定情形(最多見的情形是網絡不可用)下的表現。es6

Service worker運行在worker上下文,所以它不能訪問DOM。相對於驅動應用的主JavaScript線程,它運行在其餘線程中,因此不會形成阻塞。它設計爲徹底異步,同步API(如XHR和localStorage)不能在service worker中使用。chrome

下圖展現普通Web App與添加了Service Worker的Web App在網絡請求上的差別:數組

img

2. 使用Service Worker

2.1. 註冊Service Worker

在index.js文件裏面註冊Service Worker。promise

// index.js
// 註冊service worker,service worker腳本文件爲sw.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function () {
        console.log('Service Worker 註冊成功');
    });
}
複製代碼

值得一提的是,Service Worker裏的各種操做都被設計爲異步,以免一些長時間的阻塞操做。這些異步操做都是創建在Promise的基礎上的,若是你對Promise不夠了解,建議去熟悉一下Promise。傳送門:Promise(ES6標準入門)瀏覽器

2.2. 使用Service Worker

Service Worker的生命週期

當咱們註冊了Service Worker後,它會經歷生命週期的各個階段,同時會觸發相應的事件。整個生命週期包括了:installing --> installed --> activating --> activated --> redundant。當Service Worker安裝(installed)完畢後,會觸發install事件;而激活(activated)後,則會觸發activate事件。

下面的例子監聽了install事件

// 在sw.js裏面
// 監聽install事件
self.addEventListener('install', function (e) {
    console.log('Service Worker installed');
});
複製代碼

self是Service Worker中的一個特殊的全局變量,相似於windowself指向當前這個Service Worker。

緩存靜態資源

通常狀況下,咱們會列出一個須要緩存的資源列表,當Service Worker install時,會將改列表的資源緩存下來。

// sw.js
var cacheName = 'v1';
var cacheFiles = [
    '/',
    './index.html',
    './index.js',
    './index.css'
];

// 監聽install事件,安裝完成後,進行文件緩存
self.addEventListener('install', e => {
    console.log(e);
    e.waitUntil(
        caches.open(cacheStorageKey)
        .then(cache => cache.addAll(cacheList))
        .then(_ => self.skipWaiting()) // 該函數可以使新的sw.js立刻生效。
    );
})
複製代碼

看完這段代碼,你可能會有所疑惑。caches是個什麼鬼東西?

caches是暴露在window做用域的一個變量,咱們經過caches屬性訪問CacheStorage

CacheStorage是一種新的本地存儲,它的存儲結構是這樣的:

每一個域有若干個存儲模塊,每一個模塊內能夠存儲若干個鍵值對。 它的鍵是網絡請求(Request),值是請求對應的響應(Response)。 CacheStorage的接口集中在全局變量caches中,且僅在HTTPS協議(或localhost:*域)下可用。

咱們在chrome上的devtool-application中能夠看到CacheStorage的相關信息。

image-20190106172427647.png

介紹變量caches經常使用方法

  • open(cacheName)

    返回一個 Promise,resolve爲匹配 cacheName (若是不存在則建立一個新的cache)的 Cache對象。

  • keys()

    返回一個 Promise ,它將使用一個包含與 CacheStorage 追蹤的全部命名 Cache對象對應字符串的數組來resolve。 使用該方法迭代全部 Cache對象的列表。

Cache對象經常使用方法

  • match(request, options)

    返回一個 Promise對象,resolve的結果是跟 Cache對象匹配的第一個已經緩存的請求。

  • add(request)

    抓取這個URL,檢索並把返回的response對象添加到給定的Cache對象。這在功能上等同於調用 fetch(),而後使用 Cache.put() 將response添加到cache中。

  • addAll(requests)

    抓取一個URL數組,檢索並把返回的response對象添加到給定的Cache對象。

  • put(request, response)

    同時抓取一個請求及其響應,並將其添加到給定的cache。

  • keys(request, options)

    返回一個Promise對象,resolve的結果是Cache對象key值組成的數組。

更多詳細介紹和方法請查閱MDN-CacheStorageMDN-Cache

看到這裏你可能又會問,Request???Response???

這裏跟Fetch API有着密切的關係。

Request對象,用來表示資源的請求。

Response對象,用來表示一次請求的響應數據。

詳細資料傳送門在這裏:RequestResponse

咱們打印一下這兩個東西,就很是明瞭了。

image-20190106173445713.png

好了,接下來繼續咱們的Service Worker。

咱們能夠給 service worker 添加一個 fetch 的事件監聽器,接着調用 event 上的 respondWith() 方法來劫持咱們的 HTTP 響應,而後咱們就能夠進行一波操做了。

// sw.js
self.addEventListener('fetch', e => {
    e.respondWith(
        caches.match(e.request).then(res => {
            return res || fetch(e.request);
        })
    )
})
複製代碼

這裏的邏輯是這樣的:

  1. 瀏覽器發起請求,請求各種靜態資源(html/js/css/img);
  2. Service Worker攔截瀏覽器請求,並查詢當前cache;
  3. 若存在cache則直接返回,結束;
  4. 若不存在cache,則經過fetch方法向服務端發起請求,並返回請求結果給瀏覽器。

最終這裏就簡單實現了緩存靜態資源文件的目的了。

更新靜態緩存資源

咱們經過修改cacheName來達到更新緩存資源的目的。因爲瀏覽器判斷sw.js是否更新是經過字節方式,所以修改cacheName會從新觸發install並緩存資源。此外,在activate事件中,咱們須要檢查cacheName是否變化,若是變化則表示有了新的緩存資源,原有緩存須要刪除。

self.addEventListener('activate', e => {
    e.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cache => {
                    if (cache !== cacheStorageKey) {
                        return caches.delete(cache);
                    }
                })
            )
        })
    )
    return self.clients.claim();
})
複製代碼

最後咱們能夠在network看到請求資源的信息。

image-20190106175113184.png

資源來自於Service Worker,時間也是在10ms左右,能夠說是很是快的加載速度了,這樣的體驗對用戶很是友好。

2.4. Service Worker其餘功能

除了緩存靜態資源文件之外,Service Worker還有緩存API數據,進行消息提醒,後臺同步的功能。東西不少,目前還在慢慢探索當中。

參考資料

相關文章
相關標籤/搜索