Files
fotospiel-app/vite.config.ts
Codex Agent 3e3a2c49d6 Implemented guest-only PWA using vite-plugin-pwa (the actual published package; @vite-pwa/plugin isn’t on npm) with
injectManifest, a new typed SW source, runtime caching, and a non‑blocking update toast with an action button. The
  guest shell now links a dedicated manifest and theme color, and background upload sync is managed in a single
  PwaManager component.

  Key changes (where/why)

  - vite.config.ts: added VitePWA injectManifest config, guest manifest, and output to /public so the SW can control /
    scope.
  - resources/js/guest/guest-sw.ts: new Workbox SW (precache + runtime caching for guest navigation, GET /api/v1/*,
    images, fonts) and preserves push/sync/notification logic.
  - resources/js/guest/components/PwaManager.tsx: registers SW, shows update/offline toasts, and processes the upload
    queue on sync/online.
  - resources/js/guest/components/ToastHost.tsx: action-capable toasts so update prompts can include a CTA.
  - resources/js/guest/i18n/messages.ts: added common.updateAvailable, common.updateAction, common.offlineReady.
  - resources/views/guest.blade.php: manifest + theme color + apple touch icon.
  - .gitignore: ignore generated public/guest-sw.js and public/guest.webmanifest; public/guest-sw.js removed since it’s
    now build output.
2025-12-27 10:59:44 +01:00

200 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { wayfinder } from '@laravel/vite-plugin-wayfinder';
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import laravel from 'laravel-vite-plugin';
import { defineConfig, type PluginOption } from 'vite';
import path from 'path';
import { tamaguiPlugin } from '@tamagui/vite-plugin';
import { sentryVitePlugin } from '@sentry/vite-plugin';
import { VitePWA } from 'vite-plugin-pwa';
const devServerHost = process.env.VITE_DEV_SERVER_HOST ?? 'fotospiel-app.test';
const devServerPort = Number.parseInt(process.env.VITE_DEV_SERVER_PORT ?? '5173', 10);
const devServerOrigin = process.env.VITE_DEV_SERVER_URL ?? `http://fotospiel-app.test:${devServerPort}`;
const parsedOrigin = new URL(devServerOrigin);
const hmrPort = parsedOrigin.port === '' ? devServerPort : Number.parseInt(parsedOrigin.port, 10);
const appUrl = process.env.APP_URL ?? 'http://fotospiel-app.test';
const sentryEnabled = Boolean(
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
process.env.SENTRY_URL
);
const plugins: PluginOption[] = [
laravel({
input: ['resources/css/app.css','resources/js/app.js', 'resources/js/app.tsx', 'resources/js/guest/main.tsx', 'resources/js/admin/main.tsx'],
ssr: 'resources/js/ssr.tsx',
refresh: [
'resources/views/**/*.blade.php',
'resources/lang/**/*.php',
'app/Http/Livewire/**', // falls genutzt
// NICHT beobachten: storage/logs, vendor, public/build, etc.
],
}),
react(),
tailwindcss(),
wayfinder({
formVariants: true,
}),
VitePWA({
strategies: 'injectManifest',
srcDir: 'resources/js/guest',
filename: 'guest-sw.ts',
manifestFilename: 'guest.webmanifest',
outDir: 'public',
injectRegister: null,
registerType: 'prompt',
includeAssets: [
'favicon.ico',
'favicon.svg',
'apple-touch-icon.png',
'logo-transparent-md.png',
'logo-transparent-lg.png',
],
injectManifest: {
globDirectory: 'public/build',
globPatterns: ['**/*.{js,css,woff,woff2,svg,png,webp,ico,txt}'],
},
manifest: {
name: 'Fotospiel',
short_name: 'Fotospiel',
id: '/event',
start_url: '/event',
scope: '/',
display: 'standalone',
lang: 'de-DE',
description: 'Offline-fähige Event-Galerie für Gäste Fotos aufnehmen, Aufgaben lösen und teilen.',
background_color: '#0f172a',
theme_color: '#ec4899',
orientation: 'portrait',
categories: ['photo-video', 'social'],
icons: [
{
src: '/favicon.svg',
sizes: 'any',
type: 'image/svg+xml',
purpose: 'any',
},
{
src: '/apple-touch-icon.png',
sizes: '166x169',
type: 'image/png',
purpose: 'any',
},
{
src: '/logo-transparent-lg.png',
sizes: '698x684',
type: 'image/png',
purpose: 'any',
},
],
},
devOptions: {
enabled: false,
},
}),
tamaguiPlugin({
config: './tamagui.config.ts',
components: ['@tamagui/core', '@tamagui/stacks', '@tamagui/text', '@tamagui/button'],
optimize: false,
disableExtraction: true,
}),
];
if (sentryEnabled) {
plugins.push(
sentryVitePlugin({
org: process.env.SENTRY_ORG as string,
project: process.env.SENTRY_PROJECT as string,
authToken: process.env.SENTRY_AUTH_TOKEN as string,
url: process.env.SENTRY_URL as string,
release: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
telemetry: false,
})
);
}
export default defineConfig({
server: {
host: devServerHost,
port: devServerPort,
strictPort: true,
origin: devServerOrigin,
hmr: {
host: parsedOrigin.hostname,
protocol: parsedOrigin.protocol.replace(':','') as 'http' | 'https',
clientPort: hmrPort,
},
fs: {
strict: true,
// Erlaube nur das App-Package (ggf. Pfade anpassen)
allow: [__dirname],
},
cors: {
origin: appUrl,
credentials: true,
},
watch: {
// WENIGER ist mehr: Alles ausklammern, was nicht für HMR nötig ist
ignored: [
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/coverage/**',
'**/.cache/**',
// Laravel-spezifisch
'**/public/build/**',
'**/storage/**',
'**/vendor/**',
'**/bootstrap/cache/**',
// Monorepo-Nachbarn
'../**/node_modules/**',
'../**/dist/**',
'../**/build/**',
'../**/coverage/**',
],
// Falls ihr auf gemounteten FS seid und Events fehlen:
// usePolling: true, interval: 500,
},
proxy: {
'/fonts': {
target: appUrl,
changeOrigin: true,
},
},
},
plugins,
esbuild: {
jsx: 'automatic',
},
optimizeDeps: {
// Bei großen Monorepos hilfreich:
entries: ['resources/js/**/*'],
exclude: [
// füge notfalls große/selten genutzte Pakete hinzu
],
include: [
'react-native-web',
'@tamagui/core',
'@tamagui/stacks',
'@tamagui/text',
'@tamagui/button',
],
},
define: {
'process.env.TAMAGUI_TARGET': JSON.stringify('web'),
},
// Build-Optionen wirken vor allem bei `vite build`, schaden aber nicht:
build: {
sourcemap: sentryEnabled,
target: 'es2020',
rollupOptions: {
// keine externen Monster-Globs
},
},
});