本篇主要分享PWA在客路旅行实践经验:
- PWA简介
- 本地 https 调试
- PWA 配置
- 线上部署
- PWA 更新
- PWA 降级处理
主要技术栈:
- vue
- PWA
- webpack
- nginx
本方案更适合已经是SPA架构,想要升级PWA的项目,(service-worker.js后面简称sw)
PWA简介
离线应用
- 一般前端加载优化模板以及资源缓存在用户就近的CDN(对比电商物流前置仓)
- 引入PWA后包括模板的资源直接本地缓存(对比用户家里的售卖机)
可以去这里 outweb.io/ 感受下PWA应用,会看到国内很多出名的站点也陆续升级PWA了
PWA 调试
配置https本地环境利于pwa项目的调试,
本地证书生成可以参考mkcert。
nginx配置:
listen 443 ssl;
server_name localhost2;
ssl_certificate /Users/liuze/localhost2.pem;
ssl_certificate_key /Users/liuze/localhost2-key.pem;
这边是SPA项目,nginx主要做静态资源输出,更多关于服务端配置可参考vue 服务端配置:
location @ipad-index {
add_header Cache-Control no-cache;
expires 0;
root /Users/liuze/workspace/klook-fe/klook-gds-hotel-ipad/dist/;
try_files /index.html =404;
}
location /ipad {
alias /Users/liuze/workspace/klook-fe/klook-gds-hotel-ipad/dist/;
autoindex on;
try_files $uri @ipad-index;
}
location /pwa-check { #检测是否降级处理PWA
proxy_pass http://127.0.0.1:3008;
proxy_pass_request_headers on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
(注意在 hosts 添加 127.0.0.1 localhost2 映射)
PWA配置
配置主要基于 workbox-webpack-plugin 3.6.3/workbox-sw.js,选择的原因如下:
- 相对原生,极大简单化了配置
- 从2018年开始下载量一路飙升到现在,npm trends
- 支持离线GA
主要配置基本如下,后面会分析到:
workboxOptions: {
importScripts:['./sw-prefile.js'],//插入生成的service-worker.js中
runtimeCaching: [{
urlPattern: /v1/,
handler: "networkFirst",
options: {
cacheName: "klook-gds-hotel-ipad-static",
expiration: {
maxEntries: 50,
purgeOnQuotaError: true,
maxAgeSeconds: 60 * 60 * 24,
},
cacheableResponse: {
statuses: [0, 200]
}
}
}],
clientsClaim: true, //新的service-worker 将自己设置为controller and triggers a "controllerchange" event
offlineGoogleAnalytics: true,
navigateFallback: '/ipad/index.html',
directoryIndex: '/ipad/index.html'
}
workbox-webpack-plugin主要提供两种模式:
-
GenerateSW 模式根据配置生成sw文件,适用场景:
- sw只是涉及到简单配置
- 不涉及Web Push
-
InjectManifest 模式通过既有sw文件再加工,适用场景;
- 涉及Web Push
- 更复杂的自定义配置
这里使用的GenerateSW模式;
线上部署
线上部署与本地调试配置类似,除了根据部署项目静态资源build目录来调整nginx指向外,还需要进行证书替换
PWA更新
PWA控制页面,更新不当很容易导致重大页面错误;
这里选择用户主动更新方式;
- 监听sw的更新状况
- 如有更新就自动触发sw时,同时发送自定义事件
- 自定义事件被触发,显示更新按钮
- 用户点击更新按钮触发更新
实际中:
registerServiceWorker.js
//add interval check after registered
registered(registration) {
console.log('Service worker has been registered.')
updateInterval = setInterval(() => {
registration.update();//dynamically pull service-worker
console.log('checking update!')
}, 1000 * 10) // e.g 10s senconds checks
}
//trigger custom event and export resgitration instance
updated(registration) { //triggered whens service-worker.js changed
console.log('New content is available; please refresh.')
document.dispatchEvent(
new CustomEvent('swUpdated', {
detail: registration
})
);
}
App.vue
//add custom event
document.addEventListener(
'swUpdated', this.showRefreshUI, { once: true }
);
//show refresh button
showRefreshUI(e) {
this.registration = e.detail;
this.updateExists = true;
},
//click to refresh and post web worker message
refreshApp() {
this.updateExists = false;
if (!this.registration || !this.registration.waiting) { return; }
this.registration.waiting.postMessage({
type: 'SKIP_WAITING'
});
},
这里refreshApp主要是通过web worker进行message交互(解决了多tab同步更新问题)
有发送消息肯定就有接收方,还记得之前有个配置项:
importScripts:['./sw-prefile.js']
sw-prefile.js
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
主要往sw中注入message事件监听,使得接收消息时新的sw跳过waiting阶段,再结合配置项
clientsClaim:true
完成了新旧sw替换。
到这里似乎是皆大欢喜
现在页面已经由新的sw接管,不过前面的请求通过的旧的sw来,就造成一个页面存在两个版本的请求,所以还需要进一步处理
navigator.serviceWorker && navigator.serviceWorker.addEventListener( //triggered by registration.claim
'controllerchange', () => {
if (this.refreshing) return;
this.refreshing = true;
console.log('controllerchange triggered, -> auto refresh!!')
window.location.reload();
}
);
监听新的sw接管,然后主动触发一次页面的刷新,刷新后的就是完全新的sw接管的页面
PWA降级处理
现在主要通过定时发送信息处理降级:
- 定时check '/pwa-check'
- 根据返回判定是否注销PWA
- 注销的同时清理定时check更新的任务
这里通过node提供/pwa-check接口
小结:
Todos:
- pwa转换amp
- 前端监控信息离线发送
- 添加到桌面统计
- ...
欢迎纠错!
附录
关于我们
客路旅行正在开放前端,后端开发等岗位,这里工作1075,不打卡
- 简历投递 caprice@klook.com
参考资料: