PWA詳解

PWA 漸進式的web應用

介紹

我將帶你快速熟悉一個pwa應用的基本使用css

本文中涉及到的內容:html

  • fetch
  • CacheStorage
  • service worker
  • SyncMannager
  • postManager 與 messageChannal
  • manifest.json
  • 消息推送

Fetch

Fetch 提供了許多與XMLHttpRequest相同的功能,爲何要在這裏說起這個,由於在咱們在service worker環境中是不能去使用XMLHttpRequest對象的,故而這是一個很是重要的api。web

Fetch 的核心在於對 HTTP 接口的抽象,包括 Request,Response,Headers,Body這些接口面試

這裏只是簡單介紹shell

基本用法

Promise<Response> fetch(input[, init]);
複製代碼

參數介紹:json

  • input定義要獲取的資源。可能值:
  1. string:資源的 URL。一些瀏覽器會接受 blob: 和 data: 做爲 schemes.
  2. Request 對象。
  • init 可選
  1. method: 請求方法,如 GET、POST。
  2. headers: 請求的頭,能夠爲 Headers 的對象,或者形如{'Content-Type': 'image/jpeg'}。
  3. body: 請求體。可能爲 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 對象。GET 或 HEAD 無請求體。
  4. mode: 請求的模式,如 cors、 no-cors 或者 same-origin。
  5. credentials: 請求的 credentials,如 omit、same-origin 或者 include。爲了在當前域名內自動發送 cookie , 必須提供這個選項, 從 Chrome 50 開始, 這個屬性也能夠接受 FederatedCredential 實例或是一個 PasswordCredential 實例。
  6. ...

CacheStorage

這是一個能夠緩存瀏覽器緩存的接口,離線應用的核心就靠他了api

CacheStorage經常使用方法介紹

name 描述
Cache.match(request, options) 該方法會檢查是否存在該request的緩存,返回 Promise對象,resolve的結果是跟 Cache 對象匹配的第一個已經緩存的請求。
Cache.matchAll(request, options) 同上,不一樣的是resolve的結果是跟Cache對象匹配的全部請求組成的數組。
Cache.add(request) 抓取這個URL, 檢索並把返回的response對象添加到給定的Cache對象.這在功能上等同於調用 fetch(), 而後使用 Cache.put() 將response添加到cache中.
Cache.addAll(requests) 抓取一個URL數組,檢索並把返回的response對象添加到給定的Cache對象。
Cache.put(request, response) 同時抓取一個請求及其響應,並將其添加到給定的cache。
Cache.delete(request, options) 搜索key值爲request的Cache 條目。若是找到,則刪除該Cache 條目,而且返回一個resolve爲true的Promise對象;若是未找到,則返回一個resolve爲false的Promise對象。
Cache.keys(request, options) 返回一個Promise對象,resolve的結果是Cache對象key值組成的數組。

options參數的解釋:數組

  • ignoreSearch(Boolean): 忽略url中的query部分(?後面的參數)默認值:false
  • ignoreMethod(Boolean): 若是設置爲 true在匹配時就不會驗證 Request 對象的http 方法 (一般只容許是 GET 或 HEAD 。) 默認值:false
  • ignoreVary(Boolean): 該值若是爲 true 則匹配時不進行 VARY 部分的匹配。默認值:false
  • cacheName(DOMString): 表明一個具體的要被搜索的緩存。注意該選項被 Cache.match()方法忽略。

service worker

這是咱們要花時間最多的地方!promise

生命週期

  1. installing(正在安裝)

當你調用 navigator.serviceWorker.register 註冊一個新的 service worker ,service worker 代碼就會被下載、解析、進入installing階段。若安裝成功則進入installed,失敗則進入redundant瀏覽器

  1. installed/waiting(已安裝/等待中)

installed 狀態下的service worker會判斷本身是否已經被註冊過,若是是第一次註冊將進入activating狀態,若是發現本身被註冊且處於activated狀態,那麼將進入waiting狀態

  1. activating(激活中)

此狀態下的sw將進入activated(必將進入activated狀態)

  1. activated(已激活)

