perf: self-host fonts, drop unused generated code and add swf tests

- Self-host Nunito and Pixelify Sans via next/font instead of Google Fonts CDN
  (removes render-blocking external stylesheets and preconnects)
- Remove stale mariadb entry from serverExternalPackages (app uses mysql2)
- Exclude unused src/generated Prisma client from typecheck and remove it
- Add unit tests for swf-parser, effectmap and figuremap (0% coverage -> 90%+)
This commit is contained in:
openhands committed 2026-08-09 12:12:02 +02:00
1 parent ae1393d3e3
commit 4993b75608
7 files changed
+330 -23

No files matched your search

+1 -1
View File
@@ -36,7 +36,7 @@ const securityHeaders = [
const nextConfig: NextConfig = {
cacheComponents: true, // <-- Dit activeert Cache Components en lost de 'instant' build-fout op!
deploymentId: resolveDeploymentId(),
serverExternalPackages: ["mariadb", "lzma", "sharp", "pino", "pino-pretty"],
serverExternalPackages: ["lzma", "sharp", "pino", "pino-pretty"],
// Onderdruk de specifieke Turbopack waarschuwingen voor brede bestandspatronen in de lib-map
turbopack: {
-6
View File
@@ -4,12 +4,6 @@
@plugin "tailwindcss-animate";
@custom-variant dark (&:where(.dark, .dark *));
:root {
--font-nunito:
"Nunito", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--font-pixel: "Pixelify Sans", monospace;
}
@theme {
--font-sans:
var(--font-nunito), "Nunito", ui-sans-serif, system-ui, -apple-system,
+20 -15
View File
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import { Nunito, Pixelify_Sans } from "next/font/google";
import { headers } from "next/headers";
import Script from "next/script";
import { NextIntlClientProvider } from "next-intl";
@@ -14,6 +15,20 @@ import { resolveHotelName } from "@/lib/hotel-name";
import { siteSettings } from "@/lib/services/site-settings";
import "./globals.css";
const nunito = Nunito({
subsets: ["latin"],
weight: ["400", "600", "700", "800"],
variable: "--font-nunito",
display: "swap",
});
const pixelify = Pixelify_Sans({
subsets: ["latin"],
weight: ["400", "700"],
variable: "--font-pixel",
display: "swap",
});
// The root layout reads runtime data (locale cookie, request headers via
// enforceSiteAccess, DB-backed site settings) to render the <html> element,
// which cannot be wrapped in <Suspense>. Child pages inherit this opt-out,
@@ -58,23 +73,13 @@ export default async function RootLayout({
const nonce = (await headers()).get("x-nonce") ?? undefined;
return (
<html lang={locale} className="app" suppressHydrationWarning>
<html
lang={locale}
className={`app ${nunito.variable} ${pixelify.variable}`}
suppressHydrationWarning
>
<head>
<meta name="theme-default-dark" content={String(defaultDark)} />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400;700&display=swap"
/>
<Script
src="/scripts/theme-init.js"
strategy="beforeInteractive"
+85
View File
@@ -0,0 +1,85 @@
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { EffectMapEntry } from "@/types/effects";
import {
__setEffectMapPathForTests,
listLocalEffects,
mergeEffect,
readEffectMap,
removeEffect,
} from "./effectmap";
vi.mock("@/lib/services/site-settings", () => ({
siteSettings: { get: vi.fn(async () => "") },
}));
let dir: string;
let file: string;
const ENTRY: EffectMapEntry = {
id: "fx_1",
lib: "fx_1",
type: "fx",
revision: 1,
};
beforeEach(async () => {
dir = await fs.mkdtemp(path.join(os.tmpdir(), "effectmap-"));
file = path.join(dir, "EffectMap.json");
await fs.writeFile(file, JSON.stringify({ effects: [ENTRY] }));
__setEffectMapPathForTests(file);
});
afterEach(async () => {
__setEffectMapPathForTests(null);
await fs.rm(dir, { recursive: true, force: true });
});
describe("lib/services/effectmap", () => {
it("lists local effects from the map", async () => {
const effects = await listLocalEffects();
expect(effects).toEqual([ENTRY]);
});
it("returns an empty list when the file has no effects array", async () => {
await fs.writeFile(file, JSON.stringify({}));
expect(await readEffectMap()).toEqual({ effects: [] });
});
it("adds a new effect via mergeEffect", async () => {
const added: EffectMapEntry = {
id: "fx_2",
lib: "fx_2",
type: "fx",
revision: 2,
};
await mergeEffect(added);
const effects = await listLocalEffects();
expect(effects).toHaveLength(2);
expect(effects).toContainEqual(added);
});
it("updates an existing effect in place via mergeEffect", async () => {
await mergeEffect({ ...ENTRY, revision: 5 });
const effects = await listLocalEffects();
expect(effects).toHaveLength(1);
expect(effects[0].revision).toBe(5);
});
it("removes an effect by id", async () => {
expect(await removeEffect("fx_1")).toBe(true);
expect(await listLocalEffects()).toEqual([]);
});
it("removes an effect by lib name", async () => {
expect(await removeEffect("fx_1")).toBe(true);
expect(await listLocalEffects()).toEqual([]);
});
it("reports false when nothing was removed", async () => {
expect(await removeEffect("missing")).toBe(false);
expect(await listLocalEffects()).toHaveLength(1);
});
});
+62
View File
@@ -0,0 +1,62 @@
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { FigureLibrary } from "./figuremap";
import {
__setFigureMapPathForTests,
listFigureLibraries,
mergeLibrary,
} from "./figuremap";
vi.mock("@/lib/services/site-settings", () => ({
siteSettings: { get: vi.fn(async () => "") },
}));
let dir: string;
let file: string;
const LIB: FigureLibrary = {
id: "h_human",
revision: 1,
parts: [{ id: 1, type: "hd" }],
};
beforeEach(async () => {
dir = await fs.mkdtemp(path.join(os.tmpdir(), "figuremap-"));
file = path.join(dir, "FigureMap.json");
await fs.writeFile(file, JSON.stringify({ libraries: [LIB] }));
__setFigureMapPathForTests(file);
});
afterEach(async () => {
__setFigureMapPathForTests(null);
await fs.rm(dir, { recursive: true, force: true });
});
describe("lib/services/figuremap", () => {
it("lists figure libraries from the map", async () => {
const libraries = await listFigureLibraries();
expect(libraries).toEqual([LIB]);
});
it("returns an empty list when the file has no libraries array", async () => {
await fs.writeFile(file, JSON.stringify({}));
expect(await listFigureLibraries()).toEqual([]);
});
it("adds a new library via mergeLibrary", async () => {
const added: FigureLibrary = { id: "hh_human", revision: 2 };
await mergeLibrary(added);
const libraries = await listFigureLibraries();
expect(libraries).toHaveLength(2);
expect(libraries).toContainEqual(added);
});
it("updates an existing library in place via mergeLibrary", async () => {
await mergeLibrary({ ...LIB, revision: 9 });
const libraries = await listFigureLibraries();
expect(libraries).toHaveLength(1);
expect(libraries[0].revision).toBe(9);
});
});
+161
View File
@@ -0,0 +1,161 @@
import { deflateSync } from "node:zlib";
import LZMA from "lzma";
import { describe, expect, it } from "vitest";
import {
decompressSwf,
parseBinaryData,
parseSwfTags,
parseSymbolClass,
TAG_DEFINE_BINARY_DATA,
TAG_DEFINE_BITS_LOSSLESS,
TAG_END,
} from "./swf-parser";
const BODY = Buffer.from(
"uncompressed swf body used for decompression fixtures, padded to a reasonable length",
);
function fwsHeader(payload: Buffer, version = 6): Buffer {
const header = Buffer.alloc(8);
header.write("FWS", 0, "ascii");
header.writeUInt8(version, 3);
header.writeUInt32LE(payload.length + 8, 4);
return Buffer.concat([header, payload]);
}
function cwsFixture(payload: Buffer, version = 6): Buffer {
const header = Buffer.alloc(8);
header.write("CWS", 0, "ascii");
header.writeUInt8(version, 3);
header.writeUInt32LE(payload.length + 8, 4);
const compressed = deflateSync(payload);
return Buffer.concat([header, compressed]);
}
function zwsFixture(payload: Buffer, version = 6): Buffer {
const rawLzma = LZMA.compress(payload, 9);
const props = Buffer.from(rawLzma.slice(0, 5));
const compressedPayload = Buffer.from(rawLzma.slice(13));
const compressedSize = 5 + compressedPayload.length;
const header = Buffer.alloc(12);
header.write("ZWS", 0, "ascii");
header.writeUInt8(version, 3);
header.writeUInt32LE(payload.length + 8, 4);
header.writeUInt32LE(compressedSize, 8);
return Buffer.concat([header, props, compressedPayload]);
}
describe("decompressSwf", () => {
it("returns FWS buffers untouched", () => {
const fws = fwsHeader(BODY);
expect(decompressSwf(fws)).toBe(fws);
});
it("inflates CWS (zlib) buffers and restores the FWS signature", () => {
const result = decompressSwf(cwsFixture(BODY));
expect(result.toString("ascii", 0, 3)).toBe("FWS");
expect(result.length).toBe(BODY.length + 8);
expect(result.subarray(8).equals(BODY)).toBe(true);
});
it("decompresses ZWS (lzma) buffers and restores the FWS signature", () => {
const result = decompressSwf(zwsFixture(BODY));
expect(result.toString("ascii", 0, 3)).toBe("FWS");
expect(result.length).toBe(BODY.length + 8);
expect(result.subarray(8).equals(BODY)).toBe(true);
});
it("throws on unknown signatures", () => {
const bad = Buffer.alloc(16);
bad.write("XYZ", 0, "ascii");
expect(() => decompressSwf(bad)).toThrow("Unknown SWF signature");
});
});
function shortTag(tagType: number, data: Buffer): Buffer {
expect(data.length).toBeLessThan(0x3f);
const tag = Buffer.alloc(2 + data.length);
tag.writeUInt16LE((tagType << 6) | data.length, 0);
data.copy(tag, 2);
return tag;
}
function swfWithTags(tags: Buffer[]): Buffer {
const tagStream = Buffer.concat([...tags, Buffer.from([0, 0])]);
const body = Buffer.alloc(13);
body.write("FWS", 0, "ascii");
body.writeUInt8(6, 3);
body.writeUInt32LE(tagStream.length + 13, 4);
return Buffer.concat([body, tagStream]);
}
describe("parseSwfTags", () => {
it("parses short-form tags until the end marker", () => {
const swf = swfWithTags([
shortTag(TAG_DEFINE_BITS_LOSSLESS, Buffer.from([1, 2, 3])),
shortTag(TAG_DEFINE_BINARY_DATA, Buffer.from([9, 9, 9])),
]);
const tags = parseSwfTags(swf);
expect(tags).toHaveLength(2);
expect(tags[0].type).toBe(TAG_DEFINE_BITS_LOSSLESS);
expect(tags[0].data.equals(Buffer.from([1, 2, 3]))).toBe(true);
expect(tags[1].type).toBe(TAG_DEFINE_BINARY_DATA);
});
it("stops at TAG_END and ignores trailing bytes", () => {
const swf = swfWithTags([
shortTag(TAG_DEFINE_BITS_LOSSLESS, Buffer.from([1])),
]);
expect(parseSwfTags(swf)).toHaveLength(1);
});
it("returns an empty array for an empty tag stream", () => {
const empty = Buffer.alloc(13);
empty.write("FWS", 0, "ascii");
expect(parseSwfTags(empty)).toEqual([]);
expect(TAG_END).toBe(0);
});
});
describe("parseSymbolClass", () => {
it("maps char ids to names and tracks aliases", () => {
const data = Buffer.concat([
Buffer.from([2, 0]),
Buffer.from([1, 0]),
Buffer.from("chair\0", "utf8"),
Buffer.from([2, 0]),
Buffer.from("table\0", "utf8"),
]);
const result = parseSymbolClass(data);
expect(result.classNames.get(1)).toBe("chair");
expect(result.classNames.get(2)).toBe("table");
expect(result.symbols.get("chair")).toBe(1);
expect(result.symbols.get("table")).toBe(2);
});
it("keeps the first name for a repeated char id", () => {
const data = Buffer.concat([
Buffer.from([2, 0]),
Buffer.from([1, 0]),
Buffer.from("chair\0", "utf8"),
Buffer.from([1, 0]),
Buffer.from("lounge_chair\0", "utf8"),
]);
const result = parseSymbolClass(data);
expect(result.classNames.get(1)).toBe("chair");
expect(result.symbols.get("lounge_chair")).toBe(1);
});
});
describe("parseBinaryData", () => {
it("reads the char id and strips the 6-byte attribute header", () => {
const data = Buffer.concat([
Buffer.from([42, 0]),
Buffer.from([1, 2, 3, 4]),
Buffer.from("binary payload"),
]);
const result = parseBinaryData(data);
expect(result.id).toBe(42);
expect(result.content.equals(Buffer.from("binary payload"))).toBe(true);
});
});
+1 -1
View File
@@ -32,5 +32,5 @@
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules", "e2e", "playwright.config.ts"]
"exclude": ["node_modules", "e2e", "playwright.config.ts", "src/generated"]
}