如何在可用更新时提示 Vuejs pwa 应用程序中的用户?
How to propmt user in Vuejs pwa app on update available?
我已经阅读了几个用于更新 PWA 的 vuejs PWA 示例,但它似乎对我不起作用并且让我对这些问题感到困惑:
1. When I build the app and run it,
it will instantly download the content and shows the new content.
2. It will ask every time that there is a new version.
我该如何解决这些问题?
我只是想提示用户看到有新版本(例如v1
)如果he/she想要,请在接受后更新应用程序,不要打扰he/her下次刷新直到有新版本(例如v2
)
感谢 custom service worker exmaple,pwa
更新的工作解决方案:
//vue.config.js
module.exports = {
publicPath: "./",
pwa: {
themeColor: "#42b983",
msTileColor: "#42b983",
appleMobileWebAppCache: "yes",
manifestOptions: {
background_color: "#42b983"
}
}
};
//registerServiceWorker.js:
import { Workbox } from "workbox-window";
let wb;
if ("serviceWorker" in navigator) {
wb = new Workbox(`${process.env.BASE_URL}service-worker.js`);
wb.addEventListener("controlling", () => {
window.location.reload();
});
wb.register();
} else {
wb = null;
}
export default wb;
//main.js
import Vue from "vue";
import App from "./App.vue";
import wb from "./registerServiceWorker";
Vue.prototype.$workbox = wb;
new Vue({
render: h => h(App)
}).$mount("#app");
//App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Vue.js with PWA" />
<div class="update-dialog" v-if="prompt">
<div class="update-dialog__content">
A new version is found. Refresh to load it?
</div>
<div class="update-dialog__actions">
<button
class="update-dialog__button update-dialog__button--confirm"
@click="update"
>
Update
</button>
<button
class="update-dialog__button update-dialog__button--cancel"
@click="prompt = false"
>
Cancel
</button>
</div>
</div>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld,
},
methods: {
async update() {
this.prompt = false;
await this.$workbox.messageSW({ type: "SKIP_WAITING" });
},
},
data() {
return {
prompt: false,
};
},
created() {
if (this.$workbox) {
this.$workbox.addEventListener("waiting", () => {
this.prompt = true;
});
}
},
};
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.update-dialog {
position: fixed;
left: 50%;
bottom: 64px;
transform: translateX(-50%);
border-radius: 4px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
padding: 12px;
max-width: 576px;
color: white;
background-color: #2c3e50;
text-align: left;
&__actions {
display: flex;
margin-top: 8px;
}
&__button {
margin-right: 8px;
&--confirm {
margin-left: auto;
}
}
}
</style>
我不确定 Vue,
但是我使用了下面这些代码并且在 React 项目中对我来说工作得很好。
首先,调用registerValidSW方法注册ServiceWorker:
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
const event = new Event("app-event-newContentAvailable");
window.dispatchEvent(event);
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
我使用了 workbox,这就是我的 workbox-build.js 文件的样子:
const workboxBuild = require('workbox-build');
// NOTE: This should be run *AFTER* all your assets are built
const buildSW = () => {
// This will return a Promise
return workboxBuild.injectManifest({
swSrc: 'src/sw-template.js', // this is your sw template file
swDest: 'build/sw.js', // this will be created in the build step
globDirectory: 'build',
globIgnores: ['**/service-worker.js', '**/precache-manifest.*.js'],
globPatterns: [
'**\/*.{js,css,html,png}',
]
}).then(({count, size, warnings}) => {
// Optionally, log any warnings and details.
warnings.forEach(console.warn);
console.log(`${count} files will be precached, totaling ${size} bytes.`);
});
}
buildSW();
这是sw-template.js:
if ('function' === typeof importScripts) {
importScripts(
// 'https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'
'static/sw-workbox/v4.3.1/workbox-sw.js'
);
/* global workbox */
if (workbox) {
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute([]);
const cacheConfig = {
images: [
/\.(?:jpg|jpeg|png|gif|svg|ico)$/,
new workbox.strategies.CacheFirst({
cacheName: "images",
plugins: [
new workbox.expiration.Plugin({
maxEntries: 6000,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
})
]
}),
"GET"
],
fonts: [
/\.(?:eot|ttf|woff|woff2)$/,
new workbox.strategies.CacheFirst({
cacheName: "fonts",
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
maxEntries: 30
})
]
}),
"GET"
],
manifest: [
new RegExp('manifest.json'),
new workbox.strategies.CacheFirst({
cacheName: "manifest",
plugins: [
new workbox.expiration.Plugin({
// maxAgeSeconds: 60 * 60 * 24 * 2, // 2 days
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
maxEntries: 1
})
]
}),
"GET"
],
};
/* custom cache rules*/
workbox.routing.registerNavigationRoute('/index.html', {
blacklist: [
/^\/_/, /\/[^\/]+\.[^\/]+$/,
],
});
workbox.routing.registerRoute(...cacheConfig.images);
workbox.routing.registerRoute(...cacheConfig.fonts);
workbox.routing.registerRoute(...cacheConfig.manifest);
const SkipWaitingAndClaim = () => {
workbox.core.skipWaiting();
workbox.core.clientsClaim();
};
SkipWaitingAndClaim();
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}
在 package.json 中,我在我的脚本中添加了一个新行,以便我可以在构建应用程序之前构建我的 sw.js 文件:
"scripts": {
"build-sw": "node ./src/sw-build.js",
...
...
},
现在您可以添加 EventListener,例如 App.tsx(我使用带有重新加载操作按钮的模态):
componentDidMount() {
window.addEventListener("app-event-newContentAvailable", () => {
this.setState({ showConfirmReloadModal: true });
});
}
我已经阅读了几个用于更新 PWA 的 vuejs PWA 示例,但它似乎对我不起作用并且让我对这些问题感到困惑:
1. When I build the app and run it,
it will instantly download the content and shows the new content.
2. It will ask every time that there is a new version.
我该如何解决这些问题?
我只是想提示用户看到有新版本(例如v1
)如果he/she想要,请在接受后更新应用程序,不要打扰he/her下次刷新直到有新版本(例如v2
)
感谢 custom service worker exmaple,pwa
更新的工作解决方案:
//vue.config.js
module.exports = {
publicPath: "./",
pwa: {
themeColor: "#42b983",
msTileColor: "#42b983",
appleMobileWebAppCache: "yes",
manifestOptions: {
background_color: "#42b983"
}
}
};
//registerServiceWorker.js:
import { Workbox } from "workbox-window";
let wb;
if ("serviceWorker" in navigator) {
wb = new Workbox(`${process.env.BASE_URL}service-worker.js`);
wb.addEventListener("controlling", () => {
window.location.reload();
});
wb.register();
} else {
wb = null;
}
export default wb;
//main.js
import Vue from "vue";
import App from "./App.vue";
import wb from "./registerServiceWorker";
Vue.prototype.$workbox = wb;
new Vue({
render: h => h(App)
}).$mount("#app");
//App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Vue.js with PWA" />
<div class="update-dialog" v-if="prompt">
<div class="update-dialog__content">
A new version is found. Refresh to load it?
</div>
<div class="update-dialog__actions">
<button
class="update-dialog__button update-dialog__button--confirm"
@click="update"
>
Update
</button>
<button
class="update-dialog__button update-dialog__button--cancel"
@click="prompt = false"
>
Cancel
</button>
</div>
</div>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld,
},
methods: {
async update() {
this.prompt = false;
await this.$workbox.messageSW({ type: "SKIP_WAITING" });
},
},
data() {
return {
prompt: false,
};
},
created() {
if (this.$workbox) {
this.$workbox.addEventListener("waiting", () => {
this.prompt = true;
});
}
},
};
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.update-dialog {
position: fixed;
left: 50%;
bottom: 64px;
transform: translateX(-50%);
border-radius: 4px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
padding: 12px;
max-width: 576px;
color: white;
background-color: #2c3e50;
text-align: left;
&__actions {
display: flex;
margin-top: 8px;
}
&__button {
margin-right: 8px;
&--confirm {
margin-left: auto;
}
}
}
</style>
我不确定 Vue, 但是我使用了下面这些代码并且在 React 项目中对我来说工作得很好。
首先,调用registerValidSW方法注册ServiceWorker:
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
const event = new Event("app-event-newContentAvailable");
window.dispatchEvent(event);
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
我使用了 workbox,这就是我的 workbox-build.js 文件的样子:
const workboxBuild = require('workbox-build');
// NOTE: This should be run *AFTER* all your assets are built
const buildSW = () => {
// This will return a Promise
return workboxBuild.injectManifest({
swSrc: 'src/sw-template.js', // this is your sw template file
swDest: 'build/sw.js', // this will be created in the build step
globDirectory: 'build',
globIgnores: ['**/service-worker.js', '**/precache-manifest.*.js'],
globPatterns: [
'**\/*.{js,css,html,png}',
]
}).then(({count, size, warnings}) => {
// Optionally, log any warnings and details.
warnings.forEach(console.warn);
console.log(`${count} files will be precached, totaling ${size} bytes.`);
});
}
buildSW();
这是sw-template.js:
if ('function' === typeof importScripts) {
importScripts(
// 'https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'
'static/sw-workbox/v4.3.1/workbox-sw.js'
);
/* global workbox */
if (workbox) {
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute([]);
const cacheConfig = {
images: [
/\.(?:jpg|jpeg|png|gif|svg|ico)$/,
new workbox.strategies.CacheFirst({
cacheName: "images",
plugins: [
new workbox.expiration.Plugin({
maxEntries: 6000,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
})
]
}),
"GET"
],
fonts: [
/\.(?:eot|ttf|woff|woff2)$/,
new workbox.strategies.CacheFirst({
cacheName: "fonts",
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
new workbox.expiration.Plugin({
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year
maxEntries: 30
})
]
}),
"GET"
],
manifest: [
new RegExp('manifest.json'),
new workbox.strategies.CacheFirst({
cacheName: "manifest",
plugins: [
new workbox.expiration.Plugin({
// maxAgeSeconds: 60 * 60 * 24 * 2, // 2 days
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
maxEntries: 1
})
]
}),
"GET"
],
};
/* custom cache rules*/
workbox.routing.registerNavigationRoute('/index.html', {
blacklist: [
/^\/_/, /\/[^\/]+\.[^\/]+$/,
],
});
workbox.routing.registerRoute(...cacheConfig.images);
workbox.routing.registerRoute(...cacheConfig.fonts);
workbox.routing.registerRoute(...cacheConfig.manifest);
const SkipWaitingAndClaim = () => {
workbox.core.skipWaiting();
workbox.core.clientsClaim();
};
SkipWaitingAndClaim();
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}
在 package.json 中,我在我的脚本中添加了一个新行,以便我可以在构建应用程序之前构建我的 sw.js 文件:
"scripts": {
"build-sw": "node ./src/sw-build.js",
...
...
},
现在您可以添加 EventListener,例如 App.tsx(我使用带有重新加载操作按钮的模态):
componentDidMount() {
window.addEventListener("app-event-newContentAvailable", () => {
this.setState({ showConfirmReloadModal: true });
});
}