perf(web): preload the configured primary font #120

Merged
eleith merged 2 commits from perf/preload-primary-font into main 2026-11-02 23:25:04 +00:00
7 changed files with 97 additions and 2 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
'@when/web': patch
---
Preload the configured font's regular face from the initial HTML so it can download before stylesheet discovery. Respect custom font files and leave other weights and fallback fonts on demand. Treat inherited object-property names as unknown font families rather than failing page loads.
+42
View File
@@ -0,0 +1,42 @@
import { expect, test } from './support/test.ts';
test.use({ javaScriptEnabled: false });
test('HTML preloads only the regular configured font and CSS reuses its request', async ({
page
}) => {
const regularFont = '/assets/fonts/noto-sans/noto-sans-latin-400-normal.woff2';
const fontRequests: string[] = [];
page.on('request', (request) => {
const path = new URL(request.url()).pathname;
if (path.endsWith('.woff2')) fontRequests.push(path);
});
const fontResponse = page.waitForResponse(
(response) => new URL(response.url()).pathname === regularFont
);
await page.goto('/schedule/chat');
expect((await fontResponse).ok()).toBe(true);
const preload = page.locator('head link[rel="preload"][as="font"]');
await expect(preload).toHaveCount(1);
await expect(preload).toHaveAttribute('href', regularFont);
await expect(preload).toHaveAttribute('type', 'font/woff2');
await expect(preload).toHaveAttribute('crossorigin', 'anonymous');
const fontState = await page.evaluate(async () => {
await document.fonts.ready;
return {
bodyFontFamily: getComputedStyle(document.body).fontFamily,
regularFontLoaded: Array.from(document.fonts).some(
(face) =>
face.family.replace(/["']/g, '') === 'Noto Sans' &&
face.weight === '400' &&
face.status === 'loaded'
)
};
});
expect(fontState.bodyFontFamily).toMatch(/^["']?Noto Sans["']?(?:,|$)/);
expect(fontState.regularFontLoaded).toBe(true);
expect(fontRequests.filter((path) => path === regularFont)).toHaveLength(1);
expect(fontRequests.every((path) => path.startsWith('/assets/fonts/noto-sans/'))).toBe(true);
});
+31 -1
View File
@@ -1,5 +1,11 @@
import { expect, test } from 'vitest';
import { FALLBACK_FONT_NAME, bundledFontUrls, fontStack, isBundledFont } from './fonts';
import {
FALLBACK_FONT_NAME,
bundledFontUrls,
fontStack,
isBundledFont,
primaryFontUrl
} from './fonts';
test('isBundledFont matches families case-insensitively', () => {
expect(isBundledFont('Noto Sans')).toBe(true);
@@ -20,6 +26,30 @@ test('bundledFontUrls derives per-weight urls from the family name', () => {
expect(bundledFontUrls('Unknown')).toEqual([]);
});
test.each([
['Noto Sans', '/assets/fonts/noto-sans/noto-sans-latin-400-normal.woff2'],
['Lato', '/assets/fonts/lato/lato-latin-400-normal.woff2'],
['Outfit', '/assets/fonts/outfit/outfit-latin-400-normal.woff2'],
['INTER', '/assets/fonts/inter/inter-latin-400-normal.woff2'],
['My Font', null]
] as const)('primaryFontUrl preloads only the regular face for %s', (family, expected) => {
expect(primaryFontUrl(family)).toBe(expected);
});
test.each(['My Font', 'Noto Sans'])('a custom file takes precedence for %s', (family) => {
expect(primaryFontUrl(family, '/public/custom.woff2')).toBe('/public/custom.woff2');
});
test.each(['constructor', '__proto__'])(
'inherited object key %s is not a bundled family',
(family) => {
expect(isBundledFont(family)).toBe(false);
expect(bundledFontUrls(family)).toEqual([]);
expect(primaryFontUrl(family)).toBeNull();
expect(fontStack(family)).toBe(`${family}, ${FALLBACK_FONT_NAME}`);
}
);
test('fontStack backs only custom fonts with the fallback', () => {
expect(fontStack('Lato')).toBe('Lato');
expect(fontStack('My Font')).toBe(`My Font, ${FALLBACK_FONT_NAME}`);
+7 -1
View File
@@ -9,10 +9,11 @@ const BUNDLED_FONT_WEIGHTS: Record<string, number[]> = {
};
export function isBundledFont(family: string): boolean {
return family.toLowerCase() in BUNDLED_FONT_WEIGHTS;
return Object.hasOwn(BUNDLED_FONT_WEIGHTS, family.toLowerCase());
}
export function bundledFontUrls(family: string): string[] {
if (!isBundledFont(family)) return [];
const key = family.toLowerCase();
const slug = key.replaceAll(' ', '-');
return (BUNDLED_FONT_WEIGHTS[key] ?? []).map(
@@ -20,6 +21,11 @@ export function bundledFontUrls(family: string): string[] {
);
}
/** Preload only the configured face; leave other weights and fallbacks on demand. */
export function primaryFontUrl(family: string, customPath?: string): string | null {
return customPath ?? bundledFontUrls(family)[0] ?? null;
}
export function fontFamilies(family: string): string[] {
return isBundledFont(family) ? [family] : [family, FALLBACK_FONT_NAME];
}
@@ -12,6 +12,7 @@ const data: PageData = {
session: { expires: '2026-05-01T12:00:00Z' },
preferredTimezone: null,
appearance: validConfig.user.appearance,
fontPreloadUrl: null,
ogImage: 'https://book.example.com/og.png',
providers: [
{
+2
View File
@@ -1,4 +1,5 @@
import { getConfig } from '#lib/server/state.js';
import { primaryFontUrl } from '#lib/server/fonts.js';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ cookies }) => {
@@ -12,6 +13,7 @@ export const load: LayoutServerLoad = ({ cookies }) => {
title: cfg.user.appearance.title,
description: cfg.user.appearance.description
},
fontPreloadUrl: primaryFontUrl(cfg.user.appearance.font_name, cfg.user.appearance.font_path),
ogImage: `${cfg.url.app}${cfg.user.appearance.opengraph_path}`,
preferredTimezone: cookies.get('tz') ?? null
};
+9
View File
@@ -27,6 +27,15 @@
</script>
<svelte:head>
{#if data.fontPreloadUrl}
<link
rel="preload"
href={data.fontPreloadUrl}
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
{/if}
<link rel="icon" href={favicon} />
<meta name="description" content={data.appearance.description} />
<meta property="og:type" content="website" />