更新中javascript
提示:在測試程序的時候儘可能使用Chrome的隱身模式,確保 Service Worker 不會從之前的殘留狀態中讀取數據!!css
在sudo ng new pwa
新建工程以後,在工程的根目錄上運行sudo ng add @angular/pwa
,此時就會自動添加Service Worker文件,Manifest.json文件和各類不一樣尺寸的icon文件。 Angular PWA中文網傳送門html
在app.component.ts
中引入import { SwUpdate } from '@angular/service-worker';
來加載SW的更新模塊,每次PWA程序有更新均可以在這裏使用SwUpdate模塊獲取更新,並使用以下代碼可實現程序的更新操做:java
export class AppComponent {
update: boolean;
constructor(updates: SwUpdate, private data: DataService) {
updates.available.subscribe( event => {
this.update = true;
updates.activateUpdate().then(() =>
document.location.reload()
);
}
);
}
title = 'PWA';
}
複製代碼
SwUpdate文檔傳送門node
而後在html中使用一個*ngIf
來判斷是否更新,(是則顯示text,不是則不顯示):web
<span *ngIf="update">There's an update associated with your progressive web application!</span>
複製代碼
每次更新了程序都要從新build production程序,在根目錄上運行sudo ng build --prod
,而後進入cd dist/PWA
,最後運行http-server -o
在服務器上運行更新後的程序。npm
因爲 ng serve
對 Service Worker
無效,因此必須用一個獨立的 HTTP 服務器在本地測試項目。 可使用任何 HTTP 服務器,我使用的是來自 npm 中的 http-server 包。固然也能夠自定義端口以防止port衝突:json
http-server -p 8080 -c-1 dist/<project-name>
複製代碼
當使用http-server
打開服務器後,卻沒法正常打開網頁的時候,我曾遇到過ERR_INVALID_REDIRECT
這樣的問題致使沒法正常顯示網頁。更換http-server
版本就能夠解決這個問題: npm install -g http-server@0.9.0
。後端
注意: 若是想按期更新PWA,也就是使用interval建立一個週期輪詢方法,須要先讓應用註冊Aervice worker的進程進入穩定狀態,再讓它開始執行輪詢的過程,若是不斷輪詢更新(好比調用 interval())將阻止應用程序達到穩定態,也就永遠不會往瀏覽器中註冊 ServiceWorker 腳本。另外:應用中所執行的各類輪詢都會阻止它達到穩定態api
constructor(appRef: ApplicationRef, updates: SwUpdate) {
// Allow the app to stabilize first, before starting polling for updates with `interval()`.
const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
const everySixHours$ = interval(6 * 60 * 60 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
}
複製代碼
因此對於自動更新模塊的使用總結:
constructor(appRef: ApplicationRef, updates: SwUpdate, private data: DataService) {
const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
const everySixHours$ = interval(6 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
everySixHoursOnceAppIsStable$.subscribe(() => {
updates.checkForUpdate();
// console.log('check update in Service Worker');
});
updates.available.subscribe(event => {
console.log('gotta new version here', event.available);
updates.activateUpdate().then(() => document.location.reload());
});
}
複製代碼
每6秒檢測一次更新版本,若是沒有updates.activateUpdate().then(() => document.location.reload());
則只是在檢測到新版本時候提醒並不刷新並更新程序。 測試的時候須要從新ng build --prod
而後http-server -p 8080 -c-1 dist/PWA
從新運行http服務器,這時候在原來的頁面上的console上就會發現出現了新版本的提醒。
(其實每次運行build命令都會出現版本更新不管是否更改代碼,當應用的一個新的構建發佈時,Service Worker 就把它看作此應用的一個新版本,版本是由 ngsw.json 文件的內容決定的,包含了全部已知內容的哈希值。 若是任何一個被緩存的文件發生了變化,則該文件的哈希也將在ngsw.json中隨之變化,從而致使 Angular Service Worker 將這個活動文件的集合視爲一個新版本)
全在nsgw-config.json
文件中定義PWA緩存,好比想緩存google的Montserrat字體和API地址,該文件中全部的代碼形式都是glob格式,也就是:
好比:
在實際代碼中這樣作:
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
複製代碼
在已經被建立的assetGroups
中添加:
"urls": [
"https://fonts.googleapis.com/**"
]
複製代碼
AssetGroup遵循的TypeScript接口規則爲:
interface AssetGroup {
name: string;
installMode?: 'prefetch' | 'lazy';
// prefetch 告訴 Angular Service Worker 在緩存當前版本的應用時要獲取每個列出的資源。 這是個帶寬密集型的模式,但能夠確保這些資源在請求時可用,即便瀏覽器正處於離線狀態
// lazy 不會預先緩存任何資源。相反,Angular Service Worker 只會緩存它收到請求的資源。 這是一種按需緩存模式。永遠不會請求的資源也永遠不會被緩存。 這對於像爲不一樣分辨率提供的圖片之類的資源頗有用,那樣 Service Worker 就只會爲特定的屏幕和設備方向緩存正確的資源。
updateMode?: 'prefetch' | 'lazy';
// prefetch 會告訴 Service Worker 當即下載並緩存更新過的資源
// lazy 告訴 Service Worker 不要緩存這些資源,而是先把它們看做未被請求的,等到它們再次被請求時才進行更新。
lazy 這個 updateMode 只有在 installMode 也一樣是 lazy 時纔有效。
resources: {
files?: string[];
/** @deprecated As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead. */
versionedFiles?: string[];
urls?: string[];
};
}
複製代碼
在下方建立dataGroups
緩存API地址:
"dataGroups": [
{
"name": "jokes-api",
"urls": [
"https://api.chucknorris.io/jokes/random"
],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 20,
"maxAge": "1h",
"timeout": "5s"
}
}
]
複製代碼
dataGroups的配置遵循下面的接口:
export interface DataGroup {
name: string;
urls: string[];
version?: number;
cacheConfig: {
maxSize: number;
maxAge: string;
timeout?: string;
strategy?: 'freshness' | 'performance';
};
}
複製代碼
其中的緩存設置中的幾個項目分別是:
測試push notification API的功能沒法在隱身模式下測試
npm install web-push -g
,而後建立VAPID key: web-push generate-vapid-keys --json
。得到相似以下的VAPID:{
"publicKey":"BApAO10ISTLAR1bWho_6f4yL5-5z2RWHgnkqzG7SB81WdcsLkDdxrc1iWwHZ49trIUFekIEFGyBjomxjuKDZGc8",
"privateKey":"7y1-NPiG_igcck_iIJ5sidurBa7ghC4Py0MTQPOFLGM"
}
複製代碼
subscribeToNotifications() {
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
}) // 瀏覽器彈出消息請求,若是請求贊成會得到一個Promise
.then(sub => this.newsletterService.addPushSubscriber(sub).subscribe()) // 這裏會得到一個PushSubscription object
.catch(err => console.error("Could not subscribe to notifications", err));
}
複製代碼
PushSubscription object:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/cbx2QC6AGbY:APA91bEjTzUxaBU7j-YN7ReiXV-MD-bmk2pGsp9ZVq4Jj0yuBOhFRrUS9pjz5FMnIvUenVqNpALTh5Hng7HRQpcUNQMFblTLTF7aw-yu1dGqhBOJ-U3IBfnw3hz9hq-TJ4K5f9fHLvjY",
"expirationTime": null,
"keys": {
"p256dh": "BOXYnlKnMkzlMc6xlIjD8OmqVh-YqswZdut2M7zoAspl1UkFeQgSLYZ7eKqKcx6xMsGK7aAguQbcG9FMmlDrDIA=",
"auth": "if-YFywyb4g-bFB1hO9WMw=="
}
}
複製代碼
const notificationPayload = {
"notification": {
"title": "Angular News",
"body": "Newsletter Available!",
"icon": "assets/main-page-logo-small-hat.png",
"vibrate": [100, 50, 100],
"data": {
"dateOfArrival": Date.now(),
"primaryKey": 1
},
"actions": [{
"action": "explore",
"title": "Go to the site"
}]
}
};
複製代碼
Promise.all(allSubscriptions.map(sub => webpush.sendNotification(
sub, JSON.stringify(notificationPayload) )))
.then(() => res.status(200).json({message: 'Newsletter sent successfully.'}))
.catch(err => {
console.error("Error sending notification, reason: ", err);
res.sendStatus(500);
});
複製代碼
本身定義的話須要本身創建兩個新的文件:sw-custom.js
和 sw-master.js
。
sw-custom.js
裏面定義咱們想添加的listener,好比:(function () {
'use strict';
self.addEventListener('notificationclick', (event) => {
event.notification.close();
console.log('notification details: ', event.notification);
});
}());
複製代碼
sw-master.js
用於將sw-custom.js
和ngsw-worker.js
兩個Service Worker文件結合,不丟失原有功能:importScripts('./ngsw-worker.js');
importScripts('./sw-custom.js');
複製代碼
文件創建好了以後須要讓Angular在初始化渲染的時候可以將咱們自定義的文件包含進去,因此在angular.json
文件中的assets部分添加"src/sw-master.js"
。最後在app.module.ts
中註冊service worker的地方註冊咱們的新service worker文件:
ServiceWorkerModule.register('/sw-master.js', { enabled: environment.production })
複製代碼
煩人的一點:由於Angular封裝好的 ngsw-worker.js 只能在 ng build --prod以後建立的dist文件夾中,因此不build就無法使用service worker。這就是爲何測試PWA的Service Worker功能時候無法在ng serve上運行。因此若是咱們在測試本身的功能的時候,爲了不麻煩能夠將ServiceWorkerModule.register('/sw-master.js', { enabled: environment.production })
註釋掉,換成ServiceWorkerModule.register('sw-custom.js')
,這時候咱們無法使用原來的那些Angular封裝好的功能可是能夠測試咱們本身的listener。
解決方法: 在environment.ts
和environment.prod.ts
文件中添加新的環境變量,這樣就能夠在不同的環境下運行不一樣的Service Worker依賴包。
environment.ts
中添加: serviceWorkerScript: 'sw-custom.js'
environment.prod.ts
中添加: serviceWorkerScript: 'sw-master.js'
ServiceWorkerModule.register(environment.serviceWorkerScript)