feat: add PWA support and mobile-responsive layouts
- Add web app manifest, maskable/apple-touch icons, and a production-only service worker that caches the app shell for offline start - Improve mobile UX: responsive breakpoints from ~320px, 44px touch targets, safe-area insets, dvh heights, 16px inputs to avoid iOS zoom - Serve .webmanifest with correct content type in the Go static handler - Document PWA behavior and mobile features in the README
This commit is contained in:
parent
f220d96e3b
commit
cbc03c56bf
18 changed files with 277 additions and 9 deletions
|
|
@ -1,8 +1,95 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, type Plugin } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// Service Worker als Build-Artefakt: Die Cache-Version ist der Build-Zeitstempel,
|
||||
// damit Browser nach jedem Deploy den SW aktualisieren und alte Caches räumen.
|
||||
const SW_SOURCE = `
|
||||
const VERSION = 'wp-__SW_VERSION__';
|
||||
const CACHE_NAME = 'wannpassts-' + VERSION;
|
||||
const PRECACHE = [
|
||||
'/',
|
||||
'/manifest.webmanifest',
|
||||
'/favicon.svg',
|
||||
'/icons/icon-192.png',
|
||||
'/icons/icon-512.png',
|
||||
'/icons/icon-maskable-512.png',
|
||||
'/icons/apple-touch-icon.png',
|
||||
];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil((async () => {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
await cache.addAll(PRECACHE).catch(() => {});
|
||||
await self.skipWaiting();
|
||||
})());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil((async () => {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)));
|
||||
await self.clients.claim();
|
||||
})());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
const url = new URL(req.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
// API-Aufrufe immer live – niemals aus dem Cache bedienen.
|
||||
if (url.pathname.startsWith('/api/')) return;
|
||||
|
||||
// Navigation: Netzwerk zuerst, offline App-Shell aus dem Cache.
|
||||
if (req.mode === 'navigate') {
|
||||
event.respondWith((async () => {
|
||||
try {
|
||||
const res = await fetch(req);
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.put('/', res.clone());
|
||||
return res;
|
||||
} catch (err) {
|
||||
const cached = (await caches.match(req)) || (await caches.match('/'));
|
||||
if (cached) return cached;
|
||||
return new Response('Offline – bitte Internetverbindung prüfen.', {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||
});
|
||||
}
|
||||
})());
|
||||
return;
|
||||
}
|
||||
|
||||
// Statische Assets: Cache zuerst (gehashte Namen sind unveränderlich).
|
||||
event.respondWith((async () => {
|
||||
const cached = await caches.match(req, { ignoreSearch: true });
|
||||
if (cached) return cached;
|
||||
const res = await fetch(req);
|
||||
if (res.ok) {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
cache.put(req, res.clone());
|
||||
}
|
||||
return res;
|
||||
})());
|
||||
});
|
||||
`
|
||||
|
||||
function serviceWorkerPlugin(): Plugin {
|
||||
return {
|
||||
name: 'wannpassts-sw',
|
||||
apply: 'build',
|
||||
generateBundle() {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: 'sw.js',
|
||||
source: SW_SOURCE.replace('__SW_VERSION__', new Date().toISOString()),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [vue(), serviceWorkerPlugin()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue