【PWA學(xué)習(xí)與實(shí)踐】(3) 讓你的WebApp離線可用

本文是《PWA學(xué)習(xí)與實(shí)踐》系列的第三篇文章。

PWA作為今年最火熱的技術(shù)概念之一铅鲤,對(duì)提升Web應(yīng)用的安全划提、性能和體驗(yàn)有著很大的意義,非常值得我們?nèi)チ私馀c學(xué)習(xí)邢享。

本系列文章《PWA學(xué)習(xí)與實(shí)踐》會(huì)逐步拆解PWA背后的各項(xiàng)技術(shù)腔剂,通過實(shí)例代碼來講解這些技術(shù)的應(yīng)用方式。也正是因?yàn)镻WA中技術(shù)點(diǎn)眾多驼仪、知識(shí)細(xì)碎掸犬,因此我在學(xué)習(xí)過程中,進(jìn)行了整理绪爸,并產(chǎn)出了《PWA學(xué)習(xí)與實(shí)踐》系列文章湾碎,希望能帶大家全面了解PWA中的各項(xiàng)技術(shù)。對(duì)PWA感興趣的朋友歡迎關(guān)注奠货。

本文中的代碼都可以在learning-pwa的sw-cache分支上找到(git clone后注意切換到sw-cache分支)介褥。

1. 引言

PWA其中一個(gè)令人著迷的能力就是離線(offline)可用。

即使在離線狀態(tài)下递惋,依然可以訪問的PWA

離線只是它的一種功能表現(xiàn)而已柔滔,具體說來,它可以:

  • 讓我們的Web App在無網(wǎng)(offline)情況下可以訪問萍虽,甚至使用部分功能睛廊,而不是展示“無網(wǎng)絡(luò)連接”的錯(cuò)誤頁;
  • 讓我們?cè)谌蹙W(wǎng)的情況下杉编,能使用緩存快速訪問我們的應(yīng)用超全,提升體驗(yàn);
  • 在正常的網(wǎng)絡(luò)情況下邓馒,也可以通過各種自發(fā)控制的緩存方式來節(jié)省部分請(qǐng)求帶寬嘶朱;
  • ……

而這一切,其實(shí)都要?dú)w功于PWA背后的英雄 —— Service Worker光酣。

那么疏遏,Service Worker是什么呢?你可以把Service Worker簡(jiǎn)單理解為一個(gè)獨(dú)立于前端頁面,在后臺(tái)運(yùn)行的進(jìn)程财异。因此下翎,它不會(huì)阻塞瀏覽器腳本的運(yùn)行,同時(shí)也無法直接訪問瀏覽器相關(guān)的API(例如:DOM宝当、localStorage等)。此外胆萧,即使在離開你的Web App庆揩,甚至是關(guān)閉瀏覽器后,它仍然可以運(yùn)行跌穗。它就像是一個(gè)在Web應(yīng)用背后默默工作的勤勞小蜜蜂订晌,處理著緩存、推送蚌吸、通知與同步等工作锈拨。所以,要學(xué)習(xí)PWA羹唠,繞不開的就是Service Worker奕枢。

PWA背后的英雄 —— Service Worker

在接下來的幾篇文章里,我會(huì)從如何使用Service Worker來實(shí)現(xiàn)資源的緩存佩微、消息的推送缝彬、消息的通知以及后臺(tái)同步這幾個(gè)角度,來介紹相關(guān)原理與技術(shù)實(shí)現(xiàn)哺眯。這些部分會(huì)是PWA技術(shù)的重點(diǎn)谷浅。需要特別注意的是,由于Service Worker所具有的強(qiáng)大能力奶卓,因此規(guī)范規(guī)定一疯,Service Worker只能運(yùn)行在HTTPS域下。然而我們開發(fā)時(shí)候沒有HTTPS怎么辦夺姑?別著急墩邀,還有一個(gè)貼心的地方——為方便本地開發(fā),Service Worker也可以運(yùn)行在localhost(127.0.0.1)域下盏浙。

