import fs from 'node:fs'; import path from 'node:path'; import react from '@vitejs/plugin-react'; import sirv from 'sirv'; import type { Connect, Plugin, ResolvedConfig } from 'vite'; import { defineConfig } from 'vite'; /** * `static-contents/` holds the dumped site data (`modules/`, `rss.xml`, and the static `conferences/` and `hetero/` sites). * It sits next to the repository and is served from the site root next to * `public/`, but it is deliberately kept out of the bundle: it runs to * gigabytes of frozen data that the web server maps in directly. The dev and * preview servers mount it so that both behave like production. */ const staticContents = (): Plugin => { let dir: string; const mount = (middlewares: { use: (fn: Connect.NextHandleFunction) => void }, dev: boolean) => { if (fs.existsSync(dir)) { // maxAge 0 makes sirv send `Cache-Control: public, max-age=0`, so the // browser revalidates against the ETag instead of falling back to // heuristic caching. Without it a re-dump, or another site served // from the same localhost port, is answered from a stale cache. const serve = sirv(dir, { dev, etag: true, maxAge: 0 }); middlewares.use((req, res, next) => { // sirv decodes the path with decodeURI, which leaves reserved // characters such as %26 (&) and %23 (#) encoded, so a file like // `CFP&Reg.png` is not found. sirv reuses `req._parsedUrl` when // it was parsed from the same `req.url`, so hand it the path // decoded the way Apache resolves it. const raw = req.url ?? '/'; const idx = raw.search(/[?#]/); const pathname = idx === -1 ? raw : raw.slice(0, idx); if (pathname.includes('%')) { try { const decoded = pathname.split('/').map(decodeURIComponent).join('/'); if (!decoded.includes('%')) { (req as typeof req & { _parsedUrl?: object })._parsedUrl = { pathname: decoded, search: '', query: undefined, hash: undefined, raw, }; } } catch { // leave malformed escapes to sirv } } serve(req, res, next); }); } }; return { name: 'dynamicbrain:static-contents', configResolved(config: ResolvedConfig) { dir = path.resolve(config.root, '../static-contents'); }, configureServer(server) { mount(server.middlewares, true); }, configurePreviewServer(server) { mount(server.middlewares, false); }, }; }; // https://vite.dev/config/ export default defineConfig({ plugins: [react(), staticContents()], optimizeDeps: { exclude: ['fs'] }, build: { sourcemap: true, rolldownOptions: { external: ['fs'], output: { // react and react-dom are over 200 kB together, which would // push a single dependency chunk past the 500 kB warning // threshold. They also change far less often than the rest, // so split them off and let the two cache independently. codeSplitting: { groups: [ { name: 'vendor-react', test: /node_modules\/(react|react-dom|scheduler)\// }, { name: 'vendor-misc', test: /node_modules\// }, ], }, }, }, }, });