一旦 service worker 激活,它就準備好接管頁面並監聽功能性事件了(例如 fetch 事件)

  1. redundant(廢棄)

當sw註冊失敗或者被新的sw替換將進入此狀態(只有這兩種狀況會進入redundant)

結合代碼理解service worker生命週期

頁面中的代碼:

if("serviceWorker" in navigator){
    window.onload = function(){
        navigator.serviceWorker.register("./sw.js").then((registration)=>{
            var sw = null, state;
            if(registration.installing) {
                sw = registration.installing;
                state = 'installing';
            } else if(registration.waiting) {
                sw = registration.waiting;
                state = 'installed'
            } else if(registration.active) {
                sw = registration.active;
                state = 'activated'
            }
            state && console.log(`sw state is ${state}`);
            if(sw) {
                sw.onstatechange = function() {
                    console.log(`sw state is ${sw.state}`);
                }
            }
        }).catch(()=>{
            console.log('sw fail');
        })
    }

}
複製代碼

sw.js:

self.addEventListener('install',function () {
    console.log('install callback');
})

self.addEventListener('activate',function () {
    console.log('activate callback');
})

self.addEventListener('fetch',function () {
    console.log('fetch callback');
})
複製代碼

首次刷新頁面控制檯輸出:

install callback
sw state is installing
sw state is installed
activate callback
sw state is activating
sw state is activated
複製代碼

再次刷新頁面控制檯輸出:

sw state is activated
fetch callback
複製代碼

install,與active事件僅僅執行一次,fetch在首次刷新也面試時是不請求的

更新service worker

當更新sw時,刷新頁面,此時:

  1. 新的sw的install回調觸發,進入installing
  2. 新的sw發現上一個sw還處於actived狀態則進入waiting狀態
  3. 此時調用slkipwating方法或者關閉瀏覽器再次打開瀏覽器,會使舊的sw進入redundant,新的sw進入actived

代碼此處能夠本身嘗試

waitUntil延長生命週期

waitUntil函數傳入promise做爲參數,當promise執行完成纔會接着往下走。

在install事件中調用該方法

1.加入成功的回調:

self.addEventListener('install',function (event) {
    event.waitUntil(new Promise(function(resolve) {
        setTimeout(() => {
            console.log('install 2s')
            resolve()
        }, 2000)
    }))
})
複製代碼

控制檯輸出:

sw state is installing
install 2s
sw state is installed
activate callback
sw state is activating
sw state is activated
複製代碼
  1. 加入失敗的回調:
self.addEventListener('install',function (event) {
    event.waitUntil(new Promise(function(resolve,reject) {
        setTimeout(() => {
            console.log('install 2s')
            reject()
        }, 2000)
    }))
})
複製代碼

控制檯輸出:

sw state is installing
install 2s
sw state is redundant
Uncaught (in promise) undefined
複製代碼

sw直接進入redundant階段

在activate事件中調用該方法

與install大體都同樣,不一樣的是當你調用reject時,sw不會進入redundant階段,而是最終仍是進入actived階段

生命週期常見應用

  1. 通常咱們會在install中緩存請求,這是爲了可以在下一次請求中使用到這些緩存。
  2. 在active中咱們應該清除舊的緩存。
  3. 在fetch中咱們即可以使用這些緩存,而且更新他們

service worker的做用域範圍?

service worker只能捕獲當前目錄及其子目錄下的請求!

