forked from Mirrors/elk
Compare commits
3 commits
main
...
userquin/c
Author | SHA1 | Date | |
---|---|---|---|
|
8552bc96f9 | ||
|
c189f951ff | ||
|
b85c1bacc2 |
7 changed files with 196 additions and 53 deletions
|
@ -11,6 +11,8 @@ export function setupPageHeader() {
|
||||||
return acc
|
return acc
|
||||||
}, {} as Record<string, Directions>)
|
}, {} as Record<string, Directions>)
|
||||||
|
|
||||||
|
const publicRuntimeConfig = useRuntimeConfig().public
|
||||||
|
|
||||||
useHeadFixed({
|
useHeadFixed({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: () => locale.value,
|
lang: () => locale.value,
|
||||||
|
@ -23,12 +25,17 @@ export function setupPageHeader() {
|
||||||
titleTemplate += ` (${buildInfo.env})`
|
titleTemplate += ` (${buildInfo.env})`
|
||||||
return titleTemplate
|
return titleTemplate
|
||||||
},
|
},
|
||||||
link: process.client && useRuntimeConfig().public.pwaEnabled
|
link: process.client && publicRuntimeConfig.pwaEnabled && !publicRuntimeConfig.tauriPlatform
|
||||||
? () => [{
|
? () => [{
|
||||||
key: 'webmanifest',
|
key: 'webmanifest',
|
||||||
rel: 'manifest',
|
rel: 'manifest',
|
||||||
href: `/manifest-${locale.value}${colorMode.value === 'dark' ? '-dark' : ''}.webmanifest`,
|
href: `/manifest-${locale.value}${colorMode.value === 'dark' ? '-dark' : ''}.webmanifest`,
|
||||||
}]
|
}]
|
||||||
: [],
|
: process.client && publicRuntimeConfig.pwaEnabled && publicRuntimeConfig.tauriPlatform
|
||||||
|
? () => [{
|
||||||
|
rel: 'manifest',
|
||||||
|
href: '/manifest.webmanifest',
|
||||||
|
}]
|
||||||
|
: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,34 @@ export const createI18n = async (): Promise<LocalizedWebManifest> => {
|
||||||
|
|
||||||
const defaultManifest: Required<WebManifestEntry> = pwa.webmanifest[env]
|
const defaultManifest: Required<WebManifestEntry> = pwa.webmanifest[env]
|
||||||
|
|
||||||
|
if (process.env.TAURI_PLATFORM) {
|
||||||
|
return {
|
||||||
|
'en-US': {
|
||||||
|
...defaultManifest,
|
||||||
|
lang: 'en-US',
|
||||||
|
dir: 'ltr',
|
||||||
|
scope: '/',
|
||||||
|
id: '/',
|
||||||
|
start_url: '/',
|
||||||
|
display: 'standalone',
|
||||||
|
background_color: '#ffffff',
|
||||||
|
theme_color: '#ffffff',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'pwa-192x192.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'pwa-512x512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const locales: RequiredWebManifestEntry[] = await Promise.all(
|
const locales: RequiredWebManifestEntry[] = await Promise.all(
|
||||||
pwaLocales
|
pwaLocales
|
||||||
.filter(l => l.code !== 'en-US')
|
.filter(l => l.code !== 'en-US')
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
||||||
return vitePwaClientPlugin?.api
|
return vitePwaClientPlugin?.api
|
||||||
}
|
}
|
||||||
let webmanifests: LocalizedWebManifest | undefined
|
let webmanifests: LocalizedWebManifest | undefined
|
||||||
|
const tauriPlatform = !!process.env.TAURI_PLATFORM
|
||||||
|
|
||||||
// TODO: combine with configurePWAOptions?
|
// TODO: combine with configurePWAOptions?
|
||||||
nuxt.hook('nitro:init', (nitro) => {
|
nuxt.hook('nitro:init', (nitro) => {
|
||||||
|
@ -41,50 +42,59 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
||||||
throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!')
|
throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!')
|
||||||
|
|
||||||
webmanifests = await createI18n()
|
webmanifests = await createI18n()
|
||||||
const generateManifest = (entry: string) => {
|
|
||||||
const manifest = webmanifests![entry]
|
|
||||||
if (!manifest)
|
|
||||||
throw new Error(`No webmanifest found for locale/theme ${entry}`)
|
|
||||||
return JSON.stringify(manifest)
|
|
||||||
}
|
|
||||||
viteInlineConfig.plugins.push({
|
|
||||||
name: 'elk:pwa:locales:build',
|
|
||||||
apply: 'build',
|
|
||||||
generateBundle(_, bundle) {
|
|
||||||
if (options.disable || !bundle)
|
|
||||||
return
|
|
||||||
|
|
||||||
Object.keys(webmanifests!).map(wm => [wm, `manifest-${wm}.webmanifest`]).forEach(([wm, fileName]) => {
|
if (tauriPlatform) {
|
||||||
bundle[fileName] = {
|
options.filename = 'tauri-sw.ts'
|
||||||
isAsset: true,
|
options.manifest = webmanifests['en-US']!
|
||||||
type: 'asset',
|
options.injectManifest = options.injectManifest || {}
|
||||||
name: undefined,
|
options.injectManifest.injectionPoint = undefined
|
||||||
source: generateManifest(wm),
|
}
|
||||||
fileName,
|
else {
|
||||||
}
|
const generateManifest = (entry: string) => {
|
||||||
})
|
const manifest = webmanifests![entry]
|
||||||
},
|
if (!manifest)
|
||||||
})
|
throw new Error(`No webmanifest found for locale/theme ${entry}`)
|
||||||
viteInlineConfig.plugins.push({
|
return JSON.stringify(manifest)
|
||||||
name: 'elk:pwa:locales:dev',
|
}
|
||||||
apply: 'serve',
|
viteInlineConfig.plugins.push({
|
||||||
configureServer(server) {
|
name: 'elk:pwa:locales:build',
|
||||||
const localeMatcher = new RegExp(`^${nuxt.options.app.baseURL}manifest-(.*).webmanifest$`)
|
apply: 'build',
|
||||||
server.middlewares.use((req, res, next) => {
|
generateBundle(_, bundle) {
|
||||||
const match = req.url?.match(localeMatcher)
|
if (options.disable || !bundle)
|
||||||
const entry = match && webmanifests![match[1]]
|
return
|
||||||
if (entry) {
|
|
||||||
res.statusCode = 200
|
Object.keys(webmanifests!).map(wm => [wm, `manifest-${wm}.webmanifest`]).forEach(([wm, fileName]) => {
|
||||||
res.setHeader('Content-Type', 'application/manifest+json')
|
bundle[fileName] = {
|
||||||
res.write(JSON.stringify(entry), 'utf-8')
|
isAsset: true,
|
||||||
res.end()
|
type: 'asset',
|
||||||
}
|
name: undefined,
|
||||||
else {
|
source: generateManifest(wm),
|
||||||
next()
|
fileName,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
viteInlineConfig.plugins.push({
|
||||||
|
name: 'elk:pwa:locales:dev',
|
||||||
|
apply: 'serve',
|
||||||
|
configureServer(server) {
|
||||||
|
const localeMatcher = new RegExp(`^${nuxt.options.app.baseURL}manifest-(.*).webmanifest$`)
|
||||||
|
server.middlewares.use((req, res, next) => {
|
||||||
|
const match = req.url?.match(localeMatcher)
|
||||||
|
const entry = match && webmanifests![match[1]]
|
||||||
|
if (entry) {
|
||||||
|
res.statusCode = 200
|
||||||
|
res.setHeader('Content-Type', 'application/manifest+json')
|
||||||
|
res.write(JSON.stringify(entry), 'utf-8')
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
configurePWAOptions(options, nuxt)
|
configurePWAOptions(options, nuxt)
|
||||||
const plugins = VitePWA(options)
|
const plugins = VitePWA(options)
|
||||||
|
@ -106,7 +116,7 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
||||||
return
|
return
|
||||||
|
|
||||||
viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle })
|
viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle })
|
||||||
if (webmanifests) {
|
if (webmanifests && !tauriPlatform) {
|
||||||
Object.keys(webmanifests).forEach((wm) => {
|
Object.keys(webmanifests).forEach((wm) => {
|
||||||
viteServer.middlewares.stack.push({
|
viteServer.middlewares.stack.push({
|
||||||
route: `${nuxt.options.app.baseURL}manifest-${wm}.webmanifest`,
|
route: `${nuxt.options.app.baseURL}manifest-${wm}.webmanifest`,
|
||||||
|
@ -131,6 +141,10 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
nuxt.hook('nitro:config', async (nitroConfig) => {
|
nuxt.hook('nitro:config', async (nitroConfig) => {
|
||||||
|
// /manifest.webmanifest added on nuxt config file
|
||||||
|
if (!tauriPlatform)
|
||||||
|
return
|
||||||
|
|
||||||
nitroConfig.routeRules = nitroConfig.routeRules || {}
|
nitroConfig.routeRules = nitroConfig.routeRules || {}
|
||||||
for (const locale of pwaLocales) {
|
for (const locale of pwaLocales) {
|
||||||
nitroConfig.routeRules![`/manifest-${locale.code}.webmanifest`] = {
|
nitroConfig.routeRules![`/manifest-${locale.code}.webmanifest`] = {
|
||||||
|
|
|
@ -15,7 +15,6 @@ export default defineNuxtModule({
|
||||||
if (nuxt.options.dev)
|
if (nuxt.options.dev)
|
||||||
nuxt.options.ssr = false
|
nuxt.options.ssr = false
|
||||||
|
|
||||||
nuxt.options.pwa.disable = true
|
|
||||||
nuxt.options.sourcemap.client = false
|
nuxt.options.sourcemap.client = false
|
||||||
|
|
||||||
nuxt.options.alias = {
|
nuxt.options.alias = {
|
||||||
|
@ -44,12 +43,6 @@ export default defineNuxtModule({
|
||||||
// cleanup files copied from the public folder that we don't need
|
// cleanup files copied from the public folder that we don't need
|
||||||
nuxt.hook('close', async () => {
|
nuxt.hook('close', async () => {
|
||||||
await rm('.output/public/_redirects')
|
await rm('.output/public/_redirects')
|
||||||
await rm('.output/public/apple-touch-icon.png')
|
|
||||||
await rm('.output/public/elk-og.png')
|
|
||||||
await rm('.output/public/favicon.ico')
|
|
||||||
await rm('.output/public/pwa-192x192.png')
|
|
||||||
await rm('.output/public/pwa-512x512.png')
|
|
||||||
await rm('.output/public/robots.txt')
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -92,6 +92,7 @@ export default defineNuxtConfig({
|
||||||
public: {
|
public: {
|
||||||
env: '', // set in build-env module
|
env: '', // set in build-env module
|
||||||
buildInfo: {} as BuildInfo, // set in build-env module
|
buildInfo: {} as BuildInfo, // set in build-env module
|
||||||
|
tauriPlatform: !!process.env.TAURI_PLATFORM,
|
||||||
pwaEnabled: !isDevelopment || process.env.VITE_DEV_PWA === 'true',
|
pwaEnabled: !isDevelopment || process.env.VITE_DEV_PWA === 'true',
|
||||||
// We use LibreTranslate(https://github.com/LibreTranslate/LibreTranslate) as our default translation server #76
|
// We use LibreTranslate(https://github.com/LibreTranslate/LibreTranslate) as our default translation server #76
|
||||||
translateApi: '',
|
translateApi: '',
|
||||||
|
|
|
@ -37,6 +37,9 @@ export default defineNuxtPlugin(() => {
|
||||||
registrationError.value = true
|
registrationError.value = true
|
||||||
},
|
},
|
||||||
onRegisteredSW(swUrl, r) {
|
onRegisteredSW(swUrl, r) {
|
||||||
|
if (useRuntimeConfig().public.tauriPlatform)
|
||||||
|
return
|
||||||
|
|
||||||
// should add support in pwa plugin
|
// should add support in pwa plugin
|
||||||
if (r?.active?.state === 'activated') {
|
if (r?.active?.state === 'activated') {
|
||||||
swActivated.value = true
|
swActivated.value = true
|
||||||
|
|
97
service-worker/tauri-sw.ts
Normal file
97
service-worker/tauri-sw.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/// <reference lib="WebWorker" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
import { clientsClaim } from 'workbox-core'
|
||||||
|
import { registerRoute } from 'workbox-routing'
|
||||||
|
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
|
||||||
|
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
|
||||||
|
import { ExpirationPlugin } from 'workbox-expiration'
|
||||||
|
import { onNotificationClick, onPush } from './web-push-notifications'
|
||||||
|
|
||||||
|
declare let self: ServiceWorkerGlobalScope
|
||||||
|
|
||||||
|
self.skipWaiting()
|
||||||
|
clientsClaim()
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
// Avoid caching on dev: force always go to the server
|
||||||
|
registerRoute(
|
||||||
|
() => true,
|
||||||
|
new NetworkFirst({
|
||||||
|
cacheName: 'elk-dev',
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({ statuses: [-1] }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.PROD) {
|
||||||
|
const denyList: RegExp[] = [/^\/api\//, /^\/login\//, /^\/oauth\//, /^\/signin\//, /^\/web-share-target\//]
|
||||||
|
const matchDenyList = (url: URL) => {
|
||||||
|
const pathnameAndSearch = url.pathname + url.search
|
||||||
|
for (const regExp of denyList) {
|
||||||
|
if (regExp.test(pathnameAndSearch))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Cache page navigations (html) with a Network First strategy
|
||||||
|
registerRoute(
|
||||||
|
({ sameOrigin, request, url }) => {
|
||||||
|
return sameOrigin && request.mode === 'navigate' && matchDenyList(url)
|
||||||
|
},
|
||||||
|
new NetworkFirst({
|
||||||
|
cacheName: 'elk-pages',
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({ statuses: [200] }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
|
||||||
|
registerRoute(
|
||||||
|
({ sameOrigin, request }) =>
|
||||||
|
sameOrigin && (request.destination === 'style'
|
||||||
|
|| request.destination === 'manifest'
|
||||||
|
|| request.destination === 'script'
|
||||||
|
|| request.destination === 'worker'),
|
||||||
|
new StaleWhileRevalidate({
|
||||||
|
cacheName: 'elk-assets',
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({ statuses: [200] }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// include shiki cache
|
||||||
|
registerRoute(
|
||||||
|
({ sameOrigin, url }) =>
|
||||||
|
sameOrigin && url.pathname.startsWith('/shiki/'),
|
||||||
|
new StaleWhileRevalidate({
|
||||||
|
cacheName: 'elk-shiki',
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({ statuses: [200] }),
|
||||||
|
// 365 days max
|
||||||
|
new ExpirationPlugin({ maxAgeSeconds: 60 * 60 * 24 * 365 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache images with a Cache First strategy
|
||||||
|
registerRoute(
|
||||||
|
({ sameOrigin, request }) =>
|
||||||
|
sameOrigin && request.destination === 'image',
|
||||||
|
new CacheFirst({
|
||||||
|
cacheName: 'elk-images',
|
||||||
|
plugins: [
|
||||||
|
new CacheableResponsePlugin({ statuses: [200] }),
|
||||||
|
// 150 max, 30 days max: purge on quota error
|
||||||
|
new ExpirationPlugin({ purgeOnQuotaError: true, maxEntries: 150, maxAgeSeconds: 60 * 60 * 24 * 30 }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('push', onPush)
|
||||||
|
self.addEventListener('notificationclick', onNotificationClick)
|
Loading…
Reference in a new issue