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
|
||||
}, {} as Record<string, Directions>)
|
||||
|
||||
const publicRuntimeConfig = useRuntimeConfig().public
|
||||
|
||||
useHeadFixed({
|
||||
htmlAttrs: {
|
||||
lang: () => locale.value,
|
||||
|
@ -23,12 +25,17 @@ export function setupPageHeader() {
|
|||
titleTemplate += ` (${buildInfo.env})`
|
||||
return titleTemplate
|
||||
},
|
||||
link: process.client && useRuntimeConfig().public.pwaEnabled
|
||||
link: process.client && publicRuntimeConfig.pwaEnabled && !publicRuntimeConfig.tauriPlatform
|
||||
? () => [{
|
||||
key: 'webmanifest',
|
||||
rel: 'manifest',
|
||||
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]
|
||||
|
||||
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(
|
||||
pwaLocales
|
||||
.filter(l => l.code !== 'en-US')
|
||||
|
|
|
@ -22,6 +22,7 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
|||
return vitePwaClientPlugin?.api
|
||||
}
|
||||
let webmanifests: LocalizedWebManifest | undefined
|
||||
const tauriPlatform = !!process.env.TAURI_PLATFORM
|
||||
|
||||
// TODO: combine with configurePWAOptions?
|
||||
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!')
|
||||
|
||||
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]) => {
|
||||
bundle[fileName] = {
|
||||
isAsset: true,
|
||||
type: 'asset',
|
||||
name: undefined,
|
||||
source: generateManifest(wm),
|
||||
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()
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
if (tauriPlatform) {
|
||||
options.filename = 'tauri-sw.ts'
|
||||
options.manifest = webmanifests['en-US']!
|
||||
options.injectManifest = options.injectManifest || {}
|
||||
options.injectManifest.injectionPoint = undefined
|
||||
}
|
||||
else {
|
||||
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]) => {
|
||||
bundle[fileName] = {
|
||||
isAsset: true,
|
||||
type: 'asset',
|
||||
name: undefined,
|
||||
source: generateManifest(wm),
|
||||
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)
|
||||
const plugins = VitePWA(options)
|
||||
|
@ -106,7 +116,7 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
|||
return
|
||||
|
||||
viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle })
|
||||
if (webmanifests) {
|
||||
if (webmanifests && !tauriPlatform) {
|
||||
Object.keys(webmanifests).forEach((wm) => {
|
||||
viteServer.middlewares.stack.push({
|
||||
route: `${nuxt.options.app.baseURL}manifest-${wm}.webmanifest`,
|
||||
|
@ -131,6 +141,10 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
|||
}
|
||||
else {
|
||||
nuxt.hook('nitro:config', async (nitroConfig) => {
|
||||
// /manifest.webmanifest added on nuxt config file
|
||||
if (!tauriPlatform)
|
||||
return
|
||||
|
||||
nitroConfig.routeRules = nitroConfig.routeRules || {}
|
||||
for (const locale of pwaLocales) {
|
||||
nitroConfig.routeRules![`/manifest-${locale.code}.webmanifest`] = {
|
||||
|
|
|
@ -15,7 +15,6 @@ export default defineNuxtModule({
|
|||
if (nuxt.options.dev)
|
||||
nuxt.options.ssr = false
|
||||
|
||||
nuxt.options.pwa.disable = true
|
||||
nuxt.options.sourcemap.client = false
|
||||
|
||||
nuxt.options.alias = {
|
||||
|
@ -44,12 +43,6 @@ export default defineNuxtModule({
|
|||
// cleanup files copied from the public folder that we don't need
|
||||
nuxt.hook('close', async () => {
|
||||
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: {
|
||||
env: '', // 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',
|
||||
// We use LibreTranslate(https://github.com/LibreTranslate/LibreTranslate) as our default translation server #76
|
||||
translateApi: '',
|
||||
|
|
|
@ -37,6 +37,9 @@ export default defineNuxtPlugin(() => {
|
|||
registrationError.value = true
|
||||
},
|
||||
onRegisteredSW(swUrl, r) {
|
||||
if (useRuntimeConfig().public.tauriPlatform)
|
||||
return
|
||||
|
||||
// should add support in pwa plugin
|
||||
if (r?.active?.state === 'activated') {
|
||||
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