/** * Vite build configuration and PWA asset emission. * * The same per-build identifier is compiled into the browser bundle and the * service worker. This makes an hourly/visibility update check discover a new * deployment while still leaving activation under explicit user control. */ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { readFileSync } from 'node:fs' import { parse } from 'smol-toml' // A new URL makes the browser check and stage every deployed service worker. // The waiting worker is still activated explicitly by the user in the console. const pwaBuildId = Date.now().toString(36) const serviceWorkerSource = readFileSync(new URL('./pwa/control-sw.js', import.meta.url), 'utf8') const bundledFontLicenses = ['noto-serif-sc', 'zcool-xiaowei', 'lxgw-wenkai'].map(font => ({ fileName: `fonts/licenses/${font}-OFL.txt`, source: readFileSync( new URL(`./node_modules/@fontsource/${font}/LICENSE`, import.meta.url), 'utf8', ), })) const i18nResource = parse( readFileSync(new URL('../../resources/i18n.toml', import.meta.url), 'utf8'), ) as unknown as { default_language: string locales: Record }> } const defaultLanguage = i18nResource.default_language const defaultMessages = i18nResource.locales[defaultLanguage].messages const manifest = { id: '/control', name: defaultMessages['pwa.manifest.name'], short_name: defaultMessages['pwa.manifest.short_name'], description: defaultMessages['pwa.manifest.description'], lang: defaultLanguage, dir: 'auto', start_url: '/control/?source=pwa', scope: '/control/', display: 'standalone', orientation: 'any', background_color: '#03121a', theme_color: '#0a302f', categories: ['utilities', 'productivity'], icons: [ { src: '/pwa/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, { src: '/pwa/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, { src: '/pwa/icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable', }, ], } export default defineConfig({ plugins: [ react(), { name: 'same-origin-woff2-fonts', enforce: 'pre', transform(source, id) { if (!id.includes('/node_modules/@fontsource/') || !id.endsWith('.css')) return return source .replaceAll('font-display: swap', 'font-display: block') .replace(/,\s*url\([^)]*\.woff\) format\(['"]woff['"]\)/g, '') }, }, { name: 'control-pwa-assets', transformIndexHtml(html) { return html .replaceAll('__DEFAULT_LANGUAGE__', defaultLanguage) .replaceAll('__APP_NAME__', defaultMessages['pwa.manifest.name']) .replaceAll('__APP_SHORT_NAME__', defaultMessages['pwa.manifest.short_name']) .replaceAll('__APP_DESCRIPTION__', defaultMessages['pwa.manifest.description']) }, generateBundle() { for (const license of bundledFontLicenses) { this.emitFile({ type: 'asset', ...license }) } this.emitFile({ type: 'asset', fileName: 'control/sw.js', source: serviceWorkerSource.replaceAll('__PWA_BUILD_ID__', pwaBuildId), }) this.emitFile({ type: 'asset', fileName: 'control/manifest.webmanifest', source: JSON.stringify(manifest, null, 2), }) }, }, ], define: { __PWA_BUILD_ID__: JSON.stringify(pwaBuildId), __I18N_RESOURCE__: JSON.stringify(i18nResource), }, })