perf(sprites): ship 192px sprites and a manifest of every sprite

Sprites were shipped at 512x512 but render at 44-64px in the box grid.
Resizing to 192px halves each file (median 15.5KB -> 7.6KB); a full dex
of artwork drops from 16.6MB to 7.6MB.

The sprite build now also writes static/sprites-small/manifest.json,
listing every sprite (all forms, shiny and female variants) with its
size, so the offline worker can save the complete set and report what
is missing. A unit test keeps it in step with the files on disk.
This commit is contained in:
Josh Creek
2026-09-14 16:19:22 +01:00
parent b7d2db4959
commit 721c44dcd0
3178 changed files with 107 additions and 5 deletions
+35
View File
@@ -0,0 +1,35 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
// @ts-expect-error - plain ESM build script without type declarations
import { buildSpriteManifest } from '../../scripts/sprite-manifest.mjs';
import { resolveSpriteUrl, spriteRoot } from '$lib/utils/spriteUrl';
const spritesDir = path.join(process.cwd(), 'static', 'sprites-small');
describe('sprite manifest', () => {
const committed = JSON.parse(readFileSync(path.join(spritesDir, 'manifest.json'), 'utf8'));
it('lists exactly the sprite files on disk, with their sizes', async () => {
// "Save all artwork" downloads this list, so a stale manifest would miss or 404 sprites.
expect(committed).toEqual(await buildSpriteManifest(spritesDir));
});
it('covers forms, shiny and female variants', () => {
const files: string[] = committed.files.map(([file]: [string, number]) => file);
expect(files.some((file) => file.startsWith('shiny/'))).toBe(true);
expect(files.some((file) => file.startsWith('female/'))).toBe(true);
expect(files.some((file) => file.startsWith('shiny/female/'))).toBe(true);
// Forms use either a "<number>-<form>" key or a 10000+ national-style number.
expect(files.some((file) => /^\d+-[a-z0-9-]+\.webp$/.test(file))).toBe(true);
expect(files.some((file) => /^10\d{3}\.webp$/.test(file))).toBe(true);
});
it('uses the same folder that sprite URLs resolve into', () => {
for (const useLocal of [true, false]) {
expect(
resolveSpriteUrl({ pokedexNumber: 25, form: '', spriteKey: '25' }, true, useLocal)
).toBe(`${spriteRoot(useLocal)}/shiny/25.webp`);
}
});
});