Skip to content

Commit c06ad70

Browse files
authored
Improve fonts loading (#3682)
1 parent 0ef5dc8 commit c06ad70

File tree

1 file changed

+46
-35
lines changed

1 file changed

+46
-35
lines changed

packages/gitbook/src/lib/imageFonts.ts

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import { type FontWeight, getDefaultFont } from '@gitbook/fonts';
44
import { getFontSourcesToPreload } from '@/fonts/custom';
55
import type { GitBookSiteContext } from '@/lib/context';
66
import { filterOutNullable } from '@/lib/typescript';
7+
import QuickLRU from 'quick-lru';
78

89
type ComputeFontsInput = {
910
regularText: string;
1011
boldText: string;
1112
};
1213

14+
// Google fonts are more likely to be reused, so we keep a larger cache
15+
const googleFontsCache = new QuickLRU<string, unknown>({ maxSize: 50 });
16+
17+
// Custom fonts are less likely to be reused, so we keep a smaller cache
18+
const customFontsCache = new QuickLRU<string, unknown>({ maxSize: 10 });
19+
1320
export async function computeImageFonts(
1421
customization: GitBookSiteContext['customization'],
1522
input: ComputeFontsInput
@@ -65,17 +72,18 @@ async function loadGoogleFont(input: {
6572

6673
// If we found a font file, load it
6774
if (lookup) {
68-
return getWithCache(`google-font-files:${lookup.url}`, async () => {
75+
return getWithCache(googleFontsCache, lookup.url, async () => {
6976
const response = await fetch(lookup.url);
70-
if (response.ok) {
71-
const data = await response.arrayBuffer();
72-
return {
73-
name: lookup.font,
74-
data,
75-
style: 'normal' as const,
76-
weight: input.weight,
77-
};
77+
if (!response.ok) {
78+
throw new Error(`Failed to load font from ${lookup.url}: ${response.statusText}`);
7879
}
80+
const data = await response.arrayBuffer();
81+
return {
82+
name: lookup.font,
83+
data,
84+
style: 'normal' as const,
85+
weight: input.weight,
86+
};
7987
});
8088
}
8189

@@ -85,39 +93,42 @@ async function loadGoogleFont(input: {
8593

8694
async function loadCustomFont(input: { url: string; weight: 400 | 700 }) {
8795
const { url, weight } = input;
88-
const response = await fetch(url);
89-
if (!response.ok) {
90-
return null;
91-
}
96+
return getWithCache(customFontsCache, `${url}:${weight}`, async () => {
97+
const response = await fetch(url);
9298

93-
const data = await response.arrayBuffer();
99+
if (!response.ok) {
100+
throw new Error(`Failed to load custom font from ${url}: ${response.statusText}`);
101+
}
94102

95-
return {
96-
name: 'CustomFont',
97-
data,
98-
style: 'normal' as const,
99-
weight,
100-
} as const;
101-
}
103+
const data = await response.arrayBuffer();
102104

103-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
104-
const staticCache = new Map<string, any>();
105+
return {
106+
name: 'CustomFont',
107+
data,
108+
style: 'normal' as const,
109+
weight,
110+
} as const;
111+
});
112+
}
105113

106-
async function getWithCache<T>(key: string, fn: () => Promise<T>) {
107-
const cached = staticCache.get(key) as Promise<T>;
114+
/**
115+
* Simple in-memory cache to avoid loading the same font multiple times.
116+
*/
117+
function getWithCache<T>(
118+
cache: QuickLRU<string, unknown>,
119+
key: string,
120+
fn: () => Promise<T>
121+
): Promise<T> {
122+
const cached = cache.get(key);
108123
if (cached) {
109-
return cached;
124+
return cached as Promise<T>;
110125
}
111126

112-
const promise = fn();
113-
staticCache.set(key, promise);
114-
115-
try {
116-
const result = await promise;
117-
return result;
118-
} catch (error) {
127+
const promise = fn().catch((error) => {
119128
// Remove the failed promise from cache so it can be retried
120-
staticCache.delete(key);
129+
cache.delete(key);
121130
throw error;
122-
}
131+
});
132+
cache.set(key, promise);
133+
return promise;
123134
}

0 commit comments

Comments
 (0)