上面就是两张效果图都是网页应用
添加cli-plugin-pwa
给现有的vue项目添加,使用下面命令:
vue add pwa
npm install register-service-worker
// 如果报错的话 就是用下面的
npm install -D @vue/cli-plugin-pwa
这个依赖的版本最好跟 @vue/cli-plugin-babel
… 一样就好
执行完之后会自动在package.json添加”@vue/cli-plugin-pwa”,“register-service-worker” 依赖。
并且在项目src目录下生成registerServiceWorker.js
和service-worker.js
文件:
registerServiceWorker.js
/* eslint-disable no-console */
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}
service-worker.js
略有改动 以你自己的为准
// install new service worker when ok, then reload page.
self.addEventListener("message", msg => {
if (msg.data && msg.data.type === 'SKIP_WAITING'){
self.skipWaiting()
}
})
而且还会在public/img/icons
下生成适用于个平台安装的默认图标,可以自己生成替换即可。
并且在public
下新建manifest.json
manifest.json
{
"name": "My App",
"short_name": "My App",
"icons": [
{
"src": "./img/icons/apple-touch-icon.png",
"sizes": "120x120",
"type": "image/png"
},
{
"src": "./img/icons/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "fullscreen",
"background_color": "#fedfe1",
"theme_color": "#fedfe1"
}
然后需要手动在main.js
引入 registerServiceWorker.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './registerServiceWorker'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
最后到vue.config.js
中添加相关配置:
module.exports = {
pwa: {
name: "My App",
themeColor: "#ffffff",
msTileColor: "#ffffff",
assetsVersion: "3.0",
iconPaths: {
favicon32: 'img/icons/favicon-32x32.png',
favicon16: 'img/icons/favicon-16x16.png',
appleTouchIcon: 'img/icons/android-chrome-256x256.png',
maskIcon: 'img/icons/apple-touch-icon.png', // 120x120
msTileImage: 'img/icons/msapplication-icon-144x144.png',
},
workboxPluginMode: "InjectManifest",
workboxOptions: {
swSrc: "src/service-worker.js",
},
}
这样重新npm run build:prod
后,刷新页面后你的网站地址栏已经出现可安装的图标。
接下来我们解决意外一个问题 就是每次重新部署后客户端缓存问题,通过service worker可以提示用户有新的可用版本🔽
vue项目重新build在服务端部署后,浏览器刷新页面弹出的提示,这时如果用户点击更新就会重载页面,清除之前的缓存获取最新内容。
这是怎样发生的呢?你可能会想到下面的方式:
- 服务端编译重新部署维护一个版本号,客户端通过轮询检测和本地存储的是否相同,发现更新的版本就弹框提示(缺点 耗电。尤其是在移动端)
- 通过在html中做版本标记…
- websocket长连接像客户端推送版本更新(繁琐)
- service worker
通过观察截图左下角的红框,可以看出这个网站采用方式是 注册了 service worker
。
这种方式只需前端处理,不需要服务端做任何工作。只要每次
build
后重新在服务端部署,有文件发生变动,就可以被service worker
发现。
在registerServiceWorker.js
添加事件触发
/* eslint-disable no-console */
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated (registration) {
console.log('New content is available; please refresh.')
document.dispatchEvent(
new CustomEvent('swUpdated', { detail: registration })
)
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}
在updated()
{} 中添加swUpdated
自定义事件,然后在ReloadPrompt.vue
组件中监听事件,有更新时弹出 提示用户更新。
这个组件需要在App.vue
中引入,或者其他合适的地方。
<template>
<div>
<transition name="el-zoom-in-bottom">
<el-card class="fix_right_bottom" v-if="visible">
<div style="padding: 14px;">
<span style="font-size: 14px;">
发现新版本,点击"更新"获取。
</span>
<div class="button_group">
<el-button size="mini" @click="handlePrompt(false)">关闭</el-button>
<el-button type="primary" size="mini" @click="refreshApp">更新</el-button>
</div>
</div>
</el-card>
</transition>
</div>
</template>
<script>
export default {
created() {
document.addEventListener('swUpdated', this.updateAvailable, { once: true })
navigator.serviceWorker.addEventListener('controllerchange', () => {
// We'll also need to add 'refreshing' to our data originally set to false.
if (this.refreshing) return
this.refreshing = true
// Here the actual reload of the page occurs
window.location.reload()
})
},
data() {
return {
visible: false,
registration: null,
refreshing: false
}
},
methods: {
handlePrompt(val) {
this.visible = val
},
updateAvailable(event) {
this.registration = event.detail
this.visible = true
},
refreshApp() {
this.visible = false
// Make sure we only send a 'skip waiting' message if the SW is waiting
if (!this.registration || !this.registration.waiting) return
// Send message to SW to skip the waiting and activate the new SW
this.registration.waiting.postMessage({ type: 'SKIP_WAITING' })
}
}
}
</script>
<style scoped>
.fix_right_bottom {
position: fixed;
right: 20px;
bottom: 20px;
}
.button_group {
margin-top: 10px;
}
</style>
service-worker.js
// install new service worker when ok, then reload page.
self.addEventListener("message", msg => {
if (msg.data && msg.data.type === 'SKIP_WAITING'){
self.skipWaiting()
}
})
重新来看一下这个流程,上一版的网页注册了service-worker.js
,服务端重新build
部署,有新内容出现,
客户端刷新
=> 发现有新的service-worker可以安装
=> 触发updated(){}
=> ReloadPrompt.vue
=> 触发一次 swUpdated
=> 弹出更新弹框用户点击更新
=> 向service-worker.js传递SKIP_WAITING
=> 新的service-worker.js安装后
=> 触发ReloadPrompt.vue中的controllerchange
实现页面重载