refactor: inject pwa client plugin from module (#1758)
This commit is contained in:
parent
523578ba7b
commit
436489461c
7 changed files with 28 additions and 7 deletions
|
@ -1,5 +1,5 @@
|
|||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import { defineNuxtModule } from '@nuxt/kit'
|
||||
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
|
||||
import type { VitePluginPWAAPI } from 'vite-plugin-pwa'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import type { Plugin } from 'vite'
|
||||
|
@ -19,6 +19,8 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
|||
scope: nuxt.options.app.baseURL,
|
||||
}),
|
||||
async setup(options, nuxt) {
|
||||
const resolver = createResolver(import.meta.url)
|
||||
|
||||
let vitePwaClientPlugin: Plugin | undefined
|
||||
const resolveVitePluginPWAAPI = (): VitePluginPWAAPI | undefined => {
|
||||
return vitePwaClientPlugin?.api
|
||||
|
@ -35,6 +37,19 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
|||
baseURL: '/',
|
||||
maxAge: 0,
|
||||
})
|
||||
if (options.disable) {
|
||||
addPlugin({ src: resolver.resolve('./runtime/pwa-plugin-stub.client') })
|
||||
}
|
||||
else {
|
||||
// Register PWA types
|
||||
nuxt.hook('prepare:types', ({ references }) => {
|
||||
references.push({ types: 'vite-plugin-pwa/info' })
|
||||
references.push({ types: 'vite-plugin-pwa/client' })
|
||||
})
|
||||
// Inject $pwa helper throughout app
|
||||
addPlugin({ src: resolver.resolve('./runtime/pwa-plugin.client') })
|
||||
}
|
||||
|
||||
// TODO: combine with configurePWAOptions?
|
||||
nuxt.hook('nitro:init', (nitro) => {
|
||||
options.outDir = nitro.options.output.publicDir
|
||||
|
|
7
modules/pwa/runtime/pwa-plugin-stub.client.ts
Normal file
7
modules/pwa/runtime/pwa-plugin-stub.client.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default defineNuxtPlugin(() => {
|
||||
return {
|
||||
provide: {
|
||||
pwa: {},
|
||||
},
|
||||
}
|
||||
})
|
120
modules/pwa/runtime/pwa-plugin.client.ts
Normal file
120
modules/pwa/runtime/pwa-plugin.client.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { useRegisterSW } from 'virtual:pwa-register/vue'
|
||||
import { STORAGE_KEY_PWA_HIDE_INSTALL } from '~/constants'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const online = useOnline()
|
||||
const registrationError = ref(false)
|
||||
const swActivated = ref(false)
|
||||
const showInstallPrompt = ref(false)
|
||||
const hideInstall = useLocalStorage(STORAGE_KEY_PWA_HIDE_INSTALL, false)
|
||||
|
||||
// https://thomashunter.name/posts/2021-12-11-detecting-if-pwa-twa-is-installed
|
||||
const ua = navigator.userAgent
|
||||
const ios = ua.match(/iPhone|iPad|iPod/)
|
||||
const standalone = window.matchMedia('(display-mode: standalone)').matches
|
||||
const isInstalled = !!(standalone || (ios && !ua.match(/Safari/)))
|
||||
|
||||
const registerPeriodicSync = (swUrl: string, r: ServiceWorkerRegistration) => {
|
||||
setInterval(async () => {
|
||||
if (!online.value)
|
||||
return
|
||||
|
||||
const resp = await fetch(swUrl, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'cache': 'no-store',
|
||||
'cache-control': 'no-cache',
|
||||
},
|
||||
})
|
||||
|
||||
if (resp?.status === 200)
|
||||
await r.update()
|
||||
}, 60 * 60 * 1000 /* 1 hour */)
|
||||
}
|
||||
|
||||
const {
|
||||
needRefresh, updateServiceWorker,
|
||||
} = useRegisterSW({
|
||||
immediate: true,
|
||||
onRegisterError() {
|
||||
registrationError.value = true
|
||||
},
|
||||
onRegisteredSW(swUrl, r) {
|
||||
// should add support in pwa plugin
|
||||
if (r?.active?.state === 'activated') {
|
||||
swActivated.value = true
|
||||
registerPeriodicSync(swUrl, r)
|
||||
}
|
||||
else if (r?.installing) {
|
||||
r.installing.addEventListener('statechange', (e) => {
|
||||
const sw = e.target as ServiceWorker
|
||||
swActivated.value = sw.state === 'activated'
|
||||
if (swActivated.value)
|
||||
registerPeriodicSync(swUrl, r)
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const close = async () => {
|
||||
needRefresh.value = false
|
||||
}
|
||||
|
||||
let install: () => Promise<void> = () => Promise.resolve()
|
||||
let cancelInstall: () => void = noop
|
||||
|
||||
if (!hideInstall.value) {
|
||||
type InstallPromptEvent = Event & {
|
||||
prompt: () => void
|
||||
userChoice: Promise<{ outcome: 'dismissed' | 'accepted' }>
|
||||
}
|
||||
|
||||
let deferredPrompt: InstallPromptEvent | undefined
|
||||
|
||||
const beforeInstallPrompt = (e: Event) => {
|
||||
e.preventDefault()
|
||||
deferredPrompt = e as InstallPromptEvent
|
||||
showInstallPrompt.value = true
|
||||
}
|
||||
window.addEventListener('beforeinstallprompt', beforeInstallPrompt)
|
||||
window.addEventListener('appinstalled', () => {
|
||||
deferredPrompt = undefined
|
||||
showInstallPrompt.value = false
|
||||
})
|
||||
|
||||
cancelInstall = () => {
|
||||
deferredPrompt = undefined
|
||||
showInstallPrompt.value = false
|
||||
window.removeEventListener('beforeinstallprompt', beforeInstallPrompt)
|
||||
hideInstall.value = true
|
||||
}
|
||||
|
||||
install = async () => {
|
||||
if (!showInstallPrompt.value || !deferredPrompt) {
|
||||
showInstallPrompt.value = false
|
||||
return
|
||||
}
|
||||
|
||||
showInstallPrompt.value = false
|
||||
await nextTick()
|
||||
deferredPrompt.prompt()
|
||||
await deferredPrompt.userChoice
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
pwa: reactive({
|
||||
isInstalled,
|
||||
showInstallPrompt,
|
||||
cancelInstall,
|
||||
install,
|
||||
swActivated,
|
||||
registrationError,
|
||||
needRefresh,
|
||||
updateServiceWorker,
|
||||
close,
|
||||
}),
|
||||
},
|
||||
}
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue