Files
movieloop-frontend/vite.config.ts
T
TehRiehlDeal da32e49b0b
frontend-ci / lint (push) Successful in 24s
frontend-ci / test (push) Successful in 32s
frontend-ci / secrets-scan (push) Successful in 4s
frontend-ci / sast (push) Successful in 6s
frontend-ci / fs-scan (push) Successful in 11s
frontend-ci / typecheck (push) Successful in 14s
frontend-ci / build (push) Successful in 38s
frontend-ci / push (push) Skipped
frontend-ci / test (pull_request) Successful in 34s
frontend-ci / secrets-scan (pull_request) Successful in 4s
frontend-ci / sast (pull_request) Successful in 6s
frontend-ci / fs-scan (pull_request) Successful in 13s
frontend-ci / typecheck (pull_request) Successful in 23s
frontend-ci / lint (pull_request) Successful in 26s
frontend-ci / build (pull_request) Successful in 48s
frontend-ci / push (pull_request) Skipped
fix: push subscription payload; CSP hash computed from the shipped page (2.2.3)
Enabling push in production failed with "Validation failed": the
browser's PushSubscription.toJSON() carries expirationTime, and it was
sent verbatim to a schema that is strict about unknown fields. The
client now sends exactly endpoint + keys. The test doubles had been
shaped like the schema rather than like a browser, which is why 1,112
tests said nothing.

The CSP hash for index.html's inline theme script was hardcoded in the
container's start-up script behind a comment asking for it to be
regenerated by hand, and claiming a test asserted it; none did. The
bytes are what the browser hashes, so an edit or a checkout with
different line endings would have silently blocked the page's own
script. The hash is now emitted by `npm run build` from the index.html
that is actually shipped, copied into the image, and read at start; the
container refuses to start without it. Verified against the built
image: header hash equals served-script hash.

For the record: a CSP console message in production is what led here,
and the served page turned out to be correct - that message was a
browser extension's injected script, which the policy is right to
refuse.
2026-09-12 10:22:14 -07:00

99 lines
3.4 KiB
TypeScript

/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'
// Vendor groups for manualChunks. Each changes on its own cadence, so each
// gets its own cache entry.
const REACT = new Set(['react', 'react-dom', 'scheduler', 'use-sync-external-store']);
const NET = new Set([
'axios',
'socket.io-client',
'socket.io-parser',
'engine.io-client',
'engine.io-parser',
]);
const UI = new Set(['lucide-react', 'sonner', 'tailwind-merge', 'clsx', 'class-variance-authority']);
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
build: {
rollupOptions: {
output: {
// Vendor code is split into chunks that change only when a dependency
// does. Without this, every deploy of app code changed the hash of the
// one chunk that also held React, the router and the UI kit, and every
// returning visitor re-downloaded ~170 KB gzipped of libraries that had
// not changed. The first load is not smaller - the dependency set is
// the load - but the second visit after a deploy fetches only the app
// chunk. Route code is split separately via React.lazy in App.tsx.
manualChunks(id) {
// Vite normalises module ids to forward slashes on every platform.
const pkg = /\/node_modules\/((?:@[^/]+\/)?[^/]+)\//.exec(id)?.[1];
if (!pkg) return undefined;
if (REACT.has(pkg)) return 'vendor-react';
if (pkg === 'react-router') return 'vendor-router';
if (NET.has(pkg)) return 'vendor-net';
if (UI.has(pkg) || pkg.startsWith('@base-ui/') || pkg.startsWith('@floating-ui/')) {
return 'vendor-ui';
}
return 'vendor';
},
},
},
},
server: {
// Bind-mounted source on Docker Desktop (Windows/macOS) does not deliver
// inotify events, so Vite never sees host edits and silently serves stale
// modules. Polling is opt-in via the compose file so native dev is unaffected.
watch: process.env.VITE_USE_POLLING
? { usePolling: true, interval: 300 }
: undefined,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
'/socket.io': {
target: 'http://localhost:3000',
ws: true,
changeOrigin: true,
},
},
},
test: {
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.{test,spec}.{ts,tsx}', 'scripts/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json-summary'],
// Source only. Types, the entry point, generated UI primitives and the
// tests themselves are not behaviour this suite is responsible for.
include: ['src/**/*.{ts,tsx}'],
exclude: [
'src/**/*.{test,spec}.{ts,tsx}',
'src/test/**',
'src/types/**',
'src/main.tsx',
'src/vite-env.d.ts',
],
// The bar is 90 for both lines and branches. A file may be excepted
// down to 85 only where 90 is genuinely unattainable, with the reason
// recorded beside the override. See WP-14 in the audit plan.
thresholds: {
lines: 90,
branches: 90,
functions: 90,
statements: 90,
},
},
},
})