PWA(Progressive Web Apps,渐进式 Web 应用) 运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。这些应用无处不在、功能丰富,使其具有与原生应用相同的用户体验优势。
通常用户使用pwa应用的方式是将H5页面添加到手机主屏幕(桌面),什么叫添加到主屏幕?
添加到主屏幕(Add to Home Screen,简称 A2HS)是现代智能手机浏览器中的一项功能,使开发人员可以轻松便捷地将自己喜欢的 Web 应用程序(或网站)的快捷方式添加到主屏幕中,以便用户随后可以通过单击访问它。
Mobile Chrome / Android Webview 从 31 版开始支持 A2HS,Opera for Android 从 32 版开始支持,Firefox for Android 从 58 版 开始支持。
如何使我们的应用程序支持 A2HS 呢?
- 应用通过 HTTPS 提供服务——Web 正朝着更加安全的方向发展,包括 A2HS 在内的许多现代 Web 技术都将仅工作在安全的环境中。
- 从 HTML 头链接具有正确字段的 manifest 文件。
- 有合适的图标可显示在主屏幕上。
- Chrome 浏览器还要求该应用程序注册一个 Service Worker(这样在离线状态下就也可以运行)。
详细说明可以参考: PWA Docs
按照上面的方法,我们讲解下如何使我们的nuxt应用支持A2HS:
- 首先,我们的应用应该部署在 https 服务上。(这点大部分网站都是https的)
- 在nuxt中创建 manifest 文件: 因为我们要在app.html中加入
<link rel="manifest" href="/manifest.webmanifest">
按照nuxt访问路径,我们应该把manifest这种静态文件加到nuxt的static文件夹里,所以我们在static文件夹中创建manifest.webmanifest文件,这个文件的内容是定义我们的PWA桌面应用的配置。
{
"background_color": "#ffffff", // 安装应用时或启动应用时的背景色
"theme_color": "#ffffff", // UI的颜色,操作系统使用的
"description": "test pwa", // 应用描述
"display": "standalone", // 桌面应用的壳中h5显示方式,全屏、独立、最小UI或浏览器
"icons": [
{
"src": "icon/36-min.png",
"sizes": "36x36",
"type": "image/png"
},
{
"src": "icon/48-min.png",
"sizes": "48x48",
"type": "image/png"
},{
"src": "icon/72-min.png",
"sizes": "72x72",
"type": "image/png"
},{
"src": "icon/96-min.png",
"sizes": "96x96",
"type": "image/png"
},{
"src": "icon/144-min.png",
"sizes": "144x144",
"type": "image/png"
},{
"src": "icon/192-min.png",
"sizes": "192x192",
"type": "image/png"
},{
"src": "icon/256-min.png",
"sizes": "256x256",
"type": "image/png"
},{
"src": "icon/512-min.png",
"sizes": "512x512",
"type": "image/png"
}
], // icon图标,图标尽量多种尺寸,可以支持用户不同的设备
"name": "test", // 网站应用的全名
"short_name": "test", // 显示在桌面的短名
"start_url": "/?from=pwa", // 桌面应用的入口页面链接,可以带上参数来区分pwa应用打开
"related_applications": [{
"platform": "play",
"id": "xxx",
"url": "xxx"
}] // 关联google应用
}
- 接着在nuxt应用中注册一个 serviceworker
我们可以在static文件夹中简单创建一个sw.js,可以在里面定义两个事件监听
self.addEventListener('install', (e) => {
e.waitUntil(caches.open('test-store').then((cache) => cache.addAll([
// 可以加入你想缓存的文件列表
'/xxxx/xxxx'
]))
})
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then((response) => response || fetch(e.request))
)
})
有了sw.js后,我们需要在window.onload的时候注册一下
window.addEventListener('load',function(){
if ('serviceWorker' in window.navigator) {
window.navigator.serviceWorker
.register('sw.js')
.then(() => { console.log('Service Worker Registered'); })
.catch(() => { console.log('Service Worker Registered Failed'); });
}
})
这样我们在上面支持A2HS的浏览器中打开我们的H5时,比如在安卓手机的chrome浏览器,点击右上角菜单,就能看到安装应用这个选项,便可以安装PWA应用到手机桌面啦。
但实际上我们要做打包后js和静态图片文件缓存的话,应该在构建的时候将缓存的文件的路径打入到serviceworker里,对此google提供了一个插件叫 workbox-webpack-plugin ,我们在nuxt.config.js的插件配置他:
build: {
extend(config, { isDev, isClient }) {
const worboxWebpackPlugin = require("workbox-webpack-plugin")
if (isClient) {
config.plugins.push(
new worboxWebpackPlugin.GenerateSW({
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true,
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
swDest: path.join(__dirname, '/src/static/sw.js'),
manifestTransforms: [
async (manifestEntries) => {
const manifest = manifestEntries.filter(entry => {
return entry.url.indexOf('../server') === -1;
});
return {manifest, warnings: []};
}
]
})
)
}
}
}
因为nuxt构建是包含服务端server的文件,所以我们需要剔除他们,实际缓存的是client端的文件。 swDest是重新定义sw.js生成的路径,同样和前面demo的sw.js一样放到static文件夹里。
插件具体配置参数和作用可以参考: developers.google.com/web/tools/w…
这样我们nuxt构建后,就会在static文件夹里生成sw.js和workbox-{hash}.js。
PWA在nuxt中的简单应用就实现了。 实际上,我们是希望用户点击H5页面的某个按钮或者提示条来触发PWA应用的安装,而不是需要用户自主点击浏览器菜单中的安装应用,这怎么实现呢? 这需要我们监听并使用浏览器的 beforeinstallprompt 事件。
在nuxt中,我们是通过写一个vue组件来实现一个提示条或安装按钮的,所以我们会在组件的mounted里这样写道:
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault(); // 防止 Chrome 67 及更早版本自动显示安装提示
this.deferredPrompt = e; // 保存event,点击唤起安装需要用到
this.show(); // 触发事件时展示该组件
})
然后给该组件绑定一个点击事件:
handleClick() {
if (this.deferredPrompt) {
this.deferredPrompt.prompt();
this.deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
this.deferredPrompt = null;
});
}
this.hide(); // 点击完后隐藏该组件
}
完成后,我们发现组件有很大概率没有展示,也就是说我们监听的这个beforeinstallprompt事件并不能成功触发,这是为什么呢? 因为,beforeinstallprompt事件触发的时机过早,可能会比组件mounted还要早,所以如果浏览器已经触发了beforeinstallprompt事件,而我们才在组件mounted中去监听,那这个监听就不会再触发了,所以我们需要改进一下。
正常情况下,beforeinstallprompt事件会在window.onload后触发,我们可以在 app.html 里的script中 window.onload 的时候加个监听:
window.addEventListener('beforeinstallprompt',(e) => {
console.log(e);
e.preventDefault();
window.deferredPrompt = e;
});
然后把这个event存储在全局变量里,再修改下组件mounted里的展示判断:
// 如果beforeinstallprompt这个事件在window.onload后 组件mounted前就触发,那window.deferredPrompt就会是pwa的事件
if (window.deferredPrompt) {
this.deferredPrompt = window.deferredPrompt;
this.show();
} else {
// 如果beforeinstallprompt这个事件在window.onload后 组件mounted时未触发,则重新建立监听
window.addEventListener('beforeinstallprompt', (e) => {
console.log('bar', e);
e.preventDefault();
this.deferredPrompt = e;
this.show();
});
}