[TOC]
PWA(Progressive Web App)是對Web頁面的一種增加技術,當前若瀏覽器支持則可以表現增強后的特性,如果不支持也不影響Web頁面的表現。目前,我們主要使用的是它的前端緩存能力。雖然PWA主要針對移動端,實際PC端頁面也可以以同樣模式來操作
配置有如下幾個步驟:
1. 頁面視口
```
<!-- PC端 -->
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
```
PC端設置禁止縮放頁面,顯示比例100%,移動端不需要配置`viewport`,因為,我們使用的移動端響應式JS會動態配置。
2. 在網站根目錄創建文件`manifest.json`:
```
{
"name": "這里寫網站的標題名稱",
"short_name": "這里寫網站標題的簡稱",
"start_url": "這里寫網站起始URL一般使用/",
"display": "這里寫網站在瀏覽器中的顯示模式一般使用standalone",
"background_color": "網站的背景顏色16進制顏色值",
"description": "這里寫對于網站的描述",
"orientation": "網站顯示的方向portrait-primary",
"theme_color": "網站的主題顏色16進制顏色值",
"icons": [ { //網站放置在桌面使用的圖標,一般只需要192和512兩個尺寸即可
"src": "/img/192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "/img/512.png",
"sizes": "512x512",
"type": "image/png"
}]
}
```
3. 在頁面頭部添加
```
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="這里寫網站UI專題色16進制顏色值">
```
4. 添加`serviceworker.js`文件
```
'use strict';
//緩存版本號,隨著頁面的更改,如果要更新緩存,請修改這里的版本號
const version = 'v20190212';
const __DEVELOPMENT__ = false;
const __DEBUG__ = false;
//配置兩個離線用資源
const offlineResources = ['/', '/offline.html', '/offline.svg'];
const ignoreFetch = [
//忽略抓取的URL或目錄,請酌情修改
/https?:\/\/xiongzhang.baidu.com\//,
/https?:\/\/ae.bdstatic.com\//,
/https?:\/\/msite.baidu.com\//,
/https?:\/\/s.bdstatic.com\//,
/https?:\/\/timg01.bdimg.com\//,
/https?:\/\/zz.bdstatic.com\//,
/https?:\/\/#\//,
/https?:\/\/jspassport.ssl.qhimg.com\//,
/https?:\/\/s.ssl.qhres.com\//,
/https?:\/\/changyan.itc.cn\//,
/https?:\/\/changyan.sohu.com\//,
/.php$/,
/more/,
];
function onInstall(event) { log('install event in progress.');
event.waitUntil(updateStaticCache()); }
function updateStaticCache() { return caches.open(cacheKey('offline')).then((cache) => { return cache.addAll(offlineResources); }).then(() => { log('installation complete!'); }); }
function onFetch(event) { const request = event.request; if (shouldAlwaysFetch(request)) { event.respondWith(networkedOrOffline(request)); return; } if (shouldFetchAndCache(request)) { event.respondWith(networkedOrCached(request)); return; } event.respondWith(cachedOrNetworked(request)); }
function networkedOrCached(request) { return networkedAndCache(request).catch(() => { return cachedOrOffline(request) }); }
function networkedAndCache(request) { return fetch(request).then((response) => { var copy = response.clone();
caches.open(cacheKey('resources')).then((cache) => { cache.put(request, copy); });
log("(network: cache write)", request.method, request.url); return response; }); }
function cachedOrNetworked(request) { return caches.match(request).then((response) => { log(response ? '(cached)' : '(network: cache miss)', request.method, request.url); return response || networkedAndCache(request).catch(() => { return offlineResponse(request) }); }); }
function networkedOrOffline(request) { return fetch(request).then((response) => { log('(network)', request.method, request.url); return response; }).catch(() => { return offlineResponse(request); }); }
function cachedOrOffline(request) { return caches.match(request).then((response) => { return response || offlineResponse(request); }); }
function offlineResponse(request) { log('(offline)', request.method, request.url); if (request.url.match(/\.(jpg|png|gif|svg|jpeg)(\?.*)?$/)) { return caches.match('/offline.svg'); } else { return caches.match('/offline.html'); } }
function onActivate(event) { log('activate event in progress.');
event.waitUntil(removeOldCache()); }
function removeOldCache() { return caches.keys().then((keys) => { return Promise.all(keys.filter((key) => { return !key.startsWith(version); }).map((key) => { return caches.delete(key); })); }).then(() => { log('removeOldCache completed.'); }); }
function cacheKey() { return [version, ...arguments].join(':'); }
function log() { if (developmentMode()) { console.log("SW:", ...arguments); } }
function shouldAlwaysFetch(request) { return __DEVELOPMENT__ || request.method !== 'GET' || ignoreFetch.some(regex => request.url.match(regex)); }
function shouldFetchAndCache(request) { return ~request.headers.get('Accept').indexOf('text/html'); }
function developmentMode() { return __DEVELOPMENT__ || __DEBUG__; } log("Hello from ServiceWorker land!", version);
self.addEventListener('install', onInstall);
self.addEventListener('fetch', onFetch);
self.addEventListener("activate", onActivate);
```
以上文件命名為`serviceworker.js`并保持在網站根目錄
5.配置`serviceworker.js`加載,將serviceworker的加載配置內容添加到網站公共JS內:
```
// 延遲注冊serviceWorker
window.addEventListener('load', function() {
if('serviceWorker' in navigator){
navigator.serviceWorker.register('/serviceworker.js').then(function (registration) {
console.log('Service Worker Registered,register script: serviceworker.js.');
}).catch(function (error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
});
```
一旦以上的內容全部搞點,可以通過google的`lighthouse`檢查一下,如果serviceworker發生作用,則可以在瀏覽器查看:

6. 善后優化工作
為了讓`serviceworker.js`能保持最新,需要在服務器配置(nginx)禁止瀏覽器緩存這個文件:
```
location ~* (serviceworker.js)$ {
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
}
```
另外,結合nginx的push能力,nginx應當主動push倆文件:
```
http2_push /serviceworker.js;
http2_push /manifest.json;
```