⇧ amazing...
WorkboxのService Workerの挙動が分かり辛い...
Vue.jsでというか、Vue CLIで作成されたプロジェクトで、「@vue/cli-plugin-pwa」ってライブラリをインストールしていると、「WorkBox」というもので「Service Worker」を便利に使えるらしいのですが、挙動が分かり辛いですと。
ちなみに、Vue CLIを利用した場合、
Progressive Web App (PWA) Support オプションを選択した場合、以下の npm package が追加されます。
-
“register-service-worker”: “^1.6.2”,
- “@vue/cli-plugin-pwa”: “^4.2.0”,
また、 Service Worker 関連で registerServiceWorker.ts が追加されます。
⇧ ということになる模様。
Vue CLIを使わずに作成してるVue.jsのプロジェクトの場合でも、npmなどで上記のモジュールを追加すれば、「WorkBox」で「Service Worker」を使えるようになるってことかと。
ちなみに、
After taking a few days off and looking back at it now, I have realized that the currents docs by google are for workbox version 6, and documentation for workbox version 5 is marked legacy.
Whereas the @vue/cli-plugin-pwa
plugin has a dependency for workbox version 4.3, which is severely outdated; there are a lot of breaking changes and compatibility issues between the two. Given this fact and that I am short on time I started to look in a different direction.
https://stackoverflow.com/questions/71515017/how-to-setup-google-workbox-with-vue-js-spa
⇧「@vue/cli-plugin-pwa」の依存関係の「Workbox」のバージョンが旧いという問題があるらしい...
「@vue/cli-plugin-pwa」の依存関係が、package.jsonに記載のもので合ってるのかが分かりませんが、
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\node_modules\@vue\cli-plugin-pwa\package.json
{ "_from": "@vue/cli-plugin-pwa@^4.5.12", "_id": "@vue/cli-plugin-pwa@4.5.17", "_inBundle": false, "_integrity": "sha512-IaODWmj5eQjv97ne0CTOgPZA8QmVS7zYX64C+SivWPw0uevJAhNUdDHgyrUODP7fEfyufKliStLMQJTowohGNQ==", "_location": "/@vue/cli-plugin-pwa", "_phantomChildren": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/wasm-edit": "1.9.0", "@webassemblyjs/wasm-parser": "1.9.0", "acorn": "6.4.2", "ajv": "6.12.6", "ajv-errors": "1.0.1", "ajv-keywords": "3.5.2", "arr-diff": "4.0.0", "arr-flatten": "1.1.0", "array-unique": "0.3.2", "big.js": "5.2.2", "chrome-trace-event": "1.0.3", "define-property": "2.0.2", "emojis-list": "3.0.0", "enhanced-resolve": "4.5.0", "esrecurse": "4.3.0", "estraverse": "4.3.0", "extend-shallow": "3.0.2", "extglob": "2.0.4", "fragment-cache": "0.2.1", "is-buffer": "1.1.6", "is-extendable": "0.1.1", "isobject": "3.0.1", "json-parse-better-errors": "1.0.2", "kind-of": "6.0.3", "loader-runner": "2.4.0", "memory-fs": "0.4.1", "minimist": "1.2.6", "mkdirp": "0.5.6", "nanomatch": "1.2.13", "neo-async": "2.6.2", "node-libs-browser": "2.2.1", "object.pick": "1.3.0", "regex-not": "1.0.2", "repeat-element": "1.1.4", "repeat-string": "1.6.1", "snapdragon": "0.8.2", "snapdragon-node": "2.1.1", "split-string": "3.1.0", "tapable": "1.1.3", "terser-webpack-plugin": "1.4.5", "to-regex": "3.0.2", "watchpack": "1.7.5", "webpack-sources": "1.4.3" }, "_requested": { "type": "range", "registry": true, "raw": "@vue/cli-plugin-pwa@^4.5.12", "name": "@vue/cli-plugin-pwa", "escapedName": "@vue%2fcli-plugin-pwa", "scope": "@vue", "rawSpec": "^4.5.12", "saveSpec": null, "fetchSpec": "^4.5.12" }, "_requiredBy": [ "#DEV:/" ], "_resolved": "https://registry.npmjs.org/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.17.tgz", "_shasum": "73b2f9dd1203de46761a9843e972966e2717fe87", "_spec": "@vue/cli-plugin-pwa@^4.5.12", "_where": "C:\\Users\\Toshinobu\\Desktop\\soft_work\\vue_work\\vue-typescript-admin-template", "author": { "name": "Evan You" }, "bugs": { "url": "https://github.com/vuejs/vue-cli/issues" }, "bundleDependencies": false, "dependencies": { "@vue/cli-shared-utils": "^4.5.17", "webpack": "^4.0.0", "workbox-webpack-plugin": "^4.3.1" }, "deprecated": false, "description": "pwa plugin for vue-cli", "devDependencies": { "register-service-worker": "^1.7.1" }, "gitHead": "58ff39cef3ec3574018089b3ea5bba5bb0abc10f", "homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-plugin-pwa#readme", "keywords": [ "vue", "cli", "pwa" ], "license": "MIT", "main": "index.js", "name": "@vue/cli-plugin-pwa", "peerDependencies": { "@vue/cli-service": "^3.0.0 || ^4.0.0-0" }, "publishConfig": { "access": "public" }, "repository": { "type": "git", "url": "git+https://github.com/vuejs/vue-cli.git", "directory": "packages/@vue/cli-plugin-pwa" }, "version": "4.5.17" }
⇧ 依存関係の中に「"workbox-webpack-plugin": "^4.3.1"」ってあるので、「Workbox」のバージョンは旧くなってるということになるんですかね?
もう1つ、「Service Worker」は、Googleが発祥という事で、「Android」なんかは問題なく対応してるとは思うのですが、
やっと、iOS 11.4(Safari 11.1)からPWA(Progressive Web Apps)に対応しました!
簡単に何ができるかと言うと、WEBアプリをオフラインでも動作するネイティブアプリのように振る舞うことができるようになりました。
⇧ 上記サイト様によりますと、「iOS」も対応が進んでいるようです。
脱線しましたが、Service Workerが登録される流れとしては、
⇧ ブラウザとは関係なく、バックグラウンドで非同期に処理されてるということのようですが、stateがactivatedになっていると、Service Worker自体の読み込みは完了したと言えることになると考えて良いのか?
いまいち、ハッキリしない...
⇧ 上記サイト様によりますと、エラーが無ければ「Activated」が最終的な状態になるってことですかね?
console.dirで確認してみる
とりあえず、console.dirで確認してみる。
確認に利用するプロジェクトは、
⇧ 上記記事で、インストールしたVue.jsの管理画面の雛型を使ってます。
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\pwa\register-service-worker.ts
/* eslint-disable no-console */ import { register } from 'register-service-worker' // if (process.env.NODE_ENV === 'production') { 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(registration) { console.log('Service worker has been registered.') // Routinely check for app updates by testing for a new service worker. // setInterval(() => { // registration.update() // }, 1000 * 60 * 60) // hourly checks console.dir(registration) }, 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.') // Add a custom event and dispatch it. // Used to display of a 'refresh' banner following a service worker update. // Set the event payload to the service worker registration object. 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) } }) }
⇧ 本番環境ではないので、本番環境以外でService Workerが動くように条件を修正。
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\views\login\components\LoadingScreen.vue
<template> <!--<div :class="{loader: true, fadeout: !isLoading}">--> <div> <div class="page-container loader"> <img class="splash" src="@/assets/splash-loading.jpg" /> </div> </div> </template> <script lang="ts"> /* eslint-disable no-console */ import { Component, Prop, Vue, Watch } from 'vue-property-decorator' Component({ name: 'LoadingScreen', components: { } }) export default class Loading extends Vue { @Prop() private isLoading = true; // created() { // console.log('■') // } // mounted() { // this.checkloading() // } // private checkloading() { // console.log('■created()') // } // @Watch('isLoading') // private watchIsLoading() { // console.log('■') // } } </script> <style scoped> .page-container { max-width: 500px; width: 100%; margin: auto; } .loader { background-color: #ffffff; /**bottom: 0; */ color: white; /**display: flex; */ align-items: center; text-align: center; /**left: 0; */ overflow: hidden; position: fixed; inset: 0; /**right: 0; */ /**top: 0; */ } .splash { width: 120px; } .fadeout { animation: fadeout 2s forwards; } @keyframes fadeout { to { opacity: 0; visibility: hidden; } } </style>
⇧ ローディング画面(スマホとかだと、スプラッシュと言うらしい)用のVueファイルを準備。
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\views\login\index.vue
<template> <div class="login-container"> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left" > <div class="title-container"> <h3 class="title"> {{ $t('login.title') }} | テス | テス </h3> <lang-select class="set-language" /> </div> <el-form-item prop="username"> <span class="svg-container"> <svg-icon name="user" /> </span> <el-input ref="username" v-model="loginForm.username" :placeholder="$t('login.username')" name="username" type="text" tabindex="1" autocomplete="on" /> </el-form-item> <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual > <el-form-item prop="password"> <span class="svg-container"> <svg-icon name="password" /> </span> <el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" :placeholder="$t('login.password')" name="password" tabindex="2" autocomplete="on" @keyup.native="checkCapslock" @blur="capsTooltip = false" @keyup.enter.native="handleLogin" /> <span class="show-pwd" @click="showPwd" > <svg-icon :name="passwordType === 'password' ? 'eye-off' : 'eye-on'" /> </span> </el-form-item> </el-tooltip> <el-button :loading="loading" type="primary" style="width:100%; margin-bottom:30px;" @click.native.prevent="handleLogin" > {{ $t('login.logIn') }} </el-button> <div style="position:relative"> <div class="tips"> <span>{{ $t('login.username') }} : admin </span> <span>{{ $t('login.password') }} : {{ $t('login.any') }} </span> </div> <div class="tips"> <span>{{ $t('login.username') }} : editor </span> <span>{{ $t('login.password') }} : {{ $t('login.any') }} </span> </div> <el-button class="thirdparty-button" type="primary" @click="showDialog=true" > {{ $t('login.thirdparty') }} </el-button> </div> </el-form> <el-dialog :title="$t('login.thirdparty')" :visible.sync="showDialog" > {{ $t('login.thirdpartyTips') }} <br> <br> <br> <social-sign /> </el-dialog> <LoadingScreen :isLoading="isLoading" /> </div> </template> <script lang="ts"> import { Component, Vue, Watch } from 'vue-property-decorator' import { Route } from 'vue-router' import { Dictionary } from 'vue-router/types/router' import { Form as ElForm, Input } from 'element-ui' import { UserModule } from '@/store/modules/user' import { isValidUsername } from '@/utils/validate' import LangSelect from '@/components/LangSelect/index.vue' import SocialSign from './components/SocialSignin.vue' import LoadingScreen from './components/LoadingScreen.vue' @Component({ name: 'Login', components: { LangSelect, SocialSign, LoadingScreen } }) export default class extends Vue { private validateUsername = (rule: any, value: string, callback: Function) => { if (!isValidUsername(value)) { callback(new Error('Please enter the correct user name')) } else { callback() } } private validatePassword = (rule: any, value: string, callback: Function) => { if (value.length < 6) { callback(new Error('The password can not be less than 6 digits')) } else { callback() } } private loginForm = { username: 'admin', password: '111111' } private loginRules = { username: [{ validator: this.validateUsername, trigger: 'blur' }], password: [{ validator: this.validatePassword, trigger: 'blur' }] } private passwordType = 'password' private loading = false private showDialog = false private capsTooltip = false private redirect?: string private otherQuery: Dictionary<string> = {} private isLoading = true @Watch('$route', { immediate: true }) private onRouteChange(route: Route) { // TODO: remove the "as Dictionary<string>" hack after v4 release for vue-router // See https://github.com/vuejs/vue-router/pull/2050 for details const query = route.query as Dictionary<string> if (query) { this.redirect = query.redirect this.otherQuery = this.getOtherQuery(query) } } mounted() { if (isComplete) { this.isLoading = false } if (this.loginForm.username === '') { (this.$refs.username as Input).focus() } else if (this.loginForm.password === '') { (this.$refs.password as Input).focus() } } private checkCapslock(e: KeyboardEvent) { const { key } = e this.capsTooltip = key !== null && key.length === 1 && (key >= 'A' && key <= 'Z') } private showPwd() { if (this.passwordType === 'password') { this.passwordType = '' } else { this.passwordType = 'password' } this.$nextTick(() => { (this.$refs.password as Input).focus() }) } private handleLogin() { (this.$refs.loginForm as ElForm).validate(async(valid: boolean) => { if (valid) { this.loading = true await UserModule.Login(this.loginForm) this.$router.push({ path: this.redirect || '/', query: this.otherQuery }).catch(err => { console.warn(err) }) // Just to simulate the time of the request setTimeout(() => { this.loading = false }, 0.5 * 1000) } else { return false } }) } private getOtherQuery(query: Dictionary<string>) { return Object.keys(query).reduce((acc, cur) => { if (cur !== 'redirect') { acc[cur] = query[cur] } return acc }, {} as Dictionary<string>) } } let isComplete = false // Service Workerの確認 navigator.serviceWorker.getRegistrations() .then(function(registrations) { console.log(isComplete) console.log('■LoadingScreen') console.dir(registrations) let count = 0 for (const registration of registrations) { console.dir(registration) if (registration.active && registration.active.state === 'activated') { console.log('■Service Workerの読み込み完了■') count++ } } if (count === registrations.length) { console.log('■□■すべてのService Workerの読み込み完了■□■') isComplete = true } }).finally(() => { console.log(isComplete) const element = document.getElementsByClassName('page-container')[0].classList element.add('fadeout') }) </script> <style lang="scss"> @supports (-webkit-mask: none) and (not (cater-color: $loginCursorColor)) { .login-container .el-input { input { color: $loginCursorColor; } input::first-line { color: $lightGray; } } } .login-container { .el-input { display: inline-block; height: 47px; width: 85%; input { height: 47px; background: transparent; border: 0px; border-radius: 0px; padding: 12px 5px 12px 15px; color: $lightGray; caret-color: $loginCursorColor; -webkit-appearance: none; &:-webkit-autofill { box-shadow: 0 0 0px 1000px $loginBg inset !important; -webkit-text-fill-color: #fff !important; } } } .el-form-item { border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.1); border-radius: 5px; color: #454545; } } </style> <style lang="scss" scoped> .login-container { height: 100%; width: 100%; overflow: hidden; background-color: $loginBg; .login-form { position: relative; width: 520px; max-width: 100%; padding: 160px 35px 0; margin: 0 auto; overflow: hidden; } .tips { font-size: 14px; color: #fff; margin-bottom: 10px; span { &:first-of-type { margin-right: 16px; } } } .svg-container { padding: 6px 5px 6px 15px; color: $darkGray; vertical-align: middle; width: 30px; display: inline-block; } .title-container { position: relative; .title { font-size: 26px; color: $lightGray; margin: 0px auto 40px auto; text-align: center; font-weight: bold; } .set-language { color: #fff; position: absolute; top: 3px; font-size: 18px; right: 0px; cursor: pointer; } } .show-pwd { position: absolute; right: 10px; top: 7px; font-size: 16px; color: $darkGray; cursor: pointer; user-select: none; } .thirdparty-button { position: absolute; right: 0; bottom: 6px; } @media only screen and (max-width: 470px) { .thirdparty-button { display: none; } } } </style>
で保存して、スプラッシュの画像はフリー素材をダウンロードして配置しておきます。
で、npm run serveで、開発用サーバーを起動して、Vue.jsのアプリケーションを起動すると、
⇧ Service Workerのstateがactivatedになっていることは確認できました。
Service WorkerのstateをVue.jsのインスタンスに連携ができなさそうなのよね...
Chromeのデベロッパーツールの「Application」タブで、「Service Worker」の「Status」が「activated and is stopped」ってのが気になる...
どうやら、
- The Status line tells you the status of the service worker. The number on this line (
#1
in the screenshot above) indicates how many times the service worker has been updated. If you enable the update on reload checkbox you'll notice that the number increments on every page load. Next to the status you'll see a start button (if the service worker is stopped) or a stop button (if the service worker is running). Service workers are designed to be stopped and started by the browser at any time. Explicitly stopping your service worker using the stop button can simulate that. Stopping your service worker is a great way to test how your code behaves when the service worker starts back up again. It frequently reveals bugs due to faulty assumptions about persistent global state.
https://developer.chrome.com/docs/devtools/progressive-web-apps/
⇧「Service Worker」が「activated」されたものの、停止してる状態らしい...
う~む、「activated」は、
⇧「activate Event」が完了したら、「Service Worker」が動いていようが停まっていようが関係なく「activated」な状態になるってことなんですかね?
そもそも、Service Workerのstateを判断材料にして良いのかも分からんのですが...
毎度モヤモヤ感が半端ない...
2022年7月27日(水)追記:↓ ここから
気になった情報を見つけたので、
⇧ 上記サイト様を参考に、Service Worker 部分を書き換えてみました。
変更部分を抜粋してます。
<script lang="ts"> ...省略 let isComplete = false // Service Workerの確認 ;(async() => { await navigator.serviceWorker.ready const registration: any = await navigator.serviceWorker.getRegistration() if (registration) { console.log('■getRegistration()') registration.addEventListener('controllerchange', () => { console.log('■') }) registration.addEventListener('updatefound', () => { console.log('■□■') }) registration.update() } navigator.serviceWorker.getRegistrations() .then(function(registrations) { console.log(isComplete) console.log('■LoadingScreen') console.dir(registrations) let count = 0 for (const registration of registrations) { console.dir(registration) registration.addEventListener('updatefound', () => { console.log('更新あり') }) if (registration.active && registration.active.state === 'activated') { console.log('■Service Workerの読み込み完了■') count++ } } if (count === registrations.length) { console.log('■□■すべてのService Workerの読み込み完了■□■') isComplete = true } }).finally(() => { console.log(isComplete) const element = document.getElementsByClassName('page-container')[0].classList element.add('fadeout') }) })() </script>
で、アクセスしてみたけども、
残念ながら、updatefoundの情報を取得する方法が分からないから、更新できるService Worker が存在してようがいなかろうが、無差別に、registration.update()するしかない感じになってしまってますが...
⇧ 普通に考えたら、updatefound()の結果から判断して、update()を実行したいと思うんだけど、ドキュメントの参考例が実用的で無さ過ぎるんよね...
そもそもとして、「C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\pwa\register-service-worker.ts」の中の、
updated(registration) { console.log('New content is available; please refresh.') // Add a custom event and dispatch it. // Used to display of a 'refresh' banner following a service worker update. // Set the event payload to the service worker registration object. document.dispatchEvent( new CustomEvent('swUpdated', { detail: registration }) ) }
が実行されてないのが、良く分からん...
だって、updatefound()が呼ばれてるんだから、新しいService Workerが存在するはずだから、updated()が呼ばれないのがちょっと意味が分からない...
2022年7月27日(水)追記:↑ ここまで
2022年7月28日(木)追記:↓ ここから
どうやら、production mode でVue.jsのアプリケーションを起動しないと、WorkboxのService Worker のupdated()メソッドは実行されないらしい。
あと、vue-notificationっていうライブラリを自分でインストールする必要があるっぽいです、雛型が中途半端過ぎる...
⇧ 上記をインストールしときます。
コマンドプロンプトなどで、以下のコマンドを実行。バージョンとかはインストールした時点での最新バージョンをインストールしてます。
npm install vue-notification@1.3.20 --save-dev
vue notificationを利用できるように修正。
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\main.ts
import Vue, { DirectiveOptions } from 'vue' import 'normalize.css' import ElementUI from 'element-ui' import SvgIcon from 'vue-svgicon' import '@/styles/element-variables.scss' import '@/styles/index.scss' import App from '@/App.vue' import store from '@/store' import { AppModule } from '@/store/modules/app' import router from '@/router' import i18n from '@/lang' import '@/icons/components' import '@/permission' import '@/utils/error-log' import '@/pwa/register-service-worker' import * as directives from '@/directives' import * as filters from '@/filters' import Notifications from 'vue-notification' Vue.use(Notifications) Vue.use(ElementUI, { size: AppModule.size, // Set element-ui default size i18n: (key: string, value: string) => i18n.t(key, value) }) Vue.use(SvgIcon, { tagName: 'svg-icon', defaultWidth: '1em', defaultHeight: '1em' }) // Register global directives Object.keys(directives).forEach(key => { Vue.directive(key, (directives as { [key: string ]: DirectiveOptions })[key]) }) // Register global filter functions Object.keys(filters).forEach(key => { Vue.filter(key, (filters as { [key: string ]: Function })[key]) }) Vue.config.productionTip = false new Vue({ router, store, i18n, render: (h) => h(App) }).$mount('#app')
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\App.vue
<template> <div id="app"> <router-view /> <service-worker-update-popup /> <notifications/> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import ServiceWorkerUpdatePopup from '@/pwa/components/ServiceWorkerUpdatePopup.vue' @Component({ name: 'App', components: { ServiceWorkerUpdatePopup } }) export default class extends Vue {} </script>
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\pwa\components\ServiceWorkerUpdatePopup.vue
<script lang="ts"> /* eslint-disable no-console */ import { Component, Vue } from 'vue-property-decorator' @Component({ name: 'ServiceWorkerUpdatePopup' }) export default class extends Vue { private refreshing = false private notificationText = 'New content is available!' private refreshButtonText = 'Refresh' private registration: ServiceWorkerRegistration | null = null created() { // Listen for swUpdated event and display refresh notification as required. document.addEventListener('swUpdated', this.showRefreshUI, { once: true }) // Refresh all open app tabs when a new service worker is installed. navigator.serviceWorker.addEventListener('controllerchange', () => { console.log('■リロード') if (this.refreshing) return this.refreshing = true window.location.reload() }) } render() { // Avoid warning for missing template } private showRefreshUI(e: Event) { // Display a notification inviting the user to refresh/reload the app due // to an app update being available. // The new service worker is installed, but not yet active. // Store the ServiceWorkerRegistration instance for later use. const h = this.$createElement this.registration = (e as CustomEvent).detail this.$notify.info({ title: 'Update available', message: h('div', { class: 'sw-update-popup' }, [ this.notificationText, h('br'), h('button', { on: { click: (e: Event) => { e.preventDefault() this.refreshApp() } } }, this.refreshButtonText) ]), position: 'top-right', duration: 10 }) } private refreshApp() { // Protect against missing registration.waiting. if (!this.registration || !this.registration.waiting) return this.registration.waiting.postMessage('skipWaiting') } } </script> <style lang="scss" scoped> .sw-update-popup > button { margin-top: 0.5em; padding: 0.25em 1.5em; } </style>
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\src\pwa\register-service-worker.ts
/* eslint-disable no-console */ import { register } from 'register-service-worker' if (process.env.NODE_ENV === 'production') { // 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(registration) { console.log('Service worker has been registered.') // Routinely check for app updates by testing for a new service worker. // setInterval(() => { // registration.update() // }, 1000 * 60 * 60) // hourly checks console.dir(registration) }, 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.') // Add a custom event and dispatch it. // Used to display of a 'refresh' banner following a service worker update. // Set the event payload to the service worker registration object. 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) } }) }
で、保存して、コマンドプロンプトで、production modeで起動。
"C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-typescript-admin-template\node_modules\.bin\vue-cli-service" serve --mode=production
で、ブラウザでアクセスすると、
⇧ updated()メソッドのconsole.logの内容が表示されてるので、updated()メソッドが実行されたようです。
「@vue/cli-plugin-pwa」の「Workbox」の「Service Worker」に特有の問題なのか分かりませんが、カオスな世界になってますな...
2022年7月28日(木)追記:↑ ここまで
今回はこのへんで。