這個程式碼研究室是 Google Developers 訓練團隊開發的「開發漸進式網頁應用程式」訓練課程的一部分。建議您依序完成程式碼研究室,充分體驗本課程的價值。
如要瞭解課程的完整詳細資料,請參閱開發漸進式網頁應用程式總覽。
簡介
在本實驗室中,您將使用 Lighthouse 稽核網站,確認是否符合漸進式網頁應用程式 (PWA) 標準。您也會使用 Service Worker API 新增離線功能。
課程內容
- 如何使用 Lighthouse 稽核網站
- 如何為應用程式新增離線功能
注意事項
- HTML、CSS 和 JavaScript 基礎知識
- 熟悉 ES2015 Promise
軟硬體需求
- 可存取終端機/殼層的電腦
- 網際網路連線
- Chrome 瀏覽器 (用於 Lighthouse)
- 文字編輯器
- 選用:Android 裝置上的 Chrome
從 GitHub 下載或複製 pwa-training-labs 存放區,並視需要安裝 Node.js 的 LTS 版本。
前往 offline-quickstart-lab/app/
目錄,然後啟動本機開發伺服器:
cd offline-quickstart-lab/app npm install node server.js
你隨時可以按下 Ctrl-c
終止伺服器。
開啟瀏覽器並前往 localhost:8081/
。您應該會看到網站是簡單的靜態網頁。
注意:取消註冊所有 Service Worker,並清除 localhost 的所有 Service Worker 快取,以免干擾實驗室。在 Chrome 開發人員工具中,按一下「應用程式」分頁的「清除儲存空間」部分中的「清除網站資料」,即可達成這個目標。
使用偏好的文字編輯器開啟 offline-quickstart-lab/app/
資料夾。您將在 app/
資料夾中建構實驗室。
這個資料夾包含:
images/
資料夾包含範例圖片styles/main.css
是主要樣式表index.html
是我們範例網站的主要 HTML 網頁package-lock.json
和package.json
追蹤應用程式依附元件 (在本例中,依附元件僅適用於本機開發伺服器)server.js
是用於測試的本機開發伺服器service-worker.js
是服務工作人員檔案 (目前為空白)
開始變更網站前,請先使用 Lighthouse 進行稽核,瞭解有哪些改善空間。
返回應用程式 (在 Chrome 中),然後開啟「開發人員工具」的「稽核」分頁標籤。您應該會看到 Lighthouse 圖示和設定選項。選取「行動裝置」做為「裝置」,選取所有「稽核」,選取任一「節流」選項,然後選擇「清除儲存空間」:
按一下「執行稽核」。稽核作業需要一些時間才能完成。
說明
稽核完成後,您應該會在開發人員工具中看到包含分數的報表。系統應會顯示分數,如下所示 (分數可能不完全相同):
注意:Lighthouse 分數是近似值,可能會受到環境影響 (例如開啟大量瀏覽器視窗)。您的分數可能與這裡顯示的分數不同。
「漸進式網頁應用程式」部分應如下所示:
這份報表提供五個類別的分數和指標:
- 漸進式網頁應用程式
- 成效
- 無障礙設定
- 最佳做法
- 搜尋引擎最佳化 (SEO)
如您所見,我們的應用程式在漸進式網頁應用程式 (PWA) 類別中得分不佳。讓我們提高分數!
請花點時間查看報表的 PWA 部分,瞭解缺少哪些項目。
註冊 Service Worker
報告中列出的其中一項失敗原因,是沒有註冊任何 Service Worker。我們目前在 app/service-worker.js
有一個空白的 Service Worker 檔案。
在 index.html
底部,緊鄰結尾 </body>
標記之前,加入下列指令碼:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('service-worker.js')
.then(reg => {
console.log('Service worker registered! 😎', reg);
})
.catch(err => {
console.log('😥 Service worker registration failed: ', err);
});
});
}
</script>
說明
網頁載入後,這段程式碼會註冊空白的 service-worker.js
Service Worker 檔案。不過,目前的 Service Worker 檔案是空白的,不會執行任何動作。我們會在下一個步驟中新增服務程式碼。
預先快取資源
報告中列出的另一個失敗原因,是應用程式在離線時不會傳回狀態碼 200 的回應。我們需要更新服務工作人員來解決這個問題。
在服務工作人員檔案 (service-worker.js
) 中新增下列程式碼:
const cacheName = 'cache-v1';
const precacheResources = [
'/',
'index.html',
'styles/main.css',
'images/space1.jpg',
'images/space2.jpg',
'images/space3.jpg'
];
self.addEventListener('install', event => {
console.log('Service worker install event!');
event.waitUntil(
caches.open(cacheName)
.then(cache => {
return cache.addAll(precacheResources);
})
);
});
self.addEventListener('activate', event => {
console.log('Service worker activate event!');
});
self.addEventListener('fetch', event => {
console.log('Fetch intercepted for:', event.request.url);
event.respondWith(caches.match(event.request)
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request);
})
);
});
現在返回瀏覽器並重新整理網站。檢查控制台,確認服務工作人員:
- 已註冊
- 已安裝
- 已啟用
注意:如果先前已註冊 Service Worker,或無法觸發所有事件,請取消註冊所有 Service Worker 並重新整理頁面。如果失敗,請關閉所有應用程式執行個體,然後重新開啟。
接著,在指令列中執行 Ctrl + c
,終止本機開發伺服器。再次重新整理網站,你會發現即使伺服器離線,網站仍會載入!
注意:您可能會看到控制台錯誤,指出無法擷取 Service Worker:An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED
。由於瀏覽器無法擷取 Service Worker 指令碼 (因為網站離線),因此會顯示這項錯誤,但這是預期行為,因為我們無法使用 Service Worker 快取自身。否則使用者的瀏覽器會永遠停留在同一個 Service Worker!
說明
註冊指令碼在 index.html
中註冊 Service Worker 後,就會發生 Service Worker install
事件。在這個事件期間,install
事件監聽器會開啟具名快取,並使用 cache.addAll
方法快取指定檔案。這稱為「預先快取」,因為這會在 install
事件期間發生,而這通常是使用者首次造訪網站時。
安裝 Service Worker 後,如果目前沒有其他 Service Worker 控制網頁,新的 Service Worker 就會「啟用」(Service Worker 中會觸發 activate
事件監聽器),並開始控制網頁。
如果網頁要求資源時,該網頁是由已啟用的 Service Worker 控制,要求會像網路 Proxy 一樣通過 Service Worker。每項要求都會觸發 fetch
事件。在服務工作人員中,fetch
事件監聽器會搜尋快取,並在快取資源可用時傳回該資源。如果資源未快取,系統會正常要求資源。
快取資源可避免網路要求,讓應用程式離線運作。現在我們的應用程式在離線時可以傳回狀態碼 200 的回應了!
注意:在這個範例中,除了登入之外,activate 事件不會用於任何用途。加入這個事件是為了協助偵錯 Service Worker 生命週期問題。
選用:您也可以在開發人員工具的「應用程式」分頁中,展開「快取儲存空間」部分,查看快取資源:
使用 node server.js
重新啟動開發伺服器,然後重新整理網站。接著再次開啟開發人員工具中的「稽核」分頁,然後選取「新稽核」 (左上角的加號),重新執行 Lighthouse 稽核。稽核完成後,您會發現 PWA 分數大幅提升,但仍有進步空間。我們會在下一節中繼續提升分數。
注意:本節為選用內容,因為測試 Web 應用程式安裝橫幅不在本實驗室的涵蓋範圍內。您可以使用遠端偵錯自行測試。
我們的 PWA 分數仍不理想。報告中列出的其餘失敗項目包括:系統不會提示使用者安裝我們的網頁應用程式,以及我們尚未在網址列中設定啟動畫面或品牌顏色。只要符合一些額外的條件,我們就能修正這些問題,並逐步實作「加入主畫面」。最重要的是,我們需要建立資訊清單檔案。
建立資訊清單檔案
在 app/
中建立名為 manifest.json
的檔案,並加入下列程式碼:
{
"name": "Space Missions",
"short_name": "Space Missions",
"lang": "en-US",
"start_url": "/index.html",
"display": "standalone",
"theme_color": "#FF9800",
"background_color": "#FF9800",
"icons": [
{
"src": "images/touch/icon-128x128.png",
"sizes": "128x128"
},
{
"src": "images/touch/icon-192x192.png",
"sizes": "192x192"
},
{
"src": "images/touch/icon-256x256.png",
"sizes": "256x256"
},
{
"src": "images/touch/icon-384x384.png",
"sizes": "384x384"
},
{
"src": "images/touch/icon-512x512.png",
"sizes": "512x512"
}
]
}
資訊清單中參照的圖片已在應用程式中提供。
接著,在 index.html
中,將下列 HTML 新增至 <head>
標記底部:
<link rel="manifest" href="manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">
返回網站。在「開發人員工具」的「應用程式」分頁中,選取「清除儲存空間」部分,然後按一下「清除網站資料」。然後重新整理頁面。接著選取「資訊清單」專區。您應該會看到 manifest.json
檔案中設定的圖示和設定選項。如果沒有看到變更,請在無痕視窗中開啟網站,然後再次檢查。
說明
manifest.json
檔案會告知瀏覽器如何設定應用程式部分漸進式功能的樣式和格式,例如瀏覽器 Chrome、主畫面圖示和啟動畫面。您也可以使用這項功能,將網頁應用程式設定為以 standalone
模式開啟,就像原生應用程式一樣 (也就是在瀏覽器外開啟)。
撰寫本文時,部分瀏覽器的支援功能仍在開發中,而 <meta>
代碼會為尚未完全支援的特定瀏覽器設定這些功能的子集。
我們必須清除網站資料,才能移除 index.html
的舊快取版本 (因為該版本沒有資訊清單連結)。請嘗試執行另一項 Lighthouse 稽核,看看 PWA 分數提升了多少!
啟用安裝提示
安裝應用程式的下一個步驟,是向使用者顯示安裝提示。Chrome 67 會自動提示使用者,但從 Chrome 68 開始,安裝提示應會因應使用者手勢,以程式輔助方式啟動。
在 index.html
頂端 (<main>
代碼後方) 新增「安裝應用程式」按鈕和橫幅,程式碼如下:
<section id="installBanner" class="banner">
<button id="installBtn">Install app</button>
</section>
接著,在 styles/main.css
中新增下列樣式,為橫幅設定樣式:
.banner {
align-content: center;
display: none;
justify-content: center;
width: 100%;
}
儲存檔案。最後,在 index.html
中新增下列指令碼標記:
<script>
let deferredPrompt;
window.addEventListener('beforeinstallprompt', event => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
event.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = event;
// Attach the install prompt to a user gesture
document.querySelector('#installBtn').addEventListener('click', event => {
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice
.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
// Update UI notify the user they can add to home screen
document.querySelector('#installBanner').style.display = 'flex';
});
</script>
儲存檔案。在 Android 裝置上使用 遠端偵錯,在 Chrome 中開啟應用程式。網頁載入後,您應該會看到「安裝應用程式」按鈕 (電腦上不會顯示,因此請務必在行動裝置上測試)。按一下按鈕,系統應會彈出「新增至主畫面」提示。按照步驟在裝置上安裝應用程式。安裝完成後,輕觸新建立的主畫面圖示,即可在獨立模式 (瀏覽器外) 開啟網頁應用程式。
說明
HTML 和 CSS 程式碼會新增隱藏的橫幅和按鈕,方便我們讓使用者啟動安裝提示。
beforeinstallprompt
事件觸發後,我們會防止預設體驗 (Chrome 67 和更早版本會自動提示使用者安裝),並在全域 deferredPrompt
變數中擷取 beforeinstallevent
。接著設定「安裝應用程式」按鈕,顯示含有 beforeinstallevent
的 prompt()
方法的提示。使用者選擇是否安裝後,userChoice
promise 會以使用者的選擇 (outcome
) 解決。最後,一切就緒後,我們會顯示安裝按鈕。
您已瞭解如何使用 Lighthouse 稽核網站,以及如何實作離線功能的基本概念。如果你已完成選用章節,也學會如何將網頁應用程式安裝到主畫面!
其他資源
Lighthouse 是開放原始碼工具!您可以分叉、新增自己的測試,以及回報錯誤。Lighthouse 也提供指令列工具,可與建構程序整合。
如要查看 PWA 訓練課程中的所有程式碼研究室,請參閱課程的歡迎程式碼研究室。