好了磕蒲,簡(jiǎn)單了解了Service Worker與它能實(shí)現(xiàn)的功能后,我們還是要回到這一篇的主題只盹,也就是Service Worker的第一部分——如何利用Service Worker來實(shí)現(xiàn)前端資源的緩存辣往,從而提升產(chǎn)品的訪問速度,做到離線可用殖卑。

2. Service Worker是如何實(shí)現(xiàn)離線可用的站削?

這一小節(jié)會(huì)告訴大家,Service Worker是如何讓我們?cè)陔x線的情況下也能訪問Web App的孵稽。當(dāng)然许起,離線訪問只是其中一種表現(xiàn)十偶。

首先,我們想一下园细,當(dāng)訪問一個(gè)web網(wǎng)站時(shí)惦积,我們實(shí)際上做了什么呢?總體上來說猛频,我們通過與與服務(wù)器建立連接狮崩,獲取資源,然后獲取到的部分資源還會(huì)去請(qǐng)求新的資源(例如html中使用的css鹿寻、js等)睦柴。所以,粗粒度來說毡熏,我們?cè)L問一個(gè)網(wǎng)站坦敌,就是在獲取/訪問這些資源。

可想而知痢法,當(dāng)處于離線或弱網(wǎng)環(huán)境時(shí)狱窘,我們無法有效訪問這些資源,這就是制約我們的關(guān)鍵因素财搁。因此训柴,一個(gè)最直觀的思路就是:如果我們把這些資源緩存起來,在某些情況下妇拯,將網(wǎng)絡(luò)請(qǐng)求變?yōu)楸镜卦L問幻馁,這樣是否能解決這一問題?是的越锈。但這就需要我們有一個(gè)本地的cache仗嗦,可以靈活地將各類資源進(jìn)行本地存取。

如何獲取所需的資源甘凭?

