Progressive Web App, 簡稱 PWA,是提升Web App
的體驗的一種新方法,能給使用者原生應用的體驗,致力於用前沿的技術開發,讓網頁使用如同原生App般的體驗的一系列方案。
用來自Google Developers的解答Progressive Web Apps
:
- 漸進式 - 適用於所有現代瀏覽器,因為它是以漸進式增強作為宗旨開發的
- 離線使用 - 藉助
Service Worker
能夠在離線或者網路較差的情況下正常訪問 - 可安裝 - 使用者可以新增到桌面並生成快捷方式,一鍵訪問
- 類似應用 - 由於是在
App Shell
模型基礎上開發,因為應具有Native App
的互動和導航,給使用者Native App
的體驗 - 持續更新 - 始終是最新的,無版本和更新問題
- 安全 - 通過
HTTPS
協議提供服務,防止窺探和確保內容不被篡改 - 可索引 - 應用清單檔案和
Service Worker
可以讓搜尋引擎索引到,從而將其識別為『應用』 - 粘性 - 網頁已經關閉的情況下還可以通過推送後臺通知等,讓使用者迴流
我們也可以通過一個DEMO看看實際效果=>天氣 PWA
而其中,PWA方案的最主要核心功能都是依賴於Service Worker
這個API來實現的.
Service Worker是什麼呢?
W3C 組織早在 2014 年 5 月就提出過 Service Worker
這樣的一個 HTML5 API ,主要用來做持久的離線快取。
當然這個 API 不是憑空而來,至於其中的由來我們可以簡單的捋一捋:
瀏覽器中的javaScript
都是執行在一個單一主執行緒上的,在同一時間內只能做一件事情。隨著 Web 業務不斷複雜,我們逐漸在 js 中加了很多耗資源、耗時間的複雜運算過程,這些過程導致的效能問題在 WebApp 的複雜化過程中更加凸顯出來。
W3C 組織早早的洞察到了這些問題可能會造成的影響,這個時候有個叫Web Worker
的 API 被造出來了,這個 API 的唯一目的就是解放主執行緒,Web Worker
是脫離在主執行緒之外的,將一些複雜的耗時的活交給它幹,完成後通過 postMessage
方法告訴主執行緒,而主執行緒通過 onMessage
方法得到 Web Worker
的結果反饋。
一切問題好像是解決了,但 Web Worker 是臨時(即瀏覽器關閉後就關閉了)的,我們能不能有一個東東是一直持久存在的,並且隨時準備接受主執行緒的命令呢?基於這樣的需求推出了最初版本的 Service Worker
,Service Worker
在 Web Worker
的基礎上加上了持久離線快取能力.
Service Worker
有以下功能和特性:
- 一個獨立的 worker 執行緒,獨立於當前網頁程式。
- 一旦被 install,就永遠存在,除非被 uninstall
- 需要的時候可以直接喚醒,不需要的時候自動睡眠
- 可程式設計攔截代理請求和返回,快取檔案,快取的檔案可以被網頁程式取到(包括網路離線狀態)
- 離線內容開發者可控
- 能向客戶端推送訊息
- 不能直接操作 DOM
- 出於安全的考慮,必須在 HTTPS 環境下才能工作
所以我們基本上知道了 Service Worker
的偉大使命,就是讓快取做到優雅和極致,讓 Web App 相對於 Native App 的缺點更加弱化,也為開發者提供了對效能和體驗的無限遐想。
Service Worker工作原理
Service Worker
的技術核心是Service Worker
指令碼,它 是一種由Javascript
編寫的瀏覽器端代理指令碼。
前端頁面向核心發起註冊時會將指令碼地址通知核心,核心會啟動獨立進/執行緒載入Service Worker
指令碼並執行Service Worker
安裝及啟用動作。成功啟用後便進入空閒等待狀態,若當前的Service Worker
進/執行緒一直沒有管轄的頁面或者事件訊息時會自動終止(具體的終止策略視不同瀏覽器及版本而定,不會影響前端編寫邏輯,但前端勿在Service Worker
指令碼中儲存需要持久化的資訊,可以藉助localstorage
),當開啟新的可管轄頁面或者已管轄頁面發起message
等訊息時,Service Worker
進/執行緒會被重新喚起。
每當已安裝的Service Worker
有管轄頁面被開啟時,便會觸發Service Worker
指令碼更新,當Service Worker
指令碼發生了更改,便會忽略本地網路cache
的Service Worker
指令碼直接從網路拉取。若網路拉取的與本地有一個位元組的差異都會觸發Service Worker
指令碼的更新,更新流程與安裝類似,只是在更新安裝成功後不會立即進入active
狀態,需要等待舊版本的Service Worker
進/執行緒終止。
例項程式碼
// 在html裡註冊service-worker
if (navigator.serviceWorker != null) {
navigator.serviceWorker.register('sw.js')
.then(function(registration) {
console.log('Registered events at scope: ', registration.scope);
});
}
複製程式碼
// 首先定義需要快取的路徑, 以及需要快取的靜態檔案的列表。
var cacheStorageKey = 'minimal-pwa-8'
var cacheList = [
'/',
"index.html",
"main.css",
"e.png",
"*.png"
]
// 藉助 Service Worker, 可以在註冊完成安裝 Service Worker 時, 抓取資源寫入快取:
window.addEventListener('install', function(e) {
console.log('Cache event!')
e.waitUntil(
caches.open(cacheStorageKey).then(function(cache) {
console.log('Adding to Cache:', cacheList)
return cache.addAll(cacheList)
}).then(function() {
console.log('Skip waiting!')
return self.skipWaiting()
})
)
})
// 網頁抓取資源的過程中, 在 Service Worker 可以捕獲到 fetch 事件, 可以編寫程式碼決定如何響應資源的請求:
window.addEventListener('fetch', function(e) {
// console.log('Fetch event:', e.request.url)
e.respondWith(
caches.match(e.request).then(function(response) {
if (response != null) {
console.log('Using cache for:', e.request.url)
return response
}
console.log('Fallback to fetch:', e.request.url)
return fetch(e.request.url)
})
)
})
複製程式碼
關於事件
install 事件:當前Service Worker
指令碼被安裝時,會觸發 install 事件。
push事件:
push 事件是為推送通知而準備的。不過首先你需要了解一下 Notification API
和 PUSH API
。
通過 PUSH API
,當訂閱了推送服務後,可以使用推送方式喚醒 Service Worker
以響應來自系統訊息傳遞服務的訊息,即使使用者已經關閉了頁面。
online/offline事件:
當網路狀態發生變化時,會觸發 online
或 offline
事件。結合這兩個事件,可以與 Service Worker
結合實現更好的離線使用體驗,例如當網路發生改變時,替換/隱藏需要線上狀態才能使用的連結導航等。
fetch 事件:
當我們安裝完Service Worker
成功並進入啟用狀態後即執行於瀏覽器後臺,我們的這個執行緒就會一直監控我們的頁面應用,如果出現HTTP
請求,那麼就會觸發fetch
事件,並且給出自己的響應。
這個功能是十分強大的,藉助 Fetch API
和 Cache API
可以編寫出複雜的策略用來區分不同型別或者頁面的資源的處理方式。它能夠提供更加好的使用者體驗:
例如可以實現快取優先、降級處理的策略邏輯:監控所有 http 請求,當請求資源已經在快取裡了,直接返回快取裡的內容;否則使用 fetch API 繼續請求,如果是 圖片或 css、js 資源,請求成功後將他們加入快取中;如果是離線狀態或請求出錯,則降級返回預快取的離線內容。
使用webpack外掛
看到這裡很多人會有疑問了,既然可以通過service-worker
快取資源,那如果一個正式專案,在專案迭代後,並將程式碼推送到正式環境後,前端怎麼實時知道並重新快取新的資源呢?
第一種方式,就是每次修改都手動去更改sw檔案的版本號,觸發更新。
第二種就是使用webpack
外掛自動化處理
事實上,在我們真實的用webpack
生成的專案中,如果按照第一種方式手動去寫Service-worker.js
檔案的話,會遇到兩個問題:
webpack
生成的資源多會生成一串hash,Service-worker.js
的資源列表裡面需要同步更新這些帶hash的資源;- 每次更新程式碼,都需要通過更新
service-worker
檔案版本號來通知客戶端對所快取的資源進行更新.
看到這裡就該讓用webpack
外掛:offline-plugin 登場了,官方同時也推薦sw-precache-webpack-plugin ,offline-plugin不僅能夠解決剛剛那個提到的快取更新的問題,同時還具備以下的優點:
- 1、自動生成和更新Service-worker.js檔案和自動為SW新增快取資源列表
- 2、更為詳細的文件和例子;
- 3、迭代頻率相對更高,star數更多;
- 4、自動處理生命週期,使用者無需糾結生命週期的坑;
- 5、支援自動生成
manifest
檔案。
部署到專案中也十分的簡單
1.安裝
npm install offline-plugin [--save-dev]
複製程式碼
2.初始化
第一步,進入webpack.config.js
:
// webpack.config.js example
var OfflinePlugin = require('offline-plugin');
module.exports = {
// ...
plugins: [
// ... other plugins
// it's always better if OfflinePlugin is the last plugin added
new OfflinePlugin()
]
// ...
}
複製程式碼
3.入口檔案匯入
import * as OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install();
複製程式碼
經過上面的步驟,offline-plugin
已經整合到專案之中,通過webpack
構建即可。
具體程式碼也可檢視 demo
PWA的瀏覽器支援情況
畢竟是自家產品,chrome瀏覽器肯定是支援度最高的瀏覽器,chrome64版本是基本支援所有PWA功能API
的。
但國內的瀏覽器·支援情況相對差一些,而且chrome移動版的使用人群還是偏少的,不過在UC的支援程度也不低