@@ -4,12 +4,19 @@ import { type FontWeight, getDefaultFont } from '@gitbook/fonts';
44import { getFontSourcesToPreload } from '@/fonts/custom' ;
55import type { GitBookSiteContext } from '@/lib/context' ;
66import { filterOutNullable } from '@/lib/typescript' ;
7+ import QuickLRU from 'quick-lru' ;
78
89type 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+
1320export 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
8694async 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