有了本地的cache還不夠稀拐,我們還需要能夠有效地使用緩存、更新緩存與清除緩存丹弱,進(jìn)一步應(yīng)用各種個(gè)性化的緩存策略德撬。而這就需要我們有個(gè)能夠控制緩存的“worker”——這也就是Service Worker的部分工作之一。順便多說一句躲胳,可能有人還記得 ApplicationCache 這個(gè)API蜓洪。當(dāng)初它的設(shè)計(jì)同樣也是為了實(shí)現(xiàn)Web資源的緩存,然而就是因?yàn)椴粔蜢`活等各種缺陷坯苹,如今已被Service Worker與cache API所取代了隆檀。

Service Worker有一個(gè)非常重要的特性:你可以在Service Worker中監(jiān)聽所有客戶端(Web)發(fā)出的請(qǐng)求,然后通過Service Worker來代理,向后端服務(wù)發(fā)起請(qǐng)求恐仑。通過監(jiān)聽用戶請(qǐng)求信息泉坐,Service Worker可以決定是否使用緩存來作為Web請(qǐng)求的返回。

下圖展示普通Web App與添加了Service Worker的Web App在網(wǎng)絡(luò)請(qǐng)求上的差異:

普通Web請(qǐng)求(上)與使用Service Worker代理(下)的區(qū)別

這里需要強(qiáng)調(diào)一下裳仆,雖然圖中好像將瀏覽器腕让、SW(Service Worker)與后端服務(wù)三者并列放置了,但實(shí)際上瀏覽器(你的Web應(yīng)用)和SW都是運(yùn)行在你的本機(jī)上的歧斟,所以這個(gè)場(chǎng)景下的SW類似一個(gè)“客戶端代理”纯丸。

了解了基本概念之后,就可以具體來看下构捡,我們?nèi)绾螒?yīng)用這個(gè)技術(shù)來實(shí)現(xiàn)一個(gè)離線可用的Web應(yīng)用。

3. 如何使用Service Worker實(shí)現(xiàn)離線可用的“秒開”應(yīng)用

還記得我們之前的那個(gè)圖書搜索的demo Web App么壳猜?不了解的朋友可以看下本系列的第一篇文章勾徽,當(dāng)然你可以忽略細(xì)節(jié),繼續(xù)往下了解技術(shù)原理统扳。

沒錯(cuò)喘帚,這次我仍然會(huì)基于它進(jìn)行改造。在上一篇添加了manifest后咒钟,它已經(jīng)擁有了自己的桌面圖標(biāo)吹由,并有一個(gè)很像Native App的外殼;而今天朱嘴,我會(huì)讓它變得更酷倾鲫。

如果想要跟著文章內(nèi)容一起實(shí)踐,可以在這里下載到所需的全部代碼萍嬉。
記得切換到manifest分支乌昔,因?yàn)楸酒獌?nèi)容,是基于上一篇的最終代碼進(jìn)行相應(yīng)的開發(fā)與升級(jí)壤追。畢竟我們的最終目標(biāo)是將這個(gè)普通的“圖書搜索”demo升級(jí)為PWA磕道。

3.1. 注冊(cè)Service Worker

注意,我們的應(yīng)用始終應(yīng)該是漸進(jìn)可用的行冰,在不支持Service Worker的環(huán)境下溺蕉,也需要保證其可用性。要實(shí)現(xiàn)這點(diǎn)悼做,可以通過特性檢測(cè)疯特,在index.js中來注冊(cè)我們的Service Worker(sw.js):

// index.js
// 注冊(cè)service worker,service worker腳本文件為sw.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function () {
        console.log('Service Worker 注冊(cè)成功');
    });
}

這里我們將sw.js文件注冊(cè)為一個(gè)Service Worker肛走,注意文件的路徑不要寫錯(cuò)了辙芍。

值得一提的是,Service Worker的各類操作都被設(shè)計(jì)為異步,用以避免一些長(zhǎng)時(shí)間的阻塞操作故硅。這些API都是以Promise的形式來調(diào)用的庶灿。所以你會(huì)在接下來的各段代碼中不斷看到Promise的使用。如果你完全不了解Promise吃衅,可以先在這里了解基本的Promise概念:Promise(MDN)JavaScript Promise:簡(jiǎn)介往踢。

3.2. Service Worker的生命周期

當(dāng)我們注冊(cè)了Service Worker后,它會(huì)經(jīng)歷生命周期的各個(gè)階段徘层,同時(shí)會(huì)觸發(fā)相應(yīng)的事件峻呕。整個(gè)生命周期包括了:installing --> installed --> activating --> activated --> redundant。當(dāng)Service Worker安裝(installed)完畢后趣效,會(huì)觸發(fā)install事件瘦癌;而激活(activated)后,則會(huì)觸發(fā)activate事件跷敬。

Service Worker生命周期

下面的例子監(jiān)聽了install事件:

// 監(jiān)聽install事件
self.addEventListener('install', function (e) {
    console.log('Service Worker 狀態(tài): install');
});

self是Service Worker中一個(gè)特殊的全局變量讯私,類似于我們最常見的window對(duì)象。self引用了當(dāng)前這個(gè)Service Worker西傀。

3.3. 緩存靜態(tài)資源

通過上一節(jié)斤寇,我們已經(jīng)學(xué)會(huì)了如何添加事件監(jiān)聽,來在合適的時(shí)機(jī)觸發(fā)Service Worker的相應(yīng)操作∮倒樱現(xiàn)在娘锁,要使我們的Web App離線可用,就需要將所需資源緩存下來饺鹃。我們需要一個(gè)資源列表莫秆,當(dāng)Service Worker被激活時(shí),會(huì)將該列表內(nèi)的資源緩存進(jìn)cache悔详。

// sw.js
var cacheName = 'bs-0-2-0';
var cacheFiles = [
    '/',
    './index.html',
    './index.js',
    './style.css',
    './img/book.png',
    './img/loading.svg'
];

// 監(jiān)聽install事件馏锡,安裝完成后,進(jìn)行文件緩存
self.addEventListener('install', function (e) {
    console.log('Service Worker 狀態(tài): install');
    var cacheOpenPromise = caches.open(cacheName).then(function (cache) {
        return cache.addAll(cacheFiles);
    });
    e.waitUntil(cacheOpenPromise);
});

可以看到伟端,首先在cacheFiles中我們列出了所有的靜態(tài)資源依賴杯道。注意其中的'/',由于根路徑也可以訪問我們的應(yīng)用责蝠,因此不要忘了將其也緩存下來党巾。當(dāng)Service Worker install時(shí),我們就會(huì)通過caches.open()cache.addAll()方法將資源緩存起來霜医。這里我們給緩存起了一個(gè)cacheName齿拂,這個(gè)值會(huì)成為這些緩存的key。

上面這段代碼中肴敛,caches是一個(gè)全局變量署海,通過它我們可以操作Cache相關(guān)接口吗购。

Cache 接口提供緩存的 Request / Response 對(duì)象對(duì)的存儲(chǔ)機(jī)制。Cache 接口像 workers 一樣, 是暴露在 window 作用域下的砸狞。盡管它被定義在 service worker 的標(biāo)準(zhǔn)中, 但是它不必一定要配合 service worker 使用捻勉。——MDN

3.4 使用緩存的靜態(tài)資源

到目前為止刀森,我們僅僅是注冊(cè)了一個(gè)Service Worker踱启,并在其install時(shí)緩存了一些靜態(tài)資源。然而研底,如果這時(shí)運(yùn)行這個(gè)demo你會(huì)發(fā)現(xiàn)——“圖書搜索”這個(gè)Web App依然無法離線使用埠偿。

為什么呢?因?yàn)槲覀儍H僅緩存了這些資源榜晦,然而瀏覽器并不知道需要如何使用它們冠蒋;換言之,瀏覽器仍然會(huì)通過向服務(wù)器發(fā)送請(qǐng)求來等待并使用這些資源乾胶。那怎么辦抖剿?

聰明的你應(yīng)該想起來了,我們?cè)谖恼虑鞍氩糠纸榻BService Worker時(shí)提到了“客戶端代理”——用Service Worker來幫我們決定如何使用緩存胚吁。

下圖是一個(gè)簡(jiǎn)單的策略:

有cache時(shí)的靜態(tài)資源請(qǐng)求流程
無cache時(shí)的靜態(tài)資源請(qǐng)求流程
  1. 瀏覽器發(fā)起請(qǐng)求牙躺,請(qǐng)求各類靜態(tài)資源(html/js/css/img)愁憔;
  2. Service Worker攔截瀏覽器請(qǐng)求腕扶,并查詢當(dāng)前cache;
  3. 若存在cache則直接返回吨掌,結(jié)束半抱;
  4. 若不存在cache,則通過fetch方法向服務(wù)端發(fā)起請(qǐng)求膜宋,并返回請(qǐng)求結(jié)果給瀏覽器
// sw.js
self.addEventListener('fetch', function (e) {
    // 如果有cache則直接返回窿侈,否則通過fetch請(qǐng)求
    e.respondWith(
        caches.match(e.request).then(function (cache) {
            return cache || fetch(e.request);
        }).catch(function (err) {
            console.log(err);
            return fetch(e.request);
        })
    );
});

fetch事件會(huì)監(jiān)聽所有瀏覽器的請(qǐng)求。e.respondWith()方法接受Promise作為參數(shù)秋茫,通過它讓Service Worker向?yàn)g覽器返回?cái)?shù)據(jù)史简。caches.match(e.request)則可以查看當(dāng)前的請(qǐng)求是否有一份本地緩存:如果有緩存,則直接向?yàn)g覽器返回cache肛著;否則Service Worker會(huì)向后端服務(wù)發(fā)起一個(gè)fetch(e.request)的請(qǐng)求圆兵,并將請(qǐng)求結(jié)果返回給瀏覽器。

到目前為止枢贿,運(yùn)行我們的demo:當(dāng)?shù)谝宦?lián)網(wǎng)打開“圖書搜索”Web App后殉农,所依賴的靜態(tài)資源就會(huì)被緩存在本地;以后再訪問時(shí)局荚,就會(huì)使用這些緩存而不發(fā)起網(wǎng)絡(luò)請(qǐng)求超凳。因此愈污,即使在無網(wǎng)情況下,我們似乎依舊能“訪問”該應(yīng)用轮傍。

3.5. 更新靜態(tài)緩存資源

然而暂雹,如果你細(xì)心的話,會(huì)發(fā)現(xiàn)一個(gè)小問題:當(dāng)我們將資源緩存后金麸,除非注銷(unregister)sw.js擎析、手動(dòng)清除緩存,否則新的靜態(tài)資源將無法緩存挥下。

解決這個(gè)問題的一個(gè)簡(jiǎn)單方法就是修改cacheName揍魂。由于瀏覽器判斷sw.js是否更新是通過字節(jié)方式,因此修改cacheName會(huì)重新觸發(fā)install并緩存資源棚瘟。此外现斋,在activate事件中,我們需要檢查cacheName是否變化偎蘸,如果變化則表示有了新的緩存資源庄蹋,原有緩存需要?jiǎng)h除。

// sw.js
// 監(jiān)聽activate事件迷雪,激活后通過cache的key來判斷是否更新cache中的靜態(tài)資源
self.addEventListener('activate', function (e) {
    console.log('Service Worker 狀態(tài): activate');
    var cachePromise = caches.keys().then(function (keys) {
        return Promise.all(keys.map(function (key) {
            if (key !== cacheName) {
                return caches.delete(key);
            }
        }));
    })
    e.waitUntil(cachePromise);
    return self.clients.claim();
});

3.6. 緩存API數(shù)據(jù)的“離線搜索”

到這里限书,我們的應(yīng)用基本已經(jīng)完成了離線訪問的改造。但是章咧,如果你注意到文章開頭的圖片就會(huì)發(fā)現(xiàn)倦西,離線時(shí)我們不僅可以訪問,還可以使用搜索功能赁严。

離線/無網(wǎng)環(huán)境下普通Web App(左)與PWA(右)的差異

這是怎么回事呢扰柠?其實(shí)這背后的秘密就在于,這個(gè)Web App也會(huì)把XHR請(qǐng)求的數(shù)據(jù)緩存一份疼约。而再次請(qǐng)求時(shí)卤档,我們會(huì)優(yōu)先使用本地緩存(如果有緩存的話);然后向服務(wù)端請(qǐng)求數(shù)據(jù)程剥,服務(wù)端返回?cái)?shù)據(jù)后劝枣,基于該數(shù)據(jù)替換展示。大致過程如下:

圖書查詢接口的緩存與使用策略

首先我們改造一下前一節(jié)的代碼在sw.js的fetch事件里進(jìn)行API數(shù)據(jù)的緩存

// sw.js
var apiCacheName = 'api-0-1-1';
self.addEventListener('fetch', function (e) {
    // 需要緩存的xhr請(qǐng)求
    var cacheRequestUrls = [
        '/book?'
    ];
    console.log('現(xiàn)在正在請(qǐng)求:' + e.request.url);

    // 判斷當(dāng)前請(qǐng)求是否需要緩存
    var needCache = cacheRequestUrls.some(function (url) {
        return e.request.url.indexOf(url) > -1;
    });

    /**** 這里是對(duì)XHR數(shù)據(jù)緩存的相關(guān)操作 ****/
    if (needCache) {
        // 需要緩存
        // 使用fetch請(qǐng)求數(shù)據(jù)织鲸,并將請(qǐng)求結(jié)果clone一份緩存到cache
        // 此部分緩存后在browser中使用全局變量caches獲取
        caches.open(apiCacheName).then(function (cache) {
            return fetch(e.request).then(function (response) {
                cache.put(e.request.url, response.clone());
                return response;
            });
        });
    }
    /* ******************************* */

    else {
        // 非api請(qǐng)求舔腾,直接查詢cache
        // 如果有cache則直接返回,否則通過fetch請(qǐng)求
        e.respondWith(
            caches.match(e.request).then(function (cache) {
                return cache || fetch(e.request);
            }).catch(function (err) {
                console.log(err);
                return fetch(e.request);
            })
        );
    }
});

這里昙沦,我們也為API緩存的數(shù)據(jù)創(chuàng)建一個(gè)專門的緩存位置琢唾,key值為變量apiCacheName。在fetch事件中盾饮,我們首先通過對(duì)比當(dāng)前請(qǐng)求與cacheRequestUrls來判斷是否是需要緩存的XHR請(qǐng)求數(shù)據(jù)采桃,如果是的話懒熙,就會(huì)使用fetch方法向后端發(fā)起請(qǐng)求。

fetch.then中我們以請(qǐng)求的URL為key普办,向cache中更新了一份當(dāng)前請(qǐng)求所返回?cái)?shù)據(jù)的緩存:cache.put(e.request.url, response.clone())工扎。這里使用.clone()方法拷貝一份響應(yīng)數(shù)據(jù),這樣我們就可以對(duì)響應(yīng)緩存進(jìn)行各類操作而不用擔(dān)心原響應(yīng)信息被修改了衔蹲。

3.7. 應(yīng)用離線XHR數(shù)據(jù)肢娘,完成“離線搜索”,提升響應(yīng)速度

如果你跟著做到了這一步舆驶,那么恭喜你橱健,距離我們酷酷的離線應(yīng)用還差最后一步了!

目前為止沙廉,我們對(duì)Service Worker(sw.js)的改造已經(jīng)完畢了拘荡。最后只剩下如何在XHR請(qǐng)求時(shí)有策略的使用緩存了,這一部分的改造全部集中于index.js撬陵,也就是我們的前端腳本珊皿。

還是回到上一節(jié)的這張圖:

圖書查詢接口的緩存與使用策略

和普通情況不同,這里我們的前端瀏覽器會(huì)首先去嘗試獲取緩存數(shù)據(jù)并使用其來渲染界面巨税;同時(shí)蟋定,瀏覽器也會(huì)發(fā)起一個(gè)XHR請(qǐng)求,Service Worker通過將請(qǐng)求返回的數(shù)據(jù)更新到存儲(chǔ)中的同時(shí)向前端Web應(yīng)用返回?cái)?shù)據(jù)(這一步分就是上一節(jié)提到的緩存策略)草添;最終驶兜,如果判斷返回的數(shù)據(jù)與最開始取到的cache不一致,則重新渲染界面果元,否則忽略促王。

為了是代碼更清晰犀盟,我們將原本的XHR請(qǐng)求部分單獨(dú)剝離出來而晒,作為一個(gè)方法getApiDataRemote()以供調(diào)用,同時(shí)將其改造為了Promise阅畴。為了節(jié)省篇幅倡怎,我部分的代碼比較簡(jiǎn)單,就不單獨(dú)貼出了贱枣。

這一節(jié)最重要的部分其實(shí)是讀取緩存监署。我們知道,在Service Worker中是可以通過caches變量來訪問到緩存對(duì)象的纽哥。令人高興的是钠乏,在我們的前端應(yīng)用中,也仍然可以通過caches來訪問緩存春塌。當(dāng)然晓避,為了保證漸進(jìn)可用簇捍,我們需要先進(jìn)行判斷'caches' in window。為了代碼的統(tǒng)一俏拱,我將獲取該請(qǐng)求的緩存數(shù)據(jù)也封裝成了一個(gè)Promise方法:

function getApiDataFromCache(url) {
    if ('caches' in window) {
        return caches.match(url).then(function (cache) {
            if (!cache) {
                return;
            }
            return cache.json();
        });
    }
    else {
        return Promise.resolve();
    }
}

而原本我們?cè)?code>queryBook()方法中暑塑,我們會(huì)請(qǐng)求后端數(shù)據(jù),然后渲染頁面锅必;而現(xiàn)在事格,我們加上基于緩存的渲染:

function queryBook() {
    // ……
    // 遠(yuǎn)程請(qǐng)求
    var remotePromise = getApiDataRemote(url);
    var cacheData;
    // 首先使用緩存數(shù)據(jù)渲染
    getApiDataFromCache(url).then(function (data) {
        if (data) {
            loading(false);
            input.blur();            
            fillList(data.books);
            document.querySelector('#js-thanks').style = 'display: block';
        }
        cacheData = data || {};
        return remotePromise;
    }).then(function (data) {
        if (JSON.stringify(data) !== JSON.stringify(cacheData)) {
            loading(false);                
            input.blur();
            fillList(data.books);
            document.querySelector('#js-thanks').style = 'display: block';
        }
    });
    // ……
}

如果getApiDataFromCache(url).then返回緩存數(shù)據(jù),則使用它先進(jìn)行渲染搞隐。而當(dāng)remotePromise的數(shù)據(jù)返回時(shí)驹愚,與cacheData進(jìn)行比對(duì),只有在數(shù)據(jù)不一致時(shí)需要重新渲染頁面(注意這里為了簡(jiǎn)便劣纲,粗略地使用了JSON.stringify()方法進(jìn)行對(duì)象間的比較)么鹤。這么做有兩個(gè)優(yōu)勢(shì):

  1. 離線可用。如果我們之前訪問過某些URL味廊,那么即使在離線的情況下蒸甜,重復(fù)相應(yīng)的操作依然可以正常展示頁面;
  2. 優(yōu)化體驗(yàn)余佛,提高訪問速度柠新。讀取本地cache耗時(shí)相比于網(wǎng)絡(luò)請(qǐng)求是非常低的,因此就會(huì)給我們的用戶一種“秒開”辉巡、“秒響應(yīng)”的感覺恨憎。

4. 使用Lighthouse測(cè)試我們的應(yīng)用

至此,我們完成了PWA的兩大基本功能:Web App Manifest和Service Worker的離線緩存郊楣。這兩大功能可以很好地提升用戶體驗(yàn)與應(yīng)用性能憔恳。我們用Chrome中的Lighthouse來檢測(cè)一下目前的應(yīng)用:

Lighthouse檢測(cè)結(jié)果
Lighthouse檢測(cè)結(jié)果 - PWA

可以看到,在PWA評(píng)分上净蚤,我們的這個(gè)Web App已經(jīng)非常不錯(cuò)了钥组。其中唯一個(gè)扣分項(xiàng)是在HTTPS協(xié)議上:由于是本地調(diào)試,所以使用了http://127.0.0.1:8085今瀑,在生產(chǎn)肯定會(huì)替換為HTTPS程梦。

5. 這太酷了,但是兼容性呢橘荠?

隨著今年(2018年)年初屿附,Apple在iOS 11.3中開始支持Service Worker,加上Apple一直以來較為良好的系統(tǒng)升級(jí)率哥童,整個(gè)PWA在兼容性問題上有了重大的突破挺份。

雖然Service Worker中的一些其他功能(例如推送、后臺(tái)同步)Apple并未表態(tài)贮懈,但是Web App Manifest和Service Worker的離線緩存是iOS 11.3所支持的匀泊。這兩大核心功能不僅效果拔群影暴,而且目前看來具有還不錯(cuò)的兼容性,非常適合投入生產(chǎn)探赫。

更何況型宙,作為漸進(jìn)式網(wǎng)頁應(yīng)用,其最重要的一個(gè)特點(diǎn)就是在兼容性支持時(shí)自動(dòng)升級(jí)功能與體驗(yàn)伦吠;而在不支持時(shí)妆兑,會(huì)靜默回退部分新功能。在保證我們的正常服務(wù)情況下毛仪,盡可能利用瀏覽器特性搁嗓,提供更優(yōu)質(zhì)的服務(wù)。

Service Worker兼容性

6. 寫在最后

本文中所有的代碼示例均可以在learn-pwa/sw-cache上找到箱靴。注意在git clone之后腺逛,切換到sw-cache分支,本文所有的代碼均存在于該分支上衡怀。切換其他分值可以看到不同的版本:

  • basic分支:基礎(chǔ)項(xiàng)目demo棍矛,一個(gè)普通的圖書搜索應(yīng)用(網(wǎng)站);
  • manifest分支:基于basic分支抛杨,添加manifest等功能够委,具體可以看上一篇文章了解;
  • sw-cache分支:基于manifest分支怖现,添加緩存與離線功能茁帽;
  • master分支:應(yīng)用的最新代碼。

如果你喜歡或想要了解更多的PWA相關(guān)知識(shí)屈嗤,歡迎關(guān)注我潘拨,關(guān)注《PWA學(xué)習(xí)與實(shí)踐》系列文章。我會(huì)總結(jié)整理自己學(xué)習(xí)PWA過程的遇到的疑問與技術(shù)點(diǎn)饶号,并通過實(shí)際代碼和大家一起實(shí)踐铁追。

最后聲明一下,文中的代碼作為demo讨韭,主要是用于了解與學(xué)習(xí)PWA技術(shù)原理脂信,可能會(huì)存在一些不完善的地方癣蟋,因此透硝,不建議直接使用到生產(chǎn)環(huán)境。

《PWA學(xué)習(xí)與實(shí)踐》系列

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市幔欧,隨后出現(xiàn)的幾起案子罪治,更是在濱河造成了極大的恐慌丽声,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件觉义,死亡現(xiàn)場(chǎng)離奇詭異雁社,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)晒骇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門霉撵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洪囤,你說我怎么就攤上這事徒坡。” “怎么了瘤缩?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵喇完,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我剥啤,道長(zhǎng)锦溪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任府怯,我火速辦了婚禮海洼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘富腊。我一直安慰自己坏逢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布赘被。 她就那樣靜靜地躺著是整,像睡著了一般。 火紅的嫁衣襯著肌膚如雪民假。 梳的紋絲不亂的頭發(fā)上浮入,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天憨攒,我揣著相機(jī)與錄音怎囚,去河邊找鬼。 笑死怕品,一個(gè)胖子當(dāng)著我的面吹牛野舶,可吹牛的內(nèi)容都是我干的易迹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼平道,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼睹欲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤窘疮,失蹤者是張志新(化名)和其女友劉穎袋哼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闸衫,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涛贯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔚出。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疫蔓。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖身冬,靈堂內(nèi)的尸體忽然破棺而出衅胀,到底是詐尸還是另有隱情,我是刑警寧澤酥筝,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布滚躯,位于F島的核電站,受9級(jí)特大地震影響嘿歌,放射性物質(zhì)發(fā)生泄漏掸掏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一宙帝、第九天 我趴在偏房一處隱蔽的房頂上張望丧凤。 院中可真熱鬧,春花似錦步脓、人聲如沸愿待。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仍侥。三九已至,卻和暖如春鸳君,著一層夾襖步出監(jiān)牢的瞬間农渊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工或颊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留砸紊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓囱挑,卻偏偏與公主長(zhǎng)得像醉顽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子看铆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容