如何在可用更新时提示 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 exmaplepwa 更新的工作解决方案:

//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 });
    });
  }