Files
nvrl a27d5de9c7 phone app: installable, opens offline, takes the share sheet onto the scratchpad, back lets go of one thing
A manifest with a 192, a 512 and a maskable icon, and a hand-written service
worker built beside the app by a small vite plugin: it keeps index.html, the
logo, the entry with its imports and the latin fonts, keeps each hashed asset
once fetched, asks the server for pages first and answers from the cache when
it is down, and never touches /api. The CSP names worker-src and manifest-src.

The share target posts to /share, which the worker catches: the text waits in
a cache and becomes a scratchpad note once someone is logged in, with the
drawer down on it. On a touch screen one guard entry in the history makes back
let go of one thing at a time — a prompt, the line, a sheet, the drawer, the
editor, a tile, then a folder up — and leave only at a space's root. The bar
and the band grow by the safe-area insets. The login no longer says the server
is unreachable once it answers again with the session gone.
2026-10-22 20:10:13 +02:00

74 lines
2.7 KiB
TypeScript

import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import type { Plugin, Rollup } from 'vite'
/*
* KaTeX lists each font as woff2, woff and ttf. Every browser the app runs in
* takes woff2, and the other two would be another megabyte in the binary.
*/
const katexWoff2Only: Plugin = {
name: 'katex-woff2-only',
enforce: 'pre',
transform(code, id) {
if (!/katex(\.min)?\.css$/.test(id)) return
return code.replace(/,\s*url\([^)]*\.(?:woff|ttf)\)\s*format\("(?:woff|truetype)"\)/g, '')
},
}
/*
* src/sw.ts, built beside the app as /sw.js — unhashed, since its address is
* what the browser remembers. Two names are written into it:
*
* __PRECACHE__ what index.html needs to paint: the entry, what it imports,
* their CSS, and the latin faces of the two fonts
* __VERSION__ a hash of every file name in the build, so a build that
* changes anything changes sw.js too, and the browser takes
* the new worker and drops the old shell
*/
const serviceWorker: Plugin = {
name: 'service-worker',
apply: 'build',
buildStart() {
this.emitFile({ type: 'chunk', id: '/src/sw.ts', fileName: 'sw.js' })
},
generateBundle(_, bundle) {
const chunks = Object.values(bundle).filter((file): file is Rollup.OutputChunk => file.type === 'chunk')
const entry = chunks.find((chunk) => chunk.isEntry && chunk.facadeModuleId?.endsWith('index.html'))
const sw = bundle['sw.js']
if (!entry || sw?.type !== 'chunk') return this.error('service worker: no entry, or no sw.js')
const shell = new Set<string>()
const walk = (name: string) => {
if (shell.has(name)) return
shell.add(name)
const chunk = bundle[name]
if (chunk?.type !== 'chunk') return
chunk.viteMetadata?.importedCss.forEach((css) => shell.add(css))
chunk.imports.forEach(walk)
}
walk(entry.fileName)
for (const name of Object.keys(bundle)) {
if (/\/(jetbrains-mono|noto-sans)-latin(-ext)?-wght-normal-[^/]+\.woff2$/.test(name)) shell.add(name)
}
// FNV-1a: only a change has to show, so nothing stronger is needed.
let hash = 0x811c9dc5
for (const char of Object.keys(bundle).sort().join('\n')) hash = Math.imul(hash ^ char.charCodeAt(0), 0x01000193)
const version = (hash >>> 0).toString(16)
sw.code = sw.code
.replaceAll('__VERSION__', JSON.stringify(version))
.replaceAll('__PRECACHE__', JSON.stringify([...shell].map((name) => `/${name}`)))
},
}
export default defineConfig({
plugins: [svelte(), katexWoff2Only, serviceWorker],
build: { target: 'es2022', chunkSizeWarningLimit: 900 },
server: {
port: 4243,
proxy: {
'/api': { target: 'http://127.0.0.1:4242', changeOrigin: false },
},
},
})