好比:
當service worker在/pwa/sw.js下時那麼只能捕獲/pwa/*的請求,因此通常咱們都應該將sw.js放置於/根目錄下。

代碼示例

若是你複製如下代碼將在頁面顯示css代碼

var CACHE_NAME = "gih-cache";
var CACHED_URLS = [
    "/pwa/test.css"
];

self.addEventListener('install',function (event) {
    event.waitUntil(
        caches.open(CACHE_NAME).then(function(cache) {
            return cache.addAll(CACHED_URLS);
        })
    );
})

self.addEventListener('activate',function (event) {
    // event.waitUntil(
    // caches.keys().then(function(cacheNames) {
    // return Promise.all(
    // cacheNames.map(function(cacheName) {
    // if (CACHE_NAME !== cacheName && cacheName.startsWith("gih-cache")) {
    // return caches.delete(cacheName);
    // }
    // })
    // );
    // })
    // );
})

self.addEventListener('fetch',function (event) {
    event.respondWith(
        caches.open(CACHE_NAME).then(function(cache) {
            return cache.match(event.request).then(function(response) {
                let fetchPromise = fetch(event.request).then(function(networkResponse) {
                    cache.put(event.request, networkResponse.clone());
                    return networkResponse;
                })
                return response || fetchPromise;
            })
        })
    );
})

複製代碼

離線開發的策略

  1. 僅網絡
  2. 先網絡後緩存:當咱們但願用戶看見內容一直是最新的,那麼可使用這種模式,在fetch中更新緩存數據
  3. 緩存後網絡:當不須要用戶看見最新的內容,咱們能夠先將緩存呈現給用戶,在fetch中更新緩存,下一次用戶刷新能夠看見最新的緩存。(以上代碼採起的就是這種)

syncManager 後臺同步

在用戶使用web app時,網頁可能會被關閉,用戶鏈接可能會斷開,甚至服務器有時候也會故障。可是,只要用戶設備上安裝了瀏覽器,後臺同步中的操做就不會消失,直到它成功完成爲止。

註冊後臺同步事件

在頁面中:

navigator.serviceWorker.ready.then(function(registration) {     
    registration.sync.register('send-messages');
});
複製代碼

監聽sync事件

在sw.js中

self.addEventListener("sync", function(event) { 
    if (event.tag === "send-messages") {
        event.waitUntil(function() { 
            var sent = sendMessages(); 
            if (sent) {
                return Promise.resolve(); 
            }else{
                return Promise.reject(); 
                
            }
        }); 
    }
});
複製代碼

sync事件什麼時候結束?

當後臺屢次嘗試不成功時,那麼sync事件也會提供結束時的標識符event.lastChance

self.addEventListener("sync", event => { 
    if (event.tag == "add-reservation") {
        event.waitUntil( 
            addReservation()
            .then(function() {
                return Promise.resolve();
            }).catch(function(error) {
                if (event.lastChance) { 
                    return removeReservation();
                } else {
                    return Promise.reject();
                }
            })
        ); 
    }
});
複製代碼

postMessage

當咱們將邏輯代碼放入service worker中時,咱們就必定會有頁面與service worker通訊的需求,此時postMessage即是這麼一個擔任通訊的角色。

1. 窗口向service worker通訊

頁面代碼:

navigator.serviceWorker.controller.postMessage( {
    arrival: "05/11/2022", 
    nights: 3, 
    guests: 2
})
複製代碼

service worker代碼:

self.addEventListener("message", function (event) { 
    console.log(event.data);
});
複製代碼

2. service worker向全部打開的窗口通訊

頁面代碼:

navigator.serviceWorker.addEventListener("message", function (event) {
    console.log(event.data);
});

複製代碼

service worker代碼:

self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
        if (client.url.includes("/my-account")) { 
            client.postMessage("Hi client: "+client.id);
        } 
    });
});
複製代碼

3. service worker向特定窗口通訊

service worker代碼:

//當你能夠得到某個客戶端id時即可以向特定的客戶端發送消息
self.clients.get("d2069ced-8f96-4d28").then(function(client) {
    client.postMessage("Hi window, you are currently " +
                        client.visibilityState);
});
複製代碼

得到特定的客戶端id

//經過clients對象獲取客戶端id
self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
        self.clients.get(client.id).then(function(client) {  
            client.postMessage("Messaging using clients.matchAll()");
        });
    });
});

//經過event.sourse.id 得到·客戶端id
self.addEventListener("message", function(event) {
    self.clients.get(event.source.id).then(function(client) {
        client.postMessage("Messaging using clients.get(event.source.id)");
    });
});

//簡化寫法
self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
        client.postMessage("Messaging using clients.matchAll()"); 
    });
});

複製代碼

MessageChannel

在介紹第四種通訊方式(窗口間通訊)時,我想先插入介紹一下MessageChannel這個對象,他是實現咱們service worker與窗口間相互通訊的一種有效的技術手段。

演示代碼

// 窗口代碼
var msgChan = new MessageChannel(); 
msgChan.port1.onmessage = function(event) {
    console.log("Message received in page:", event.data); 
};
var msg = {action: "triple", value: 2}; 
//這裏能夠是postMessage的第二個參數
navigator.serviceWorker.controller.postMessage(msg, [msgChan.port2]);


// service worker代碼 
self.addEventListener("message", function (event) {
    var data = event.data;
    var openPort = event.ports[0]; 
    if (data.action === "triple") {
        openPort.postMessage(data.value*3); 
    }
});
複製代碼

4. 窗口間的通訊

窗口間通訊方式有多種,你如localStorage這些均可以實現,這裏仍是應該思考如何使用service worker來進行通訊,這裏先再也不贅述。

manifest.json

當咱們的web應用已經使用以上技術作到了一系列的離線優化後,咱們能夠考慮將咱們的應用安裝在本地。

頁面引入:

<link rel="manifest" href="/manifest.json">
複製代碼

manifest.json:

{
    "short_name": "Gotham Imperial",
    "name": "Gotham Imperial Hotel",
    "description": "Book your next stay, manage reservations, and explore Gotham", "start_url": "/my-account?utm_source=pwa",
    "scope": "/",
    "display": "fullscreen",
    "icons": [
        {
        "src": "/img/app-icon-192.png", "type": "image/png",
        "sizes": "192x192"
        }, {
        } 
    ],
    "theme_color": "#242424",
    "background_color": "#242424" 
}
複製代碼

屬性介紹

  • name與/或short_name:
    name 是應用的全名。當空間足夠長時,就會使用這個字段做爲顯示名稱,short_name 能夠做爲短 名的備選方案

  • start_url:
    當用戶點擊圖標時,打開的 URL。能夠是根域名,也能夠是內部頁面。

  • icon:
    包含了一個或多個對象的數組,對象屬性:src(圖標的絕對路徑或者相對路徑)、type(文件類型)和 sizes(圖片的像素尺寸)。要觸發Web應用安裝橫條,清單中至少要包含一個圖標, 尺寸至少是 144像素×144像素。 因爲每一個設備都會根據設備分辨率,從這個數組中選擇最佳的圖標尺寸,所以建議至少 包含 192×192 的圖標和 512×512 的圖標,以覆蓋大多數的設備和用途。

  • display:

  1. browser——在瀏覽器中打開應用。
  2. standalone——打開應用時不顯示瀏覽器欄(不顯示瀏覽器界面,例如地址欄)。
  3. fullscreen——打開應用時不顯示瀏覽器欄和設備欄(例如在安卓設備上,這意味着 同時隱藏瀏覽器界面和屏幕頂部的狀態欄)。
  • description:
    應用的描述。

  • orientation:
    容許你強制指定某個屏幕方向。

  • theme_color
    主題顏色可讓瀏覽器和設備調整 UI 以匹配你的網站(見圖 9-5)。這個顏色的選擇會 影響瀏覽器地址欄顏色、任務切換器中的應用顏色,甚至是設備狀態欄的顏色。主題顏色也能夠經過頁面的meta標籤進行設置(例如:)。若是頁面帶有 theme-color 的 meta 標籤,則該設置會覆蓋清單 中的 theme_color 設置。請注意,雖然 meta 標籤可讓你設置或者覆蓋單個頁面的主 題顏色,可是清單文件中的 theme_color 設置是會影響整個應用的。

  • background_color
    設置應用啓動畫面的顏色以及應用加載時的背景色。一旦加載後,頁面中定義的任何背 景色(經過樣式表或者內聯 HTML 標籤設置)都會覆蓋這一設置;可是,經過將其設 置爲與頁面背景色相同的顏色,就能夠實現從頁面啓動的瞬間到徹底渲染之間的平滑過 渡。若是不設置這一顏色,頁面就會從白色背景啓動,隨後被頁面的背景色替換。

更新中

相關文章
相關標籤/搜索