前言:今天結識了google PWA提供的一個對移動端Web應用提供離線體驗的一個功能,感覺很有用。我這里不分享自己的寫法和代碼。官網文檔說的很詳細,直接粘過來大家看吧。
推薦官網地址:你的第一個漸進式 Web 應用(Progressive Web App - PWA)
文章詳情如下:
簡介
什么是 Web 應用,一個漸進式 Web 應用?
漸進式 Web 應用會在桌面和移動設備上提供可安裝的、仿應用的體驗,可直接通過 Web 進行構建和交付。它們是快速、可靠的 Web 應用。最重要的是,它們是適用於任何瀏覽器的 Web 應用。如果你在構建一個 Web 應用,其實已經開始構建漸進式 Web 應用了。
快速 & 可靠
每個 Web 體驗都必須快速,對於漸進式 Web 應用更是如此。快速是指在屏幕上獲取有意義內容所需的時間,要在不到 5 秒的時間內提供交互式體驗。
並且,它必須真的很快。很難形容可靠的高性能有多重要。可以這樣想: 本機應用的首次加載令人沮喪。應用商店和漫長的下載都是門檻,不過,該前期成本包含了今后該應用的每次啟動時間,一旦你安裝好該應用,這些啟動過程就不會再有額外的延遲。每個應用的啟動速度都與上一次一樣快,沒有任何差異。已安裝的漸進式 Web 應用必須能讓用戶獲得可靠的性能。
安裝
漸進式 Web 應用可以在瀏覽器選項卡中運行,但也可以安裝。為網站添加書簽只是添加了一個快捷方式,但已安裝的漸進式 Web 應用的外觀和行為會與任何已安裝的應用類似。它與其它應用的啟動位置是一樣的。你可以控制啟動體驗,包括自定義啟動畫面、圖標等。它在應用窗口中作為應用運行,沒有地址欄或其它瀏覽器 UI。與其它已安裝的應用一樣,它是任務管理器中的頂級應用。
請記住,讓可安裝的 PWA 保持快速可靠是至關重要的。安裝 PWA 的用戶希望他們的應用正常運行,無論他們使用何種網絡連接。這是每個已安裝應用必須滿足的基線預期。
手機 & 桌面
使用響應式設計技術,漸進式 Web 應用可在移動和桌面上工作,使用跨平台的單一代碼庫。如果你正在考慮是否編寫本機應用,請看看 PWA 提供的好處。
你將構建什么
在此 codelab 中,你將使用漸進式 Web 應用技術構建天氣 Web 應用。你的應用將:
- 使用響應式設計,因此它可用在桌面或移動設備上。
- 快速,使用 Service Worker 來預緩存運行時所需的應用資源(HTML,CSS,JavaScript,圖像),並在運行時緩存天氣數據以提高性能。
- 可安裝,使用 Web 應用清單(manifest)和
beforeinstallprompt
事件告訴用戶它是可安裝的。
Warning:為了簡化此 codelab ,並解釋提供離線體驗的基礎知識,我們使用的是原生 JavaScript。在生產應用中,我們強烈建議使用 Workbox 工具來構建 Service Worker 。它可以幫你消除許多可能遇到的坑和死角。
你將學到什么
- 如何創建和添加 Web 應用清單
- 如何提供簡單的離線體驗
- 如何提供完整的離線體驗
- 如何使你的應用可安裝
此 codelab 專注於漸進式 Web 應用。屏蔽了不相關的概念和代碼塊,並為你提供簡單的復制和粘貼。
你需要什么
-
最近版本的 Chrome(74 或更高版本)
PWA 只是個 Web 應用,因此適用於所有瀏覽器,但我們將使用 Chrome DevTools 的一些功能來更好地了解瀏覽器層面的情況,並用它來測試安裝體驗。
-
了解 HTML,CSS,JavaScript 和Chrome DevTools 。
環境准備
獲取 Dark Sky API 的密鑰
我們的天氣數據來自 Dark Sky API。要使用它,你需要申請 API 密鑰。它很容易使用,並且可以免費用於非商業項目。
Note: 你還可以在沒有 Dark Sky API 密鑰的情況下完成此 codelab。如果我們的服務器無法從 Dark Sky API 獲取真實數據,它將返回假數據。
驗證你的 API 密鑰是否正常工作
要測試你的 API 密鑰是否正常工作,請向 DarkSky API 發出 HTTP 請求。修改以下網址,將 DARKSKY_API_KEY
替換為你的 API 密鑰。如果一切正常,你應該看到紐約市的最新天氣預報。
https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319
獲取代碼
我們已將此項目所需的一切都放入 Git 倉庫中。首先,你需要獲取代碼並在你喜歡的開發環境中打開它。對於此代碼庫,我們建議使用 Glitch。
強烈推薦: 使用 Glitch 導入倉庫
推薦用 Glitch 來使用此代碼庫。
- 打開一個新的瀏覽器選項卡,然后轉到 https://glitch.com 。
- 如果你沒有帳戶,則需要注冊。
- 單擊 New Project,然后單擊 Clone from Git Repo.
- 克隆 https://github.com/googlecodelabs/your-first-pwapp.git 並單擊確定。
- 獲取完 repo 后,編輯
.env
文件,並使用 DarkSky API 密鑰來更新它。 - 單擊 Show Live 按鈕以查看此 PWA 的運行情況。
替代方案: 下載代碼並在本地工作
如果你想下載代碼並在本地工作,你需要安裝好最新版本的 Node 和代碼編輯器。
Caution: 如果你在本地工作,某些 Lighthouse 審計可能無法通過,甚至可能無法安裝,因為本地服務器並沒有在安全環境下運行。
- 解壓縮下載的 zip 文件。
- 運行
npm install
以安裝運行服務器所需的依賴項。 - 編輯
server.js
並設置 DarkSky API 密鑰。 - 運行
node server.js
以在端口 8000 上啟動服務器. - 打開瀏覽器選項卡並轉到 http://localhost:8000
建立基線
我們的起點是什么?
我們的起點是為此 codelab 設計的基本天氣應用。代碼已經大幅簡化,以突顯此代碼庫中的概念,並且它幾乎沒有做錯誤處理。如果你選擇在生產應用中復用此代碼,請確保處理各種錯誤並完全測試所有代碼。
我們將試着......
- 使用右下角的藍色加號按鈕來添加新城市。
- 使用右上角的刷新按鈕來刷新數據。
- 使用每張城市卡片右上角的 x 來刪除城市。
- 了解它在桌面和移動設備上的工作原理。
- 看看當離線時會發生什么。
- 使用 Chrome 的“網絡”面板,查看當網絡受限制為慢速3G 時會發生什么。
- 通過更改
server.js
中的FORECAST_DELAY
為天氣預報服務器添加延遲
用 Lighthouse 進行審計
Lighthouse是一款易於使用的工具,可幫助你提高網站和網頁的質量。它可用來對性能,可訪問性,漸進式 Web 應用等進行審計。每種審計及都有一個參考文檔,解釋了該審計為何重要,以及如何解決所發現的問題。
我們將使用 Lighthouse 來審計我們的天氣應用,並驗證我們所做的更改。
Note: 你可以在 Chrome DevTools 中,以命令行或 Node 模塊的方式運行 Lighthouse。考慮將Lighthouse 添加到你的構建流程中,以確保你的 Web 應用不會出現回歸問題。
讓我們運行 Lighthouse
- 在新選項卡中打開項目。
- 打開 Chrome DevTools 並切換到 Audits 選項卡,DevTools 會顯示審計類別列表,全部啟用它們。
- 單擊 Run audits,60-90 秒后,Lighthouse 會在頁面上顯示報告。
審計漸進式 Web 應用
我們將重點關注漸進式 Web 應用的審計結果。
這里有很多紅色信息需要關注:
- ❗失敗: 在離線時當前頁面未給出 200 響應。
- ❗失敗: 在離線時
start_url
未給出 200 響應。 - ❗失敗: 未注冊用來控制頁面和
start_url
的 Service Worker。 - ❗失敗: Web 應用清單 (manifest) 不符合可安裝性要求。
- ❗失敗: 未配置自定義閃屏。
- ❗失敗: 未設置地址欄的主題顏色。
讓我們進入並開始修復其中的一些問題!
添加 Web 應用清單
到本節結束時,我們的天氣應用將通過以下審計:
- Web 應用清單 (manifest) 不符合可安裝性要求。
- 未配置自定義的初始屏幕。
- 未設置地址欄的主題顏色。
創建 Web 應用清單
Web 應用清單是一個簡單的 JSON 文件,它使開發人員能夠控制本應用對用戶的顯示方式。
使用 Web 應用清單,你的 Web 應用可以:
- 告訴瀏覽器你希望本應用在獨立窗口中打開(
display
)。 - 定義首次啟動本應用時要打開哪個頁面(
start_url
)。 - 定義應用在 Dock 或應用啟動器上應該是什么樣子(
short_name
,icons
)。 - 創建一個閃屏(
name
,icons
,colors
)。 - 告訴瀏覽器以橫向或縱向模式打開窗口(
orientation
)。 - 以及很多其它配置 。
在項目中創建名為 public/manifest.json
的文件,並復制/粘貼以下內容:
public/manifest.json
{
"name": "Weather",
"short_name": "Weather",
"icons": [{
"src": "/images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "/images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "/images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "/images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "/images/icons/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
}, {
"src": "/images/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2"
}
本清單可支持一組用於不同屏幕尺寸的圖標。對於此 codelab ,我們還包含了一些其它的,因為我們需要把它們集成進 iOS。
Note: 如果想安裝它,Chrome 會要求你至少提供 192x192px 圖標和 512x512px 圖標。但是你也可以提供其它尺寸。 Chrome 會使用最接近 48dp 的圖標,例如,2x 設備上的 96px 或 3x 設備的 144px。
添加指向 Web 應用清單的鏈接
接下來,我們需要通過向應用中的每個頁面添加 <link rel="manifest"...
來把此清單告知瀏覽器。把下列代碼行添加到 index.html
文件中的 <head>
元素下。
public/index.html
<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">
DevTools Detour
DevTools 提供了一種快速簡便的方法來檢查你的 manifest.json
文件。打開 Application 面板上的 Manifest 窗格。如果你已正確添加清單信息,你將能夠在此窗格中看到它以對人類友好的格式進行解析和顯示。
添加 iOS 元標記和圖標
iOS 上的 Safari 不支持 Web 應用清單( 至少目前為止 ),因此你需要將傳統的 meta
標簽添加到 index.html
文件的 <head>
中:
public/index.html
<!-- CODELAB: Add iOS meta tags and icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png">
額外工作: 簡易 Lighthouse 修復
我們的 Lighthouse 審計還提出了一些其它很容易解決的問題,所以讓我們先來處理下這些問題。
設置元描述
根據 SEO 審計,Lighthouse 提出我們的 “ Document does not have a meta description. ”。描述可以顯示在 Google 的搜索結果中。高質量,獨特的描述可以使你的搜索結果與搜索用戶更相關,並可以增加搜索流量。
要添加描述,請將以下 meta
標記添加到文檔的 <head>
中:
public/index.html
<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">
設置地址欄的主題顏色
在 PWA 審計中,Lighthouse 提出我們的應用“ Does not set an address-bar theme color ”。將瀏覽器的地址欄設置為與你的品牌色相匹配,可以提供更加沉浸式的用戶體驗。
要在移動設備上設置主題顏色,請將以下 meta
標記添加到文檔的 <head>
中:
public/index.html
<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />
用 Lighthouse 驗證更改
再次運行 Lighthouse(通過單擊“審計”窗格左上角的+號)並驗證你的更改。
SEO 審計
*✅通過: 文檔已有元描述。
漸進式應用審計
- ❗失敗: 在離線時當前頁面未給出 200 響應。
- ❗失敗: 在離線時
start_url
未給出 200 響應。 - ❗失敗: 未注冊用來控制頁面和
start_url
的 Service Worker。 *✅通過: Web 應用清單符合可安裝性要求。 *✅通過: 已配置自定義閃屏。 *✅通過: 已設置地址欄的主題顏色。
提供基本的離線體驗
用戶會期待所安裝的應用在離線時始終具有基線體驗。因此對於可安裝的 Web 應用來說,永遠不會顯示 Chrome 的離線恐龍圖是至關重要的。離線體驗的范圍包括簡單的離線頁面、具有先前緩存數據的只讀體驗,甚至具有完全功能的離線體驗(在網絡連接恢復時自動同步)。
在本節中,我們將向天氣應用添加一個簡單的離線頁面。如果用戶在離線時嘗試加載應用,則會顯示我們的自定義頁面,而不是瀏覽器顯示的典型離線頁面。到本節結束時,我們的天氣應用將通過以下審計:
- 在離線時當前頁面未給出 200 響應。
- 在離線時
start_url
未給出 200 響應。 - 未注冊用來控制頁面和
start_url
的 Service Worker。
在下一部分中,我們將使用完整的離線體驗替換我們的自定義離線頁面。這將改善離線體驗,但更重要的是,它將顯著提高我們的性能,因為我們的大多數資產(HTML,CSS 和 JavaScript)都將在本地存儲和提供,從而消除了網絡方面的潛在瓶頸。
向 Service worker 求援
如果你對 Service Worker 不熟悉,可以通過閱讀 Service Workers 簡介來了解它們可以做什么,它們的生命周期如何工作等等,從而獲得初步的理解。完成此 codelab 后,請務必查看 Debugging Service Workers code lab以便更深入地了解如何與 Service Worker 合作。
通過 Service Worker 提供的功能應被視為漸進增強功能,並且僅在瀏覽器支持時才添加。例如,對於 Service Worker ,你可以為應用緩存應用外殼和數據,以便在網絡不可用時也能使用它。如果不支持 Service Worker ,則不會調用離線代碼,而用戶將獲得基本體驗。使用特性檢測來提供漸進增強功能的開銷很小,並且在不支持該功能的舊瀏覽器中不會出錯。
Warning: Service Worker 功能僅在通過 HTTPS 訪問的頁面上可用(http://localhost 及其等價物也可用來協助我們進行測試)。
注冊 Service Worker
第一步是注冊 Service Worker 。將以下代碼添加到 index.html
文件中:
public/index.html
// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then((reg) => {
console.log('Service worker registered.', reg);
});
});
}
此代碼檢查 Service Worker API 是否可用,如果是,則在頁面加載完畢時注冊 /service-worker.js
的 Service Worker。
注意, Service Worker 是從根目錄提供的,而不是從 /scripts/
目錄。這是設置 Service Worker 的 scope
的最簡單方法。Service Worker 的 scope
會決定 Service Worker 能控制哪些文件,換句話說, Service Worker 將攔截哪個路徑下的請求。默認的 scope
是 Service Worker 文件所在的位置並及其各級目錄。因此,如果 service-worker.js
位於根目錄中,則 Service Worker 將控制對該域中所有網頁的請求。
Precache 離線頁面
首先,我們需要告訴 Service Worker 緩存什么。我們已經創建了一個簡單的離線頁面 ( public/offline.html
),只要沒有網絡連接,我們就會顯示它。
在 service-worker.js
中,將 '/offline.html',
添加到 FILES_TO_CACHE
數組中,最終結果應如下所示:
public/service-worker.js
// CODELAB: Update cache names any time any of the cached files change.
const FILES_TO_CACHE = [
'/offline.html',
];
接下來,我們需要修改 install
事件以告知 Service Worker 預先緩存離線頁面:
public/service-worker.js
// CODELAB: Precache static resources here.
evt.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('[ServiceWorker] Pre-caching offline page');
return cache.addAll(FILES_TO_CACHE);
})
);
Note: Service Worker 的事件和生命周期將在下一節中介紹。
我們的 install
事件現在使用 caches.open()
打開緩存並傳入緩存名稱。提供緩存名稱能讓我們對緩存資源進行版本控制或移除,這樣我們我們就能輕松的修改一個而不影響另一個。
一旦緩存打開,我們就可以調用 cache.addAll()
了,它接受一個 URL 列表,從服務器獲取這些 URL 並將其響應添加到緩存中。請注意,如果任何單個請求失敗,cache.addAll()
就不會生效。這意味着你可以確保,如果安裝步驟成功了,你的緩存必然處於一致的狀態。但是,如果由於某種原因而失敗了,它將在下次 Service Worker 啟動時自動重試。
DevTools Detour
讓我們來看看如何使用 DevTools 來理解和調試 Service Worker 。在重新加載頁面之前,打開 DevTools,轉到 Application面板上的 Service Workers 窗格。它應該如下所示:
當你看到這樣的空白頁面時,表示當前打開的頁面中沒有任何已注冊的 Service Worker 。
現在,重新加載頁面,“Service Worker” 窗格應如下所示:
當你看到這樣的信息時,表示該頁面正在運行 Service Worker 。
狀態標簽旁邊有一個數字(這里是34251),在你使用 Service Worker 時,請密切注意該數字。這是一種判斷你的 Service Worker 是否已更新的簡單方式。
清理舊的離線頁面
我們將使用 activate
事件來清理緩存中的任何舊數據。此代碼可確保你的 Service Worker 在任何應用外殼文件發生更改時更新其緩存。為了使其工作,你需要在 Service Worker 文件的頂部增加 CACHE_NAME
變量。
將以下代碼添加到 activate
事件中:
public/service-worker.js
// CODELAB: Remove previous cached data from disk.
evt.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== CACHE_NAME) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
DevTools Detour
打開 “Service Worker” 窗格,刷新頁面,你將看到安裝了新的 Service Worker ,並且狀態編號會遞增。
更新后的 Service Worker 立即獲得控制權,因為我們的 install
事件以 self.skipWaiting()
結束, activate
事件以 self.clients.claim()
結束。如果沒有這些,只要有一個打開着此頁面的選項卡,舊的 Service Worker 就會繼續控制此頁面。
處理失敗的網絡請求
最后,我們需要處理 fetch
事件。我們將使用 "網絡優先,回退到緩存" 的策略 。 Service Worker 將首先嘗試從網絡獲取資源,如果失敗,它將從緩存中返回離線頁面。
public/service-worker.js
// CODELAB: Add fetch event handler here.
if (evt.request.mode !== 'navigate') {
// Not a page navigation, bail.
return;
}
evt.respondWith(
fetch(evt.request)
.catch(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.match('offline.html');
});
})
);
fetch
處理程序只需要處理頁面導航,其它請求會被該處理程序忽略,交由瀏覽器進行常規處理。但是,如果該請求的 .mode
是 navigate
,就會嘗試用 fetch
從網絡獲取項目。如果失敗,則 catch
處理程序就會用 caches.open(CACHE_NAME)
打開緩存,並使用 cache.match('offline.html')
來獲得預緩存的離線頁面。然后使用 evt.respondWith()
將結果傳回瀏覽器。
Key Point: 把 fetch
調用包裝在 evt.respondWith()
中會阻止瀏覽器的默認處理,並告訴瀏覽器我們要自己處理該響應。如果你沒有在 fetch
處理程序中調用 evt.respondWith()
,你只會獲得默認的網絡行為。
DevTools Detour
讓我們檢查一下,確保一切正常。打開 “Service Worker” 窗格,刷新頁面,你將看到安裝了新的 Service Worker ,並且狀態編號會增加。
我們還可以查看已緩存的內容。轉到 DevTools 的 Application 面板上的 Cache Storage 窗格。右鍵單擊 Cache Storage,選擇 Refresh Caches,展開該部分,你應該會在左側看到靜態緩存的名稱。單擊緩存名稱將顯示緩存的所有文件。
現在,我們來測試下離線模式。返回 DevTools 的 Service Workers 窗格並檢查 Offline 復選框。檢查后,你應該會在 Network 面板選項卡旁邊看到一個黃色警告圖標。這表示你處於離線狀態。
重新加載你的頁面......可以了!我們得到我們的離線熊貓圖,而不是 Chrome 的離線恐龍圖!
測試 Service Worker 的小技巧
當涉及緩存時,調試 Service Worker 可能是一個挑戰,如果緩存未按預期更新,事情可能變成噩夢。在典型的 Service Worker 生命周期和代碼中的錯誤中,你可能會很快感到沮喪。但是,不必如此。
使用 DevTools
在 “Application” 面板的“ Service Worker ”窗格中,有一些復選框可以讓你更輕松。
- Offline - 選中后會模擬離線體驗並阻止任何請求進入網絡。
- Update on reload - 選中后將獲得最新的 Service Worker ,安裝它,並立即激活它。
- Bypass for network - 當檢查請求時,繞過 Service Worker 並直接發送到網絡。
來點新鮮的
在某些情況下,你可能會發現自己正在加載已緩存的數據或者沒能按預期更新內容。要清除所有已保存的數據(localStorage,indexedDB 數據,緩存文件)並刪除任何 Service Worker ,請使用 “Application” 選項卡中的 “Clear storage” 窗格。或者,你也可以使用隱身窗口。
其它提示:
- 當 Service Worker 取消注冊之后,它可能仍然會列在這里,直到包含它的瀏覽器窗口關閉。
- 如果你的應用打開了多個窗口,則直到將所有窗口都重新加載並更新到最新的 Service Worker 之前,新的 Service Worker 不會生效。
- 取消注冊 Service Worker 不會清除緩存!
- 如果存在 Service Worker 並且注冊了新的 Service Worker ,則除非你立即獲得控制權 ,否則在重新加載頁面之前,新的 Service Worker 將不會獲得控制權。
使用 Lighthouse 驗證這些更改
再次運行 Lighthouse 並驗證你的更改。在驗證更改之前,請不要忘記取消 “Offline” 復選框!
SEO 審計
*✅通過: 文檔已有元描述。
漸進式應用審計
*✅通過: 在離線時當前頁面未給出 200 響應。 *✅通過: 在離線時 start_url
未給出 200 響應。 *✅通過: 未注冊用來控制頁面和 start_url
的 Service Worker。 *✅通過: Web 應用清單符合可安裝性要求。 *✅通過: 已配置自定義閃屏。 *✅通過: 已設置地址欄的主題顏色。
提供完整的離線體驗
將手機開啟飛行模式,然后嘗試運行一些你喜歡的應用。幾乎在所有情況下,它們都提供了相當強大的離線體驗。用戶希望他們的應用具有穩健的體驗,Web 應用也不例外。漸進式 Web 應用在設計時應該把離線作為核心場景。
Key Point:離線優先的設計可以通過減少應用發出的網絡請求數量並改用預先緩存資源並直接從本地緩存提供資源來大幅提高Web 應用的性能。即使用最快的網絡連接,從本地緩存提供的服務也仍然會更快!
Service Worker 生命周期
Service Worker 的生命周期是最復雜的部分。如果你不知道它想要做什么以及有什么好處,你可能會覺得它處處和你作對。但是一旦你知道它是如何工作的,你就可以為用戶提供無縫、免打擾的更新,將網絡應用和本機應用中最好的一面結合起來。
Key Point:此 codelab 僅涵蓋 Service Worker 生命周期的基礎知識。要深入了解,請參閱有關 WebFundamentals 的 Service Worker 生命周期 文檔。
install
事件
Service Worker 獲得的第一個事件是 install
。它會在 Service Worker 執行時立即觸發,並且每個 Service Worker 只會調用一次。如果你更改了 Service Worker 腳本,瀏覽器就會將其視為另一個 Service Worker,並且它將獲得自己的 install
事件。
通常, install
事件用於緩存應用運行時所需的全部內容。
activate
事件
Service Worker 每次啟動時都會收到 activate
事件。 activate
事件的主要目的是配置 Service Worker 的行為,清除以前運行中遺留的任何資源(例如舊緩存),並讓 Service Worker 准備好處理網絡請求(例如下面要講的 fetch
事件)。
fetch
事件
fetch 事件允許 Service Worker 攔截並處理任何網絡請求。它可以通過網絡獲取資源、可以從自己的緩存中提取資源、生成自定義響應,以及很多種不同的選擇。查看離線寶典了解你可以使用的不同策略。
更新 Service Worker
瀏覽器會檢查每個頁面加載時是否有新版本的 Service Worker 。如果找到新版本,則會下載這個新版本並在后台安裝,但不會激活它。它會處於等待狀態,直到不再打開任何使用舊 Service Worker 的頁面。一旦關閉了使用舊 Service Worker 的所有窗口,新的 Service Worker 就會被激活並獲得控制權。更多詳細信息,請參閱 Service Worker 生命周期文檔中的更新 Service Worker部分。
選擇正確的緩存策略
選擇正確的緩存策略取決於你嘗試緩存的資源類型以及以后可能需要的資源。對於這個天氣應用,需要緩存的資源可以分為兩類: 需要預先緩存的資源以及將在運行時緩存的數據。
緩存靜態資源
預先緩存資源與用戶安裝桌面或移動應用時的情況類似。應用運行所需的關鍵資源已安裝或緩存在設備上,以后無論是否有網絡連接都可以加載它們。
對於這個應用,我們將在安裝 Service Worker 時預先緩存所有靜態資源,以便把我們運行應用所需的一切都存儲在用戶的設備上。為了確保我們的應用快速加載,我們將使用緩存優先策略:不去網絡獲取資源,而是從本地緩存中取出;只有當緩存不可用時,我們才會嘗試從網絡中獲取它。
從本地緩存中取可消除任何網絡方面的變數。無論用戶使用何種網絡(WiFi,5G,3G 甚至 2G),我們需要運行的關鍵資源都幾乎可以立即使用。
Caution: 在此示例中,使用 cache-first
策略提供靜態資源,這會導致在不詢問網絡的情況下返回任何緩存內容的副本。雖然 cache-first
策略易於實施,但它可能會在將來的演化中帶來挑戰。
緩存應用數據
stale-while-revalidate strategy 對某些類型的數據是理想的,比如本應用。它會盡可能快地獲取屏幕上要顯示的數據,然后在網絡返回最新數據后進行更新。 Stale-while-revalidate 意味着我們需要發起兩個異步請求,一個到緩存,一個到網絡。
在正常情況下,緩存數據幾乎會立即返回,為應用提供可以使用的最新數據。然后,當網絡請求返回時,將使用來自網絡的最新數據更新應用。
對於我們的應用,這提供了比 "網絡優先,回退到緩存" 策略更好的體驗,因為用戶不必等到網絡請求超時后才在屏幕上看到某些內容。他們最初可能會看到較舊的數據,但一旦網絡請求返回,應用就將使用最新數據進行更新。
更新應用邏輯
如前所述,應用需要啟動兩個異步請求,一個到緩存,一個到網絡。該應用使用 window
上的 caches
對象來訪問緩存並獲取最新數據。這是漸進增強的一個很好的例子,因為 caches
對象可能並非在所有瀏覽器中都可用,如果不可用,網絡請求仍然可以工作。
更新 getForecastFromCache()
函數,檢查 caches
對象是否在全局 window
對象中可用,如果是,請從緩存中請求數據。
public/scripts/app.js
// CODELAB: Add code to get weather forecast from the caches object.
if (!('caches' in window)) {
return null;
}
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
.then((response) => {
if (response) {
return response.json();
}
return null;
})
.catch((err) => {
console.error('Error getting data from cache', err);
return null;
});
然后,我們需要修改 updateData()
以便它進行兩次調用,一次調用 getForecastFromNetwork()
以從網絡獲取天氣預報,並發起另一次 getForecastFromCache()
以獲取緩存的最新天氣預報:
public/scripts/app.js
// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
.then((forecast) => {
renderForecast(card, forecast);
});
我們的天氣應用現在發出兩個異步數據請求,一個來自緩存,另一個來自 fetch
。如果緩存中有數據,它將被非常快速地返回和渲染(幾十毫秒)。然后,當 fetch
響應時,將使用直接來自天氣 API 的最新數據更新卡片。
請注意緩存請求和 fetch
請求如何結束於更新天氣預報卡片的調用。應用要如何知道它是否顯示了最新的數據?這在 renderForecast()
的如下代碼中處理:
public/scripts/app.js
// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
return;
}
每次更新卡片時,應用都會將數據的時間戳存儲在卡片上的隱藏屬性中。如果卡片上已存在的時間戳比傳遞給函數的數據新,應用就什么也不做。
預先緩存我們的應用資源
在 Service Worker 中,讓我們添加一個 DATA_CACHE_NAME
以便我們可以將應用數據與應用外殼分開。更新應用外殼並清除舊緩存后,我們的數據將保持不變,仍用於超快速加載。請記住,如果你的數據格式將來發生了變化,你就需要一種方法來處理這種情況,並確保應用外殼和內容保持同步。
public/service-worker.js
// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v2';
const DATA_CACHE_NAME = 'data-cache-v1';
別忘了也要同時更新 CACHE_NAME
,我們還將更改所有的靜態資源。
為了讓本應用離線工作,我們需要預先緩存它所需的所有資源。這也有助於提升性能。該應用無需從網絡獲取所有資源,而是可以從本地緩存加載所有資源,從而消除任何網絡不穩定性。
把 FILES_TO_CACHE
數組改為如下文件列表:
public/service-worker.js
// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
'/',
'/index.html',
'/scripts/app.js',
'/scripts/install.js',
'/scripts/luxon-1.11.4.js',
'/styles/inline.css',
'/images/add.svg',
'/images/clear-day.svg',
'/images/clear-night.svg',
'/images/cloudy.svg',
'/images/fog.svg',
'/images/hail.svg',
'/images/install.svg',
'/images/partly-cloudy-day.svg',
'/images/partly-cloudy-night.svg',
'/images/rain.svg',
'/images/refresh.svg',
'/images/sleet.svg',
'/images/snow.svg',
'/images/thunderstorm.svg',
'/images/tornado.svg',
'/images/wind.svg',
];
由於我們在手動生成要緩存的文件列表,因此每當更新文件時也必須更新 CACHE_NAME
。我們可以從緩存文件列表中刪除 offline.html
,因為本應用現在具有離線工作所需的所有必要資源,不會再顯示離線頁面。
Caution:在此示例中,我們手動控制着自己的 Service Worker。每次我們更新任何靜態資源時,我們都需要重新刷新 Service Worker 並更新緩存,否則將提供舊內容。此外,當一個文件更改時,整個緩存都會無效並需要重新下載。這意味着即使修復一個簡單的單字符拼寫錯誤也將使緩存無效並要求再次下載所有內容 - 效率不夠高。 Workbox 能優雅地處理它,通過將其集成到你的構建過程中,只有已更改的文件才需要更新,為用戶節省了帶寬並讓你更輕松地進行維護!
更新 activate 事件處理程序
為了防止我們 activate
事件不小心刪除數據,在 service-worker.js
的 activate
事件,把 if (key !== CACHE_NAME) {
改為:
public / service-worker.js
if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {
更新 fetch 事件處理程序
我們需要修改 Service Worker 以攔截對 weather API 的請求並將其響應存儲在緩存中,以便我們以后可以輕松地訪問它們。在 stale-while-revalidate 策略中,我們希望把網絡響應作為“真相之源”,始終由它向我們提供最新信息。如果不能,也可以失敗,因為我們已經在應用中檢索到了最新的緩存數據。
更新 fetch
事件處理程序以便和其它對數據 API 的請求分開。
public/service-worker.js
// CODELAB: Add fetch event handler here.
if (evt.request.url.includes('/forecast/')) {
console.log('[Service Worker] Fetch (data)', evt.request.url);
evt.respondWith(
caches.open(DATA_CACHE_NAME).then((cache) => {
return fetch(evt.request)
.then((response) => {
// If the response was good, clone it and store it in the cache.
if (response.status === 200) {
cache.put(evt.request.url, response.clone());
}
return response;
}).catch((err) => {
// Network request failed, try to get it from the cache.
return cache.match(evt.request);
});
}));
return;
}
evt.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(evt.request)
.then((response) => {
return response || fetch(evt.request);
});
})
);
該代碼攔截請求並檢查它是否用於天氣預報。如果是,請使用 fetch
發出請求。一旦返回了響應,就打開緩存,克隆響應,將其存儲在緩存中,然后將響應返回給原始請求者。
我們需要刪除 evt.request.mode !== 'navigate'
檢查,因為我們希望這個 Service Worker 處理所有請求(包括圖像,腳本,CSS 文件等),而不僅僅是導航。如果我們留着這個檢查,則只會從 Service Worker 緩存中提供 HTML,其它所有內容都將從網絡請求。
試一試
該應用現在應該能完全離線工作。刷新頁面以確保你已安裝最新的 Service Worker ,然后保存幾個城市並按應用上的刷新按鈕以獲取新的天氣數據。
然后轉到 DevTools 的 Application 面板上的 Cache Storage 窗格。展開該部分,你應該會在左側看到靜態緩存和數據緩存的名稱。打開數據緩存會顯示為每個城市存儲的數據。
然后,打開 DevTools 並切換到 Service Workers 窗格,選中 Offline 復選框,然后嘗試重新加載頁面,然后離線並重新加載頁面。
如果你是一個快速的網絡上,並希望看看天氣預報數據會如何在慢速連接上更新,請在 server.js
中把 FORECAST_DELAY
設置為 5000
。這樣對天氣預報 API 的所有請求都將延遲 5000 毫秒。
用 Lighthouse 驗證更改
再次運行 Lighthouse 是個好主意。
SEO 審計
*✅通過: 文檔已有元描述。
漸進式應用審計
*✅通過: 在離線時當前頁面未給出 200 響應。 *✅通過: 在離線時 start_url
未給出 200 響應。 *✅通過: 未注冊用來控制頁面和 start_url
的 Service Worker。 *✅通過: Web 應用清單符合可安裝性要求。 *✅通過: 已配置自定義閃屏。 *✅通過: 已設置地址欄的主題顏色。
添加安裝體驗
安裝漸進式 Web 應用后,其外觀和行為會與所有其它已安裝的應用類似。它與其它應用啟動時的位置相同。它在沒有地址欄或其它瀏覽器 UI 的應用中運行。與所有其它已安裝的應用一樣,它是任務切換器中的頂級應用。
在 Chrome 中,可以通過其 "三點菜單" 來安裝漸進式 Web 應用,也可以向用戶提供按鈕或其它 UI 組件,以提示他們安裝你的應用。
Success: 由於 Chrome 的 "三點菜單" 的安裝體驗不夠顯眼,我們建議你在應用中提供一些指示以通知用戶你的應用可以安裝,並使用安裝按鈕來完成安裝過程。
用 Lighthouse 進行審計
為了使用戶能夠安裝漸進式 Web 應用,它需要滿足一些條件 。最簡單的方法是使用 Lighthouse 來確保它符合可安裝的標准。
如果你使用此 codelab,你的 PWA 就已經符合這些標准了。
Key Point:對於本節,在 DevTools 的 Application 面板的 Service Workers 窗格中啟用 Bypass for network 復選框。選中后,請求將繞過 Service Worker 並直接發送到網絡。這簡化了我們的開發過程,因為我們在完成本節中的任務時不必更新我們的 Service Worker 。
將 install.js 添加到 index.html
首先,讓我們將 install.js
添加到 index.html
文件中。
public/index.html
<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>
監聽 beforeinstallprompt
事件
如果符合添加到主屏幕條件 ,Chrome 將觸發 beforeinstallprompt
事件,你可以使用該事件指示你的應用可以“安裝”,然后提示用戶安裝它。添加如下代碼以監聽 beforeinstallprompt
事件:
public/scripts/install.js
// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);
保存事件並顯示安裝按鈕
在我們的 saveBeforeInstallPromptEvent
函數中,我們將保存對 beforeinstallprompt
事件的引用,以便我們稍后可以調用它的 prompt()
,並修改我們的 UI 以顯示安裝按鈕。
public/scripts/install.js
// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');
顯示 "提示/隱藏" 按鈕
當用戶單擊安裝按鈕時,我們需要調用保存的 beforeinstallprompt
事件的 .prompt()
函數。我們還需要隱藏安裝按鈕,因為 .prompt()
只能在每個保存的事件上調用一次。
public/scripts/install.js
// CODELAB: Add code show install prompt & hide the install button.
deferredInstallPrompt.prompt();
// Hide the install button, it can't be called twice.
evt.srcElement.setAttribute('hidden', true);
調用 .prompt()
將向用戶顯示模態對話框,請他們將你的應用添加到主屏幕。
記錄結果
你可以通過監聽所保存的 beforeinstallprompt
事件的 userChoice
屬性返回的 Promise 來檢查用戶是如何響應的安裝對話框。在顯示出提示並且用戶已對其作出響應后,Promise 將返回一個具有 outcome
屬性的對象。
public/scripts/install.js
// CODELAB: Log user response to prompt.
deferredInstallPrompt.userChoice
.then((choice) => {
if (choice.outcome === 'accepted') {
console.log('User accepted the A2HS prompt', choice);
} else {
console.log('User dismissed the A2HS prompt', choice);
}
deferredInstallPrompt = null;
});
對 userChoice
的一個說明, 規范中把它定義成了屬性 ,而不是你所期望的函數。
記錄所有安裝事件
除了你所添加的用於安裝應用的任何 UI 之外,用戶還可以通過其它方法安裝 PWA,例如 Chrome 的 "三點菜單"。要跟蹤這些事件,請監聽 appinstalled 事件。
public/scripts/install.js
// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);
然后,我們需要修改 logAppInstalled
函數,對於這個 codelab,我們只用了 console.log
,但在生產應用中,你可能希望將其作為事件記錄在你的分析軟件中。
public/scripts/install.js
// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);
更新 Service Worker
不要忘記修改 service-worker.js
文件中的 CACHE_NAME
,因為你對已緩存的文件做了些更改。在 DevTools 的 “Application” 面板上的 “Service Worker” 窗格中啟用 Bypass for network 復選框只在開發中有用,在現實世界中是無濟於事的。
試一試
讓我們看看我們的安裝步驟是如何進行的。為了安全起見,使用 DevTools 應用面板中的 Clear site storage 按鈕清除所有內容以確保我們從頭開始。如果你之前安裝過該應用,請務必將其卸載,否則安裝圖標將不會再次顯示。
驗證安裝按鈕是否可見
首先,驗證我們的安裝圖標是否正確顯示了,請務必同時在桌面和移動設備上試用。
- 在新的 Chrome 標簽頁中打開網址。
- 打開 Chrome 的 "三點菜單"(地址欄旁邊)。 ▢驗證你在菜單中是否看到了 “Install Weather...”。
- 使用右上角的刷新按鈕刷新天氣數據,以確保我們符合user engagement heuristics 。 ▢確認應用標題中顯示了安裝圖標。
驗證安裝按鈕是否有效
接下來,讓我們確保一切都安裝正確,並正確觸發了我們的事件。你可以在桌面設備或移動設備上執行此操作。如果你想在移動設備上進行測試,請確保使用遠程調試,以便查看控制台中的日志。
- 打開 Chrome,然后在新的瀏覽器標簽中,導航到你的 Weather PWA。
- 打開 DevTools 並切換到 “Console” 窗格。
- 單擊右上角的安裝按鈕。 ▢確認安裝按鈕消失了 ▢確認顯示了安裝模態對話框。
- 單擊 “Cancel”。 ▢確認 “User dismissed the A2HS prompt” 顯示在控制台輸出中。 ▢確認重新出現了安裝按鈕。
- 再次單擊安裝按鈕,然后單擊模態對話框中的安裝按鈕。 ▢確認 “User accepted the A2HS prompt” 顯示在了控制台輸出中。 ▢確認 “Weather App was installed” 顯示在了控制台輸出中。 ▢確認天氣應用已添加到你查找應用的典型位置。
- 啟動 Weather PWA。 ▢確認應用是作為獨立應用打開的,可以在桌面上的應用窗口中,也可以在移動設備上全屏顯示。
請注意,如果你從 localhost 在桌面上運行,則你安裝的 PWA 可能會顯示地址標題,因為 localhost 不認為是安全主機。
驗證 iOS 安裝是否正常
我們還要檢查它在 iOS 上的行為。如果你有 iOS 設備,可以直接使用它;或者如果你使用的是 Mac,可以嘗試使用 Xcode 提供的 iOS 模擬器。
- 打開 Safari,在新的瀏覽器選項卡中,導航到 Weather PWA。
- 單擊 Share
按鈕。
- 向右滾動並單擊 Add to Home Screen 按鈕。 ▢驗證標題、URL 和圖標是否正確。
- 單擊 Add。 ▢確認應用圖標已添加到了主屏幕。
- 從主屏幕啟動 Weather PWA。 ▢確認應用全屏啟動。
額外工作: 檢測你的應用是否從主屏幕啟動了
媒體查詢 display-mode
可以根據應用的啟動方式來應用樣式,或者使用 JavaScript 來判定它是如何啟動的。
@media all and (display-mode: standalone) {
body {
background-color: yellow;
}
}
你還可以在 JavaScript 中檢測它是否運行在獨立模式下來檢查這個 display-mode
媒體查詢 。
額外工作: 卸載你的 PWA
請記住,如果已經安裝了應用,則 beforeinstallevent
不會觸發,因此在開發期間,你可能需要多次安裝和卸載應用,以確保一切正常運行。
Android
在 Android 上,卸載 PWA 的方式與卸載其它已安裝的應用的方式相同。
- 打開應用抽屜。
- 向下滾動以查找天氣圖標。
- 將應用圖標拖到屏幕頂部。
- 選擇卸載。
ChromeOS
在 ChromeOS 上,可以從啟動器搜索框輕松卸載 PWA。
- 打開啟動器。
- 在搜索框中輸入“Weather”,你的 Weather PWA 應該出現在搜索結果中。
- 右鍵單擊(按住 alt 鍵單擊)Weather PWA。
- 點擊 從 Chrome 中刪除...
macOS 和 Windows
在 Mac 和 Windows 上,必須通過 Chrome 卸載 PWA。
- 在新的瀏覽器標簽中,打開 chrome://apps。
- 右鍵單擊(按住 alt 鍵單擊)Weather PWA。
- 點擊從 Chrome 中刪除...
恭喜
恭喜,你已經成功構建了第一個漸進式 Web 應用!
你添加了一個 Web 應用清單以使其能夠安裝,並添加了一個 Service Worker 以確保你的 PWA 始終快速而且可靠。你學習了如何使用 DevTools 審計應用以及如何用它幫你改善用戶體驗。
你現在知道了將任何 Web 應用轉換為漸進式 Web 應用所需的關鍵步驟。