feat: pwa with push notifications (#337)
This commit is contained in:
parent
a18e5e2332
commit
f0c91a3974
48 changed files with 2903 additions and 14 deletions
69
modules/pwa/config.ts
Normal file
69
modules/pwa/config.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import type { Nuxt } from '@nuxt/schema'
|
||||
import type { VitePWAOptions } from 'vite-plugin-pwa'
|
||||
import { resolve } from 'pathe'
|
||||
|
||||
export function configurePWAOptions(options: Partial<VitePWAOptions>, nuxt: Nuxt) {
|
||||
if (!options.outDir) {
|
||||
const publicDir = nuxt.options.nitro?.output?.publicDir
|
||||
options.outDir = publicDir ? resolve(publicDir) : resolve(nuxt.options.buildDir, '../.output/public')
|
||||
}
|
||||
|
||||
let config: Partial<
|
||||
import('workbox-build').BasePartial
|
||||
& import('workbox-build').GlobPartial
|
||||
& import('workbox-build').RequiredGlobDirectoryPartial
|
||||
>
|
||||
|
||||
if (options.strategies === 'injectManifest') {
|
||||
options.injectManifest = options.injectManifest ?? {}
|
||||
config = options.injectManifest
|
||||
}
|
||||
else {
|
||||
options.workbox = options.workbox ?? {}
|
||||
if (options.registerType === 'autoUpdate' && (options.injectRegister === 'script' || options.injectRegister === 'inline')) {
|
||||
options.workbox.clientsClaim = true
|
||||
options.workbox.skipWaiting = true
|
||||
}
|
||||
if (nuxt.options.dev) {
|
||||
// on dev force always to use the root
|
||||
|
||||
options.workbox.navigateFallback = nuxt.options.app.baseURL ?? '/'
|
||||
if (options.devOptions?.enabled && !options.devOptions.navigateFallbackAllowlist)
|
||||
options.devOptions.navigateFallbackAllowlist = [new RegExp(nuxt.options.app.baseURL) ?? /\//]
|
||||
}
|
||||
config = options.workbox
|
||||
// todo: change navigateFallback based on the command: use 404 only when using generate
|
||||
/* else if (nuxt.options.build) {
|
||||
if (!options.workbox.navigateFallback)
|
||||
options.workbox.navigateFallback = '/200.html'
|
||||
} */
|
||||
}
|
||||
if (!nuxt.options.dev)
|
||||
config.manifestTransforms = [createManifestTransform(nuxt.options.app.baseURL ?? '/')]
|
||||
}
|
||||
|
||||
function createManifestTransform(base: string): import('workbox-build').ManifestTransform {
|
||||
return async (entries) => {
|
||||
// prefix non html assets with base
|
||||
/*
|
||||
entries.filter(e => e && !e.url.endsWith('.html')).forEach((e) => {
|
||||
if (!e.url.startsWith(base))
|
||||
e.url = `${base}${e.url}`
|
||||
})
|
||||
*/
|
||||
entries.filter(e => e && e.url.endsWith('.html')).forEach((e) => {
|
||||
const url = e.url.startsWith('/') ? e.url.slice(1) : e.url
|
||||
if (url === 'index.html') {
|
||||
e.url = base
|
||||
}
|
||||
else {
|
||||
const parts = url.split('/')
|
||||
parts[parts.length - 1] = parts[parts.length - 1].replace(/\.html$/, '')
|
||||
// e.url = `${base}${parts.length > 1 ? parts.slice(0, parts.length - 1).join('/') : parts[0]}`
|
||||
e.url = parts.length > 1 ? parts.slice(0, parts.length - 1).join('/') : parts[0]
|
||||
}
|
||||
})
|
||||
|
||||
return { manifest: entries, warnings: [] }
|
||||
}
|
||||
}
|
83
modules/pwa/index.ts
Normal file
83
modules/pwa/index.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { defineNuxtModule } from '@nuxt/kit'
|
||||
import type { VitePluginPWAAPI } from 'vite-plugin-pwa'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import type { Plugin } from 'vite'
|
||||
import type { VitePWANuxtOptions } from './types'
|
||||
import { configurePWAOptions } from './config'
|
||||
|
||||
export * from './types'
|
||||
export default defineNuxtModule<VitePWANuxtOptions>({
|
||||
meta: {
|
||||
name: 'pwa',
|
||||
configKey: 'pwa',
|
||||
},
|
||||
defaults: nuxt => ({
|
||||
base: nuxt.options.app.baseURL,
|
||||
scope: nuxt.options.app.baseURL,
|
||||
}),
|
||||
async setup(options, nuxt) {
|
||||
let vitePwaClientPlugin: Plugin | undefined
|
||||
const resolveVitePluginPWAAPI = (): VitePluginPWAAPI | undefined => {
|
||||
return vitePwaClientPlugin?.api
|
||||
}
|
||||
|
||||
// TODO: combine with configurePWAOptions?
|
||||
nuxt.hook('nitro:init', (nitro) => {
|
||||
options.outDir = nitro.options.output.publicDir
|
||||
options.injectManifest = options.injectManifest || {}
|
||||
options.injectManifest.globDirectory = nitro.options.output.publicDir
|
||||
})
|
||||
nuxt.hook('vite:extend', ({ config }) => {
|
||||
const plugin = config.plugins?.find(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite-plugin-pwa')
|
||||
if (plugin)
|
||||
throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!')
|
||||
})
|
||||
nuxt.hook('vite:extendConfig', (viteInlineConfig, { isClient }) => {
|
||||
viteInlineConfig.plugins = viteInlineConfig.plugins || []
|
||||
const plugin = viteInlineConfig.plugins.find(p => p && typeof p === 'object' && 'name' in p && p.name === 'vite-plugin-pwa')
|
||||
if (plugin)
|
||||
throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!')
|
||||
|
||||
configurePWAOptions(options, nuxt)
|
||||
const plugins = VitePWA(options)
|
||||
viteInlineConfig.plugins.push(plugins)
|
||||
if (isClient)
|
||||
vitePwaClientPlugin = plugins.find(p => p.name === 'vite-plugin-pwa') as Plugin
|
||||
})
|
||||
|
||||
if (nuxt.options.dev) {
|
||||
const webManifest = `${nuxt.options.app.baseURL}${options.devOptions?.webManifestUrl ?? options.manifestFilename ?? 'manifest.webmanifest'}`
|
||||
const devSw = `${nuxt.options.app.baseURL}dev-sw.js?dev-sw`
|
||||
const workbox = `${nuxt.options.app.baseURL}workbox-`
|
||||
// @ts-expect-error just ignore
|
||||
const emptyHandle = (_req, _res, next) => {
|
||||
next()
|
||||
}
|
||||
nuxt.hook('vite:serverCreated', (viteServer, { isServer }) => {
|
||||
if (isServer)
|
||||
return
|
||||
|
||||
viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle })
|
||||
viteServer.middlewares.stack.push({ route: devSw, handle: emptyHandle })
|
||||
})
|
||||
if (!options.strategies || options.strategies === 'generateSW') {
|
||||
nuxt.hook('vite:serverCreated', (viteServer, { isServer }) => {
|
||||
if (isServer)
|
||||
return
|
||||
|
||||
viteServer.middlewares.stack.push({ route: workbox, handle: emptyHandle })
|
||||
})
|
||||
nuxt.hook('close', async () => {
|
||||
// todo: cleanup dev-dist folder
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
nuxt.hook('nitro:init', (nitro) => {
|
||||
nitro.hooks.hook('rollup:before', async () => {
|
||||
await resolveVitePluginPWAAPI()?.generateSW()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
12
modules/pwa/types.ts
Normal file
12
modules/pwa/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { VitePWAOptions } from 'vite-plugin-pwa'
|
||||
|
||||
export interface VitePWANuxtOptions extends Partial<VitePWAOptions> {}
|
||||
|
||||
declare module '@nuxt/schema' {
|
||||
interface NuxtConfig {
|
||||
pwa?: { [K in keyof VitePWANuxtOptions]?: Partial<VitePWANuxtOptions[K]> }
|
||||
}
|
||||
interface NuxtOptions {
|
||||
pwa: VitePWANuxtOptions
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue