摘要:類似于我們熟知的,可以脫離主線程,處理一些臟累活,干完后通過向主線程匯報工作結果。所以,也是脫離主線程的存在,與不同的是,具有持久化的能力。
什么是 PWA
Progressive Web App, 簡稱 PWA,是「漸進式」提升 Web App 體驗的一種新方法,能給用戶類似原生應用的體驗。
「高可靠,高性能,優體驗」是 PWA 慣用的形容詞,他的另外一個優點就是「漸進式」,開發者可以對照 PWA Checklist 逐步對自己站點進行 PWA 化升級。
PWA 的發展史
2007
蘋果前 CEO,Steve Jobs,2007 年 WWDC 上提出了為初代 iPhone 開發應用的概念,當時所介紹的,就是 Web App——可以從主屏直接啟動的 Web 應用。
圖片來源 appleinsider.com
可惜當時這個理念太過超前,并沒有引發太多關注,反而是后來的原生 App 應用更符合當時的市場需求,互聯網公司更愿意投入人力在原生 App 的開發上,而忽略了 Web。因此原生 App 的大量出現,占據了移動時代的主流地位,Web 似乎就要被 App 所取代。
2014
隨著 Web 技術的發展,時間來到 2014 年, W3C 公布了 Service Worker 的相關草案,其生產環境在 2015 年被 Chrome 支持。隨后 PWA 加以完善,相關技術不斷升級優化,在用戶體驗和用戶保活兩方面的可發掘價值越來越大。
2017
繼移動站點噴井式發展之焰末,原生 App 的弊端越發明顯,對于它來講,最大的痛點便是其天生封閉的基因導致的內容無法被索引,相對的 Web 站點可索引的優勢開始凸顯,與此同時,PWA 遵循 W3C 標準開發的技術,完全開放,能夠快速地被各大瀏覽器廠商支持,市場支持度一夜崛起。
另外一邊,App 的推廣并不順利,據調查統計,移動設備用戶 80% 的時間花費在了常用的 5 個應用上,16年近一半的美國用戶平均每月安裝「0」個新 App,用戶積極探索新 App 已經成為了過去式,拉新和保活的成本越來越高。
原生 App 的發展遇到了天花板,推廣也正向瓶頸一步步靠近,Web 看到了自己的機遇,PWA 以及支撐 PWA 的一系列關鍵技術應運而生。
2018
2018 年對于 PWA 來說是里程碑的一年,萬眾矚目的 Apple 終于在 iOS 11.3 里支持了 Web App Manifest,以及內置的 Safari 11.1 支持了 Service Worker。
與此同時,全球頂級瀏覽器廠商,Google、Microsoft、Apple 已經全數宣布支持 PWA 技術,這預示著,Web App 將會迎來全新的時代
2019
截至當下的 PWA 支持度
依據 Can I use 的統計(20190515)
App Manifest 的支持度達到 58.82%
Service Worker 的支持度達到 90.36%
Notifications API 的支持度達到 76.12%
Push API 的支持度達到 78.35%
Background Sync 的支持度達到 71.35%
Service Worker 以全數「登船」。信息來源于 jakearchibald.github.io/isservicewo…
PWA 的核心
PWA 有幾個核心功能,分別是「離線,安裝,推送」
離線瀏覽
弱網或離線的情況下依然可以「正常訪問」甚至「秒開」,這種體驗甚至超過了 app。主要的技術點就是 Service Worker。
SW 類似于我們熟知的 Web Worker,Web Worker 可以脫離主線程,處理一些「臟累」活,干完后通過 postMessage 向主線程匯報工作結果。所以,SW 也是脫離主線程的存在,與 Web Worker 不同的是,SW 具有持久化的能力。
SW 還具備有以下功能和特性:
一個獨立的 worker 線程,獨立于當前網頁進程,有自己獨立的 worker context。
一旦被 install,就永遠存在,除非被手動 unregister
用到的時候可以直接喚醒,不用的時候自動睡眠
可編程攔截代理請求和返回,緩存文件,緩存的文件可以被網頁進程取到(包括網絡離線狀態)
離線內容開發者可控
能向客戶端推送消息
不能直接操作 DOM
必須在 HTTPS 環境下才能工作
異步實現,內部大都是通過 Promise 實現
基于以上我們可以看到 SW 要讓緩存做到極致優雅的偉大使命。
想要靈活的使用 SW 功能,就要充分了解他的生命周期,以及各階段的狀態。
以下是 MDN 給出的 SW 的詳細生命周期圖。
可以看到,SW 的生命周期包含這么幾個狀態 安裝中, 安裝后, 激活中, 激活后和廢棄
安裝( installing ):這個狀態發生在 Service Worker 注冊之后,表示開始安裝,觸發 install 事件回調指定一些靜態資源進行離線緩存。 install 事件回調中有兩個方法:
event.waitUntil():傳入一個 Promise 為參數,等到該 Promise 為 resolve 狀態為止。
self.skipWaiting():self 是當前 context 的 global 變量,執行該方法表示強制當前處在 waiting 狀態的 Service Worker 進入 activate 狀態。
安裝后( installed ):Service Worker 已經完成了安裝,并且等待其他的 Service Worker 線程被關閉。
激活( activating ):在這個狀態下沒有被其他的 Service Worker 控制的客戶端,允許當前的 worker 完成安裝,并且清除了其他的 worker 以及關聯的舊緩存資源,等待新的 Service Worker 線程被激活。
activate 回調中有兩個方法:
event.waitUntil():傳入一個 Promise 為參數,等到該 Promise 為 resolve 狀態為止。
self.clients.claim():在 activate 事件回調中執行該方法表示取得頁面的控制權, 這樣之后打開頁面都會使用版本更新的緩存。舊的 Service Worker 腳本不再控制頁面,之后會被停止。
激活后( activated ):在這個狀態會處理 activate 事件回調 (提供了更新緩存策略的機會)。并可以處理功能性的事件,fetch (請求)、sync (后臺同步)和 push (推送)。
廢棄狀態 ( redundant ):這個狀態表示一個 Service Worker 生命周期的結束。
這里特別說明一下,進入廢棄狀態的原因可能為這幾種:
安裝 (install) 失敗
激活 (activating) 失敗
新版本的 Service Worker 替換了它并成功激活
MDN 也列出了 Service Worker 所有支持的事件:
install:Service Worker 安裝成功后被觸發的事件,在事件處理函數中可以添加需要緩存的文件
activate:當 Service Worker 安裝完成后并進入激活狀態,會觸發 activate 事件。通過監聽 activate 事件你可以做一些預處理,如對舊版本的更新、對無用緩存的清理等。
message:Service Worker 運行于獨立 context 中,無法直接訪問當前頁面主線程的 DOM 等信息,但是通過 postMessage API,可以實現他們之間的消息傳遞,這樣主線程就可以接受 Service Worker 的指令操作 DOM。
Service Worker 有幾個重要的「功能性事件」,這些功能性的事件支撐和實現了 Service Worker 的特性。
fetch (請求):當瀏覽器在當前指定的 scope 下發起請求時,會觸發 fetch 事件,并得到傳有 response 參數的回調函數,回調中可以做各種代理和緩存操作。
push (推送):push 事件是為推送準備的。不過首先需要了解一下 Notification API 和 PUSH API。通過 PUSH API,當訂閱了推送服務后,可以使用推送方式喚醒 Service Worker 以響應來自系統消息傳遞服務的消息,即使用戶已經關閉了頁面。
sync (后臺同步):sync 事件由 background sync (后臺同步)發出。background sync 配合 Service Worker 推出的 API,用于為 Service Worker 提供一個可以實現注冊和監聽同步處理的方法。但它還不在 W3C Web API 標準中。在 Chrome 中這也只是一個實驗性功能,需要訪問 chrome://flags/#enable-experimental-web-platform-features ,開啟該功能,然后重啟生效。
有了以上 SW 的事件及 API,接下來就是實戰部分了。
瀏覽器支持,包括 Cache API,Promise,HTML5 fetch API。關于目前 SW 的瀏覽器支持情況,后面將有介紹。
HTTPS,可用 127.0.0.1 和 localhost 測試,但部署須在 https 協議下。
巧婦難為無米之炊,以上兩個先決條件是必須要滿足的。
if ("serviceWorker" in navigator) { window.addEventListener("load", function() { navigator.serviceWorker.register("/sw.js", {scope: "/"}).then(function(registration) { // 注冊成功 console.log("ServiceWorker registration successful with scope: ", registration.scope); }, function(err) { // 注冊失敗 :( console.log("ServiceWorker registration failed: ", err); }); }); }
其實,關鍵代碼只有一行
navigator.serviceWorker.register("/sw.js", {scope: "/"})
注意,此處有坑
Service Worker 的注冊路徑決定了其 scope 默認作用域,如 SW 注冊文件的路徑為 https://www.a.com/public/sw.js 時,對應默認 scope 是 /public/,其作用范圍如下
域名 | 是否生效 |
---|---|
www.a.com/ | 否 |
www.a.com/page/ | 否 |
www.a.com/public/ | 是 |
www.a.com/public/page… | 是 |
www.b.com/ | 否 |
www.b.com/public/ | 否 |
以上可看出,當作用域 scope 為 /public/ 后,其作用范圍只限于本身和子域,父域和兄弟域皆無效,跨域就更免談了。
當然,我們可以通過設置 scope 來限定自己的作用域,但是!請注意,『以下寫法是錯誤的』。
navigator.serviceWorker.register("/public/sw.js", {scope: "/"}) // 錯誤寫法 navigator.serviceWorker.register("/public/sw.js", {scope: "/page"}) // 錯誤寫法
以上寫法均會報錯
The path of the provided scope ("/") is not under the max scope allowed ("/public/"). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
所以,sw.js 文件最好放在根域名下
如
navigator.serviceWorker.register("/sw.js", {scope: "/page"})
當 scope 不同時,請求被監控情況也有不同
代號 | 請求 |
---|---|
r1 | www.a.com/api |
r2 | www.a.com/page1/api |
r3 | www.a.com/page2/api |
r4 | www.a.com/static/img1.png |
r5 | www.b.com/api2 |
r6 | www.b.com/static/img2.png |
域名 | scope | 被監控請求 |
---|---|---|
www.a.com/ | / | r1-6 |
www.a.com/ | /page1 | 無 |
www.a.com/page1 | / | r1-6 |
www.a.com/page1 | /page1 | r1-6 |
www.a.com/page1 | /page2 | 無 |
所以,scope 與被監控請求的域并沒有什么關系,他只與站點域名有關
我們可以通過打開 Chrome 的 DevTools -> Application -> Service Workers 查看 SW 的注冊情況。
看到類如 Status: #xxxx activated and is running,即說明注冊并激活成功。
也可以通過打開 Chrome 的管理頁 chrome://inspect/#service-workers 查看
在受控頁面啟動注冊流程后,我們來看看處理 install 事件的 Service Worker 腳本。
最基本的例子是,您需要為安裝事件定義回調,并處理想要緩存的文件。
self.addEventListener("install", function(event) { // Perform install steps });
在 install 回調的內部,我們可以執行以下步驟(當然也可以啥也不干):
打開緩存。
緩存文件。
確認所有需要的資產是否已緩存。
var CACHE_NAME = "my-site-cache-v1"; var urlsToCache = [ "/", "/styles/main.css", "/script/main.js" ]; self.addEventListener("install", function(event) { // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log("Opened cache"); return cache.addAll(urlsToCache); }) ); });
此處,我們以所需的緩存名稱調用 caches.open(),之后再調用 cache.addAll() 并傳入文件數組。 這是一個 promise 鏈(caches.open() 和 cache.addAll())。 event.waitUntil() 方法帶有 promise 參數并使用它來判斷安裝所花費的時間,以及安裝是否成功。
如果所有文件都成功緩存,則將安裝 Service Worker。 如有任意文件無法下載,則安裝失敗。此設計可保證 SW 啟動的正確性,但過長的資源列表也增加了安裝失敗的幾率,可根據項目情況自行定義,也可不定義。
在安裝 Service Worker 且用戶轉至其他頁面或刷新當前頁面后,Service Worker 將開始接收 fetch 事件。下面提供了一個示例。
self.addEventListener("fetch", function(event) { event.respondWith( //匹配緩存 caches.match(event.request) .then(function(response) { //命中走觀察 if (response) { return response; } //未命中則透傳向網絡 return fetch(event.request); } ) ); });
如果希望連續緩存新請求,可以通過處理 fetch 請求的響應并將其添加到緩存來實現,如下所示。
self.addEventListener("fetch", function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; } /** * 通過檢查,則克隆響應。 * 這樣做的原因在于,該響應是數據流, 因此主體只能使用一次。 * 由于我們想要返回能被瀏覽器使用的響應,并將其傳遞到緩存以供使用, * 因此需要克隆一份副本。我們將一份發送給瀏覽器,另一份則保留在緩存。 */ var fetchRequest = event.request.clone(); return fetch(fetchRequest).then( function(response) { // 只緩存成功的請求,第三方資源不緩存,當然也可以處理緩存。 if(!response || response.status !== 200 || response.type !== "basic") { return response; } var responseToCache = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); }); return response; } ); }) ); });
上一部分介紹了 SW 的使用,以及自定義請求響應,實際上,fetch 的玩法有很多,但也都是大同小異。
因此,為了使 SW 更容易使用,GoogleChrome 團隊在 Chrome Submit 2017 上首次推出的一套 Web App 靜態資源和請求結果本地存儲的解決方案 workbox。
來直接感受下 workbox 的語法
// sw.js。 SW 的注冊不變,改變的只是 `sw.js` 的寫法 importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); // 不同的資源使用不同的緩存策略,并存儲在不同的 storage 中 workbox.routing.registerRoute( /.(");
這里有個緩存策略,簡略介紹一下。
Stale While Revalidate
此策略會優先匹配緩存,如果未命中,則透傳網絡,如果命中則返回緩存響應,同時在后臺更新緩存網絡響應,此策略比較安全,更像是競爭策略,誰快誰響應。
Network First
網絡優先策略,如果網絡通暢,返回網絡響應并緩存,如果離線,則返回緩存響應
Cache First
緩存優先策略,如果緩存匹配,返回響應,如果不匹配則透傳網絡,并緩存「有效」響應
Network Only
強制網絡響應,即為普通請求
Cache Only
強制緩存響應,無匹配則返回 404
更多關于 workbox 請前往 developers.google.com/web/tools/w…
可「安裝」
PWA 另外一個爆點就是「可安裝」,學名叫作「添加到主屏幕」,這歸功于一個配置文件 manifest.json,它給予開發者自定義圖標、顯示名稱、啟動方式等信息并添加至桌面的能力,同時也提供 API 方便開發者管理網絡應用安裝橫幅,讓用戶可以方便快捷地將站點添加到主屏幕中。
支持度
當前 manifest.json 的標準仍屬于草案階段,Chrome、Firefox 和 Apple 都已經實現了這個功能或者功能的部分,微軟正努力在 Edge 瀏覽器上實現。
在 caniuse 中可查到 manifest 的支持度,數據顯示 92.64% 的移動瀏覽器已經達到了支持或者部分支持的程度,想必在不久以后,當規范標準通過后,manifest 的支持度可以達到一個新高度。
配置
我以一個相對完整的 manifest.json 配置文件進行講解
配置介紹
// manifest.json { /* 自定義名稱 */ "short_name": "短名稱", "name": "這是一個完整名稱", /** 自定義安裝 icon * 當PWA添加到主屏幕時,瀏覽器會根據有效圖標的 sizes 字段進行選擇。 * 首先尋找與顯示密度相匹配并且尺寸調整到 48dp 屏幕密度的圖標; * 如果未找到任何圖標,則會查找與設備特性匹配度最高的圖標; * 如果匹配到的圖標路徑錯誤,將會顯示瀏覽器默認 icon。 * * 在啟動應用時,啟動畫面圖像會從圖標列表中提取最接近 128dp 的圖標進行顯示 */ "icons": [ { "src": "path-to-images/icon-96x96.png", "type": "image/png", "sizes": "96x96" }, { "src": "path-to-images/icon-144x144.png", "type": "image/png", "sizes": "144x144" } ], /* 設置啟動網址 */ "start_url": "index.html", /** 設置啟動背景顏色 * 完整色值 "#0000ff" * 縮寫 "#00f" * 預設色值 "blue" * rgb "rgb(0, 0, 255)" * transparent 背景色顯示為黑色 */ "background_color": "#0000ff", /** 設置啟動顯示類型 * fullscreen 應用的顯示界面將占滿整個屏幕 * standalone 瀏覽器相關UI(如導航欄、工具欄等)將會被隱藏 * minimal-ui 顯示形式與standalone類似,瀏覽器相關UI會最小化為一個按鈕,不同瀏覽器在實現上略有不同 * browser 瀏覽器模式,與普通網頁在瀏覽器中打開的顯示一致 */ "display": "fullscreen", /** 指定頁面顯示方向 * 更多配置介紹:https://lavas.baidu.com/pwa/engage-retain-users/add-to-home-screen/improved-webapp-experience#%E6%8C%87%E5%AE%9A%E9%A1%B5%E9%9D%A2%E6%98%BE%E7%A4%BA%E6%96%B9%E5%90%91 */ "orientation": "landscape", /* 設置主題顏色 */ "theme_color": "#000", /** 設置作用域 * start_url 必須在作用域內 */ "scope": "/" }
測試
配置完之后就可以在 Chrome 的 DevTools 中進行驗證測試了。
圖片來源于 developers.google.com
可推送消息
消息推送是 App 保活沖績效的常用手段,由于 HTTP 是一個無狀態協議,推送功能在用戶關閉了瀏覽器之后便沒了辦法,這一次 PWA 賦予了 Web 這個能力。
其中便包含了兩個技術點
推送 push:連接服務端和 SW 進行消息傳遞
通知 notification:控制客戶端(瀏覽器)進行消息提示
下面我們來解析一個通知體
// 消息體的 title self.addEventListener("push", event => { const title = "Credit Card"; const options = { // 主內容 "body": "Did you make a $1,000,000 purchase at Dr. Evil...", // 視覺配置,如 icon,Badge,image 等,不同的視覺配置展示的位置也不同 // 詳情參看 https://lavas.baidu.com/pwa/engage-retain-users/notification/notification-display "icon": "images/ccard.png", // 震動設置,其中的數字以2個為一組,分別表示震動的毫秒數,和不震動的毫秒數 "vibrate": [200, 100, 200, 100, 200, 100, 400], // 鈴聲 "sound": "path/to/sound.mp3", // 標簽,用于客戶端消息歸類 "tag": "request", // actions,用戶操作后會將結果反饋給瀏覽器 "actions": [ { "action": "yes", "title": "Yes", "icon": "images/yes.png" }, { "action": "no", "title": "No", "icon": "images/no.png" } ] } // 激活通知 self.registration..showNotification(title, options); }); self.addEventListener("notificationclick", event => { // Do something with the event event.notification.close(); }); self.addEventListener("notificationclose", event => { // Do something with the event });
以上的消息配置,展示的結果如下圖。
關于推送功能的更多實操不屬于本文探究的范疇,有實際需求的同學可以前往官網進行了解。
傳送門>>
developers.google.com/web/fundame…
lavas.baidu.com/pwa/engage-…
PWA & 小程序
有人說「PWA 是小程序的祖宗」,不無道理,PWA 對小程序肯定存在一定的借鑒意義,但是否會擠壓 PWA 的市場?我們應該放心,小程序的設計并不是 Web 的替代者,而是介于原生 App 和 Web 之間的存在。
小程序更傾向于輕便及時觸手可得。既沒有原生 App 的「沉重」也沒有 Web 「遲鈍」。在此得天獨厚的基礎之上加之以「社交流量」的加持,微信小程序的存在并非偶然。
但是,如果沒了網絡,一樣玩不轉;主流的搜索引擎并無法捕獲小程序的內容。所以,App、Web 和小程序是相輔相成的。
另外,筆者想表達另外一個觀點
「存在即合理,合理未必長久」
在經歷過一段痛苦的微信小程序洗禮之后,我們「欣然」接受了。奈何眾XX小程序『競相開放,爭奇斗艷,不亦樂乎』。殊不知,我等不才,竟要為了這區區語法之差異,徹夜無法停歇,然,產與出相比,孰輕孰重?
唉~程序員何必為難程序員~~
總結
關于 PWA 的技術早在 2 年前即已相對完整,只是由于「天朝人民太過賦予」,對支持與否未發布意見的 Apple 在天朝市場有著舉足輕重的地位,而「外圍」仿佛對 Android 機更為推崇,所以,PWA 在國內的發展和推廣并不理想。
此時此刻,PWA 的支持度也達到了一個相對讓人滿意的水平,雖然體驗依然無法和原生 App 相提并論,但作為 App 短板的補丁已是綽綽有余。
所以,『架構師』們,可以盤起來了。
以上就是 PWA 的相關知識點,希望對你有所幫助。
[1]. developers.google.com/web/fundame…
[2]. developers.google.com/web/fundame…
[3]. developers.google.com/web/tools/w…
[4].下一代 Web 應用模型 —— Progressive Web App
[5]. lavas.baidu.com/
首發:zwwill/blog#33
作者:木羽
轉載請標明出處
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6679.html
摘要:手機型號圖標大小分辨率大小豎屏豎屏豎屏豎屏添加智能廣告條,頂部會自動彈出一條關于應用下載的廣告條,下載地址就是你設置的文檔參考 字符編碼 兩種寫法等效,推薦寫第一種 語言設置 簡體中文 繁體中文 不是寫lang=zh-CN 瀏覽器內核控制 設置IE的優先版本為edge,若不存在則選擇當前最高版本,含有谷歌內核的會選擇谷歌,如果設置多條的話只有第一條生效 針對多內核瀏覽器設置優...
摘要:熱門文章我在淘寶做前端的這三年紅了櫻桃,綠了芭蕉。文章將在淘寶的三年時光折射為入職職業規劃招聘晉升離職等與我們息息相關的經驗分享,值得品讀。 showImg(https://segmentfault.com/img/remote/1460000018739018?w=1790&h=886); 【Alibaba-TXD 前端小報】- 熱門前端技術快報,聚焦業界新視界;不知不覺 2019 ...
摘要:熱門文章我在淘寶做前端的這三年紅了櫻桃,綠了芭蕉。文章將在淘寶的三年時光折射為入職職業規劃招聘晉升離職等與我們息息相關的經驗分享,值得品讀。 showImg(https://segmentfault.com/img/remote/1460000018739018?w=1790&h=886); 【Alibaba-TXD 前端小報】- 熱門前端技術快報,聚焦業界新視界;不知不覺 2019 ...
摘要:原生越來越好了,如果是簡單的頁面,沒必要引入一個龐大的,尤其在手機端,對速度流量敏感的地方,另外最好自己簡單封裝一些常用的函數,比如等。。。這時候它就不知所措了,只好待在中間。參考前端界有哪些越早知道越好的小技巧小知識 1.You Might Not Need jQuery 不用jQuery,原生js如何實現,可以參考這里:You Might Not Need jQuery 。原生js...
閱讀 2570·2021-09-06 15:02
閱讀 3199·2021-09-02 10:18
閱讀 2820·2019-08-30 15:44
閱讀 684·2019-08-30 15:43
閱讀 1947·2019-08-30 14:08
閱讀 2758·2019-08-30 13:16
閱讀 1397·2019-08-26 13:52
閱讀 930·2019-08-26 12:21