PWA = 普通的網站 + manifest + Service Workersjavascript
manifest文件包含網站相關的信息,包括圖標,背景屏幕,顏色和默認方向。css
Service Workers爲網站提供了更好的體驗(漸進加強),容許將網站添加到設備的主屏幕,離線緩存。html
PWA應該具有的特性:java
Service Workers由JavaScript編寫,運行在瀏覽器後臺,基於事件驅動。若是用戶瀏覽器不支持Service Workers的話,並不會形成影響,網站還能夠做爲普通網站進行瀏覽,所以作到了「漸進加強」。git
經過Service Workers,能夠緩存 UI 外殼(用戶界面所必需的最小化的 HTML、CSS 和 JavaScript),動態內容在UI外殼加載後再加載,爲用戶提供相似原生app的體驗。github
從生命週期圖中能夠看出,當第一次加載頁面時,並不會有激活的 Service Worker 來控制頁面。只有當 Service Worker 安裝完成而且用戶刷新了頁面或跳轉至網站的其餘頁面,Service Worker 纔會激活並開始攔截請求。
若是須要在第一次加載時,就但願Service Workers激活並開始攔截請求,能夠經過以下方式當即激活Service Workers。web
self.addEventListener('install', function(event) { //使 Service Worker 解僱當前活動的worker, 而且一旦進入等待階段就會激活自身,觸發activate事件 event.waitUntil(self.skipWaiting()); });
結合self.clients.claim() 一塊兒使用,以確保底層 Service Worker 的更新當即生效。npm
self.addEventListener('activate', function(event) { e.waitUntil( caches.keys().then(function(keyList) { return Promise.all(keyList.map(function(key) { if (key !== cacheName) { console.log('[ServiceWorker] Removing old cache', key); return caches.delete(key); } })); }) ); return self.clients.claim(); //確保底層 Service Worker 的更新當即生效 });
var cacheKey = "first-pwa"; //緩存的key,能夠添加多個不一樣的緩存 var cacheList = [ //須要緩存的文件列表 '/', 'index.html', 'icon.png', 'main.css' ]; //在安裝過程當中緩存已知的資源 self.addEventListener('install', event => { //監聽install事件 event.waitUntil( //install完成後 caches.open(cacheKey) //打開cache .then(cache => cache.addAll(cacheList)) //將須要緩存的文件加入cache列表 .then(() => self.skipWaiting()) //使 Service Worker 解僱當前活動的worker, // 而且一旦進入等待階段就會激活自身,觸發activate事件 //無需等待用戶跳轉或刷新頁面 ); }); //攔截fetch請求 self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(response => { //若是請求的資源在緩存中 if (response != null) return response; //返回緩存資源 //經過網絡獲取資源,並緩存 var requestToCache = event.request.clone(); //克隆當前請求 return fetch(requestToCache.url).then(response => { if (!response || response.status !== 200) { return response; //返回錯誤的響應 } var responseToCache = response.clone(); //克隆響應 caches.open(cacheKey) .then(cache => { cache.put(requestToCache, responseToCache); //將響應添加到緩存中 }); return response; //返回響應 }); }) ); });
屬於Service Workers做用域範圍內的全部http請求都將觸發fetch事件,包括html、css、js、圖片等。json
若是用戶在瀏覽器中啓用了節省數據的功能,瀏覽器在每一個http請求頭部中會加入save-data請求頭。api
this.addEventListener('fetch', function (event) { if(event.request.headers.get('save-data')){ // 咱們想要節省數據,因此限制了圖標和字體 if (event.request.url.includes('fonts.googleapis.com')) { // 不返回任何內容 event.respondWith(new Promise(resolve => resolve(new Response('', { status: 417, statusText: 'Ignore fonts to save data.' }))) ); } } });
mainifest.json須要在網頁head標籤中引用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato"> <link rel="stylesheet" href="main.css"> <link rel="manifest" href="manifest.json"/> <title>PWA</title> </head> <body> <h1>Hello PWA!</h1> <script type="text/javascript"> if (navigator.serviceWorker != null) { navigator.serviceWorker.register('sw.js').then(registration => { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function (err) { console.log('ServiceWorker registration failed: ', err); }); } else { //serviceWorker is not supported } </script> </body> </html>
manifest.json中包含的字段主要包括:
{ "name": "First PWA", "short_name": "pwa", "display": "standalone", "start_url": "/index.html", "theme_color": "#FFDF00", "background_color": "#FFDF00", "orientation": "landscape", "scope": "/", "icons": [ { "src": "icon.png", "sizes": "144x144", "type": "image/png" } ] }
display:顯示模式,默認爲browser。包括fullscreen、standalone、minimal-ui 或 browser 。
參考 https://developer.mozilla.org/en-US/docs/Web/Manifest
//監聽添加到主屏幕事件 window.addEventListener('beforeinstallprompt', function (event) { // //取消添加 // e.preventDefault(); // return false; event.userChoice.then(function (result) { console.log(result.outcome); if (result.outcome == 'dismissed') { } else { } }); });
目前FireFox、Chrome、Edge 已經支持 Push API。推送的過程主要分爲三個步驟:
在訂閱前,須要先生成VAPID, VAPID是「自主應用服務器標識」 ( Voluntary Application Server Identification ) 的簡稱。它是一個規範,定義了應用服務器和推送服務之間的握手。
1.客戶端訂閱消息,此時瀏覽器會詢問用戶是否容許消息推送通知。
2.從瀏覽器獲取PushSubscription對象,其中包含了客戶端的信息,能夠理解爲標示設備的id。
var vapidPublicKey = 'BF0eSi4ANvVKr017Gr_Xzb-bN9l8-c3qRUHqVU6C-vFy_i3xgrKDY-13BPF5BVx93IVObJwnwrt5vjX-ltM6Uuo'; function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } function subscribeForPushNotification(registration) { return registration.pushManager.getSubscription() .then(function (subscription) { if (subscription) { return; } return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }) .then(function (subscription) { var rawKey = subscription.getKey ? subscription.getKey('p256dh') : ''; var key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : ''; var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : ''; var authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : ''; var endpoint = subscription.endpoint; return fetch('http://localhost:3001/api/register', { method: 'post', headers: new Headers({ 'content-type': 'application/json' }), body: JSON.stringify({ endpoint: subscription.endpoint, key: key, authSecret: authSecret, }), }); }); }); }
3.將PushSubscription發送到服務端保存。
服務端示例:
this.post('/register', 'register', async (req, res, next) => { try { let {endpoint, authSecret, key} = req.body; let subscriber = { endpoint, keys: { auth: authSecret, p256dh: key } }; subscribers.push(subscriber); res.apiSuccess({}); } catch (err) { next(err); } });
經過Web Push協議將須要推送的消息發送到push service。
使用web-push的發送示例:
this.post('/send', 'send', async (req, res, next) => { try { let message = req.body.message; for (let subscriber of subscribers) { webpush.sendNotification( subscriber, JSON.stringify({ msg:message, url:'http://localhost:3001', icon:'' }) ); } res.apiSuccess({}); } catch (err) { next(err); } });
當push service收到消息後,會將消息保存起來,直到目標設備上線後將消息推送到客戶端,或者消息超時再也不發送。
https://github.com/SangKa/PWA-Book-CN
https://developers.google.com/web/fundamentals/push-notifications/how-push-works
https://codelabs.developers.google.com/codelabs/your-first-pwapp/#0