原文轉載自「劉悦的技術博客」https://v3u.cn/a_id_216
PWA(Progressive web apps,漸進式 Web 應用)使用現代的 Web API 以及傳統的漸進式增強策略來創建跨平台 Web 應用程序,説白了,PWA可以讓我們的站點以原生APP的形式運行,但相比於安裝原生APP應用,訪問PWA顯然更加容易和迅速,還可以通過鏈接來分享PWA應用。
有許多知名的網絡平台已經將 PWA 方案落地,比如Twitter。選擇增強的網站體驗而不是原生應用。事實上使用PWA也確實從中獲得了顯而易見的益處。https://www.pwastats.com 這個網站上分享了許多案例研究,PWA相比於傳統應用有以下好處:
1、減少應用安裝後的加載時間,通過 Service Workers 來進行緩存,以此來節省帶寬和時間。
2、當應用有可用的更新時,可以只更新發生改變的那部分內容。相比之下,對於一個原生應用而言,即便是最微小的改動也需要強制用户去進行熱更新或者再次下載整個應用。
3、外觀和使用感受與原生平台更加融為一體——應用圖標被放置在主屏幕上,應用可以全屏運行等。
憑藉系統通知和推送消息與用户保持連接,對用户產生更多的吸引力,並且提高轉換效率。
誠然,從零開始研發PWA應用會有一定的成本,但如果我們本身就擁有基於Web的站點,那麼就可以通過增加對應的配置文件和服務進行升級操作,直接擁有PWA應用。
HTTPS服務
首先PWA要求站點的請求方式為HTTPS,如果是生產環境,可以通過為Nginx服務器配置SSL的方式進行適配,但是線下環境測試PWA時就有點費勁了,所以通過openssl工具為本地域名localhost做自簽證書:
openssl req -x509 -out localhost.crt -keyout localhost.key \
-newkey rsa:2048 -nodes -sha256 \
-days 3650 \
-subj '/CN=localhost' -extensions EXT -config <( \
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
產出:localhost.crt和localhost.key文件,key是私用密鑰openssl格式,通常是rsa算法。csr是證書請求文件,用於申請證書,在製作csr文件的時,必須使用自己的私鑰來簽署申,還可以設定一個密鑰。
將文件放到項目的根目錄下,隨後在構建項目服務的時候配置即可,以Tornado為例:
server = httpserver.HTTPServer(app,xheaders=True,ssl_options={
"certfile": "./localhost.crt",
"keyfile": "./localhost.key",
})
# 指定端口
server.listen(443)
這裏通過設置ssl\_options參數來導入私鑰和證書,同時將端口改為HTTPS默認端口號443。如此,在本地也可以對PWA進行測試了,當然了,如果不需要本地操作,也可以跳過這步。
manifest.json配置文件
為了實現 PWA 應用添加至桌面的功能,除了要求站點支持 HTTPS 之外,還需要準備 manifest.json 文件去配置應用的圖標、名稱等信息。
以本站為例,在站點根目錄創建manifest.json文件:
{
"name": "劉悦的技術博客",
"short_name": "劉悦的技術博客",
"description": "劉悦的技術博客",
"icons": [
{
"src": "https://v3u.cn/v3u/Public/images/pwa192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "https://v3u.cn/v3u/Public/images/pwa512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"background_color": "#FFF",
"theme_color": "#FFF",
"display": "standalone",
"orientation": "portrait",
"start_url": "/",
"scope": "/"
}
由上至下,依次是 PWA 應用的名稱、描述、圖標文件、banner顏色、顯示方式、開始頁面的鏈接和 PWA 的作用域。為此我們需要提供兩張不同分辨率的站點圖標文件:
ServiceWorker服務
Service Worker是一個註冊在指定源和路徑下的事件驅動型Web Worker。它充當了Web應用程序與瀏覽器之間的代理服務器,進行資源在文件級別下的緩存與操控,攔截頁面請求,實現在不同的情況下對不同請求的響應策略。
Service Worker本質上就是一個Web Worker,因此它具有Web Worker的特點:無法操作DOM、脱離主線程、獨立上下文。
Service Worker還具有這些特點:只能在Https下使用、運行在瀏覽器後台,不受頁面刷新影響、更強大的離線緩存能力(使用Cache API)、請求攔截能力、完全異步,不能使用同步API、持續運行,第一次訪問頁面後,Service Worker就會安裝激活並持續運行,直到手動銷燬。
以本站為例,在站點根目錄創建sw.js文件,注意Service Worker文件位置一定得在根目錄,如果不在根目錄也要通過重寫或者url映射讓其可以通過根目錄路徑進行訪問,如:https://v3u.cn/sw.js,否則瀏覽器會檢測不到Service Worker服務:
var CACHE_NAME = 'v3u-cache-v1';
var urlsToCache = [
'/',
'/v3u/Public/css/tidy_min.css'
];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('Open cache');
return cache.addAll(urlsToCache);
}).then(function () {
self.skipWaiting();
})
);
});
當我們為頁面註冊Service Worker後,Service Worker開始進行安裝,安裝成功之後,會在worker中觸發install事件;如果安裝失敗,則進入廢棄狀態。
如果Service Worker邏輯文件更新(相關資源文件變動或者內部邏輯更新等),Service Worker會重新安裝,如果這個時候,頁面依然存在激活狀態下的worker(舊的Service Worker),那麼新的worker會進入waiting狀態進行等待,直到我們主動去操作worker強制其更新,或者等待用户關閉所有頁面,這個時候新的worker才會進入到激活狀態。
在install事件中,我們使用caches.open方法打開cache對象,並通過cache.addAll緩存所有我們列出的文件。如果Service Worker存在更新,我們使用skipWaiting跳過等待,直接強制新的worker進入激活狀態。
隨後,添加fetch事件:
self.addEventListener('fetch', function(event){
if(event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request).then(function(response){
if(response){
console.log('return caches');
return response;
}else{
return fetch(event.request).catch(function(){
if(/\.html$/.test(event.request.url))
return caches.match('/html/neterror.html');
});
}
})
)
});
這裏只監聽了全站的GET請求方式,即我們只希望控制資源請求。通過caches.match檢查請求是否命中了緩存,如果命中,則直接返回緩存給用户,防止重複請求,節約資源。如果沒有命中,則將使用fetch方法請求網絡資源並返回給用户。當網絡狀態異常時(fetch().catch()),返回404頁面的緩存給用户,告知用户當前處於無網絡狀態,不能訪問相關頁面。指定了一些頁面和文件進行緩存,我們希望用户在無網絡的情況下只能訪問到我們指定緩存的頁面。
當然,還有另外一種情況,我們指定了一些頁面進行緩存(常用頁面),當用户訪問到一些不常用頁面時,再對其進行緩存。這樣,我們可以對資源配置進行優化,不過多的佔用用户本地資源去緩存所有頁面,因為PWA的緩衝本身是存儲到客户端的,對於非所有用户的常用頁面,按需緩存:
self.addEventListener('fetch', function(event){
if(event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request).then(function(response){
if(response){
console.log('return caches');
return response;
}else{
return fetch(event.request).then(function(res){
var responseToCache = res.clone();
caches.open(CACHE_NAME).then(function(cache){
catch.put(event.request, responseToCache);
})
return res;
});
}
})
)
});
至此,ServiceWorker服務文件就撰寫完成了。
生產環境上線配置:
分別將manifest.json和sw.js文件分別上傳到生產環境之後,在頁面的head標籤中進行聲明:
<link rel="manifest" href="manifest.json">
聲明後,注意訪問一下是否正確返回:https://v3u.cn/manifest.json
隨後在頁面中註冊Service Worker服務:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () =>
navigator.serviceWorker.register("/sw.js?v0")
.catch(() => {}) // ignore
);
}
</script>
這裏首先判斷當前瀏覽器的navigator是否支持serviceWorker,隨後使用navigator.serviceWorker.register函數來註冊Service Worker。其中,參數為要執行的worker邏輯文件路徑,注意這個路徑是基於origin的,而非當前文件。
接着鍵入組合鍵,打開chrome瀏覽器的開發者工具:
Mac系統上的“⌥+⌘+I”
Win系統上的“F12+Ctrl+Shift+I”
在Chrome 的應用標籤下進行檢查,看應用清單有沒有讀出你的 PWA 應用信息配置文件:
隨後在serviceWorker標籤下檢查serviceWorker是否正確運行:
接着訪問站點,在地址欄即可添加PWA應用:
訪問效果:
結語
漸進式增強和響應式設計已經可以讓我們構建對移動端非常友好的站點,而PWA則又在我們的身後輕輕地推了一把,黃河之水源可濫觴,星星之火正在燎原,一年以內,我們都將感到PWA的灼人温度。
原文轉載自「劉悦的技術博客」 https://v3u.cn/a_id_216