// After `vite build`: // 1. Make the shell's asset URLs relative. In hash-router mode Kit emits them absolute, // which would break the page anywhere but the domain root. // 2. Compute what the home page really downloads from this origin and stamp it into // build/index.html, which the giant number reads. Google Fonts are not counted. import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; import { join, relative } from 'node:path'; const root = new URL('../', import.meta.url).pathname; const build = join(root, 'build'); const generatedNodes = join(root, '.svelte-kit/generated/build/client-optimized/nodes'); const manifest = JSON.parse(readFileSync(join(root, '.svelte-kit/output/client/.vite/manifest.json'), 'utf8')); const PLACEHOLDER = '__TP_DOWNLOAD_BYTES__'; const indexPath = join(build, 'index.html'); const shell = readFileSync(indexPath, 'utf8').replaceAll('"/_app/', '"./_app/'); if (!shell.includes(PLACEHOLDER)) throw new Error('download-size placeholder not found in build/index.html'); if (shell.includes('"/_app/')) throw new Error('absolute asset URLs left in build/index.html'); function walk(dir) { return readdirSync(dir).flatMap((name) => { const path = join(dir, name); return statSync(path).isDirectory() ? walk(path) : [path]; }); } const keyByFile = new Map(Object.entries(manifest).map(([key, entry]) => [entry.file, key])); const files = new Set(); const seen = new Set(); function visit(key) { if (seen.has(key)) return; seen.add(key); const entry = manifest[key]; if (!entry) throw new Error(`manifest entry missing: ${key}`); files.add(entry.file); for (const css of entry.css ?? []) files.add(css); for (const dep of entry.imports ?? []) visit(dep); } // Everything the shell preloads or loads, with its static imports. const shellRefs = [...shell.matchAll(/["'](\.\/_app\/[^"']+)["']/g)].map((m) => m[1].slice(2)); if (shellRefs.length === 0) throw new Error('no asset references found in the shell'); for (const ref of new Set(shellRefs)) { if (keyByFile.has(ref)) visit(keyByFile.get(ref)); else files.add(ref); // stylesheet } // The layout, error and home page nodes; the benchmark route is split out and not downloaded by the app. const nodeSources = readdirSync(generatedNodes).map((file) => [file, readFileSync(join(generatedNodes, file), 'utf8')]); const appNodes = nodeSources.filter(([, source]) => !source.includes('routes/bench/')); if (!appNodes.some(([, source]) => source.includes('routes/+page.svelte'))) throw new Error('could not find the home page node'); for (const [file] of appNodes) visit(`.svelte-kit/generated/build/client-optimized/nodes/${file}`); // The synthesis worker; the benchmark worker is not part of the app. const workers = walk(join(build, '_app/immutable/workers')).filter((path) => !path.includes('bench-worker')); if (workers.length !== 1) throw new Error(`expected one app worker, found ${workers.length}`); files.add(relative(build, workers[0])); const fixed = ['_app/version.json', 'player-worklet.js', ...['encoder', 'decoder', 'conditioning', 'vocoder_step'].map((f) => `models/${f}.onnx`), 'models/cmudict.dict.gz']; const others = [...files, ...fixed].reduce((sum, file) => sum + statSync(join(build, file)).size, 0); // index.html contains its own size, so iterate until the digit count settles. let total = others; for (let i = 0; i < 4; i++) total = others + Buffer.byteLength(shell.replace(PLACEHOLDER, String(total))); writeFileSync(indexPath, shell.replace(PLACEHOLDER, String(total))); writeFileSync(join(root, '.svelte-kit/download-files.json'), JSON.stringify(['index.html', ...files, ...fixed].sort(), null, 1)); console.log(`download size: ${total} bytes (${(total / 1e6).toFixed(1)} MB) across ${files.size + fixed.length + 1} files`);