[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?:\/\/hm.baidu.com\//,
/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发生作用,则可以在浏览器查看:
![](https://box.kancloud.cn/dee76f6d571704de111c04eb2d93dc0e_851x460.jpg)
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;
```