Progressive Web Apps (PWA) 的離線優先策略是通過Service Worker和Cache API實現的,它允許在沒有網絡連接時仍然可以訪問網站的部分或全部內容。
1. 創建Service Worker註冊文件(service-worker.js):
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache-v1').then((cache) => {
return cache.addAll([
'/index.html',
'/style.css',
'/script.js',
// 添加其他需要預緩存的文件路徑
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request).then((networkResponse) => {
caches.open('my-cache-v1').then((cache) => {
cache.put(event.request.url, networkResponse.clone());
});
return networkResponse;
}).catch(() => {
// 如果所有嘗試都失敗,可以返回一個備用響應,比如錯誤頁面
return caches.match('/offline.html');
});
})
);
});
2. 註冊Service Worker:
在你的主應用中註冊Service Worker:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then((registration) => {
console.log('Service Worker registered:', registration);
})
.catch((error) => {
console.error('Service Worker registration failed:', error);
});
});
}
3. 更新策略:
當有新版本的應用時,需要更新Service Worker和緩存內容。可以在Service Worker中監聽activate事件:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.filter((cacheName) => cacheName !== 'my-cache-v1').map((cacheName) => caches.delete(cacheName))
);
})
);
});
4. 更新Service Worker:
更新Service Worker時,需要改變Service Worker文件名(如增加版本號),這樣瀏覽器會認為這是新的SW並觸發安裝過程。
5. 更新Service Worker生命週期管理:
確保在Service Worker更新時,舊版本的Service Worker不會影響用户體驗。通常,你可能希望舊版本Service Worker完成所有請求後再關閉:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
6. 配置manifest文件:
創建一個manifest.json文件,定義應用的元數據和離線圖標:
{
"short_name": "My App",
"name": "My Awesome Progressive Web App",
"icons": [
{
"src": "icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000"
}
在HTML中引用manifest文件:
<link rel="manifest" href="/manifest.json">
7. 離線通知和重新加載提示
當用户離線後重新上線時,可以通過Service Worker發送通知提醒用户重新加載頁面以獲取更新內容:
self.addEventListener('online', (event) => {
clients.matchAll({ type: 'window' }).then((clients) => {
clients.forEach((client) => {
client.postMessage({ type: 'RELOAD' });
});
});
});
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'RELOAD') {
clients.matchAll({ type: 'window' }).then((clients) => {
clients.forEach((client) => {
if (client.url === self.registration.scope && 'focus' in client) {
client.focus();
client.reload();
}
});
});
}
});
在主應用中監聽消息:
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data && event.data.type === 'RELOAD') {
alert('網絡已恢復,刷新頁面獲取最新內容。');
location.reload();
}
});
8. 離線提示和體驗
當用户離線時,提供友好的離線頁面或提示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>離線頁面</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f2f2f2;
}
h1 {
font-size: 2rem;
color: #333;
}
</style>
</head>
<body>
<h1>您已離線,稍後再試。</h1>
</body>
</html>
然後在Service Worker的fetch事件中處理:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
// 檢查網絡請求失敗的情況
return fetch(event.request).catch(() => {
// 返回離線頁面
return caches.match('/offline.html');
});
})
);
});
9. 更新緩存策略
有時你可能希望緩存特定版本的資源,而不是始終使用最新的。這可以通過在Service Worker中添加版本控制來實現:
const CACHE_NAME = 'my-cache-v2';
const urlsToCache = [
// ...
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request).then((networkResponse) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request.url, networkResponse.clone());
});
return networkResponse;
});
})
);
});
10. 使用App Shell架構
App Shell模型是一種常見的PWA設計模式,它提供一個基本的用户界面框架,即使在離線狀態下也能加載。App Shell通常包括導航、頭部、側邊欄等非動態內容,這樣即使在離線時,用户也能看到應用的基本結構。
首先,創建一個App Shell HTML文件(如app-shell.html),包含基本的佈局和樣式。然後,在Service Worker中預緩存App Shell:
const appShellUrls = [
'/app-shell.html',
'/app-style.css',
// 其他App Shell相關的資源
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('app-shell-cache').then((cache) => {
return cache.addAll(appShellUrls);
})
);
});
在fetch事件中,優先從緩存中獲取App Shell資源:
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(caches.match('/app-shell.html'));
} else {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
}
});
11. 使用Service Worker攔截網絡請求
Service Worker還可以用於攔截特定類型的網絡請求,例如API調用。這使得你可以在離線時返回默認值或存儲的響應,以提供一致的用户體驗:
self.addEventListener('fetch', (event) => {
if (event.request.url.startsWith('https://api.example.com')) {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request).then((networkResponse) => {
caches.open('api-cache').then((cache) => {
cache.put(event.request.url, networkResponse.clone());
});
return networkResponse;
});
})
);
} else {
// 處理其他非API請求
}
});
12. 集成WebSocket支持
如果你的應用使用WebSocket進行實時通信,可以使用workbox-websocket庫在Service Worker中處理WebSocket連接,確保在離線時能夠接收和發送消息:
importScripts('https://unpkg.com/workbox-sw@latest/runtime-caching/workbox-sw.prod.v2.js');
importScripts('https://unpkg.com/workbox-websocket@latest/workbox-websocket.prod.v2.js');
workbox.webSocket.register('wss://your-websocket-endpoint.com', {
onConnect: (client) => {
console.log('WebSocket connected:', client);
},
onClose: (client) => {
console.log('WebSocket disconnected:', client);
},
});
13. 測試和監控
確保在不同網絡條件下測試你的PWA,包括2G、3G和離線狀態。可以使用Chrome開發者工具的模擬網絡條件功能。同時,使用Lighthouse等工具定期評估PWA的性能和離線體驗。
14. 總結
通過這些策略,可以創建一個高度可用且用户體驗優秀的PWA,即使在離線或弱網絡環境下也能正常工作。PWA的目標是提供接近原生應用的體驗,因此持續優化和測試是關鍵。