File size: 3,808 Bytes
3ec6b9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 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`);