Skip to content

Commit 8fa78a5

Browse files
author
Hannes Bornö
authored
Google fonts single request (#42406)
Make a single request when using several weights and/or styles for google fonts instead of one for each variation. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
1 parent 6edeb9d commit 8fa78a5

File tree

7 files changed

+501
-121
lines changed

7 files changed

+501
-121
lines changed

packages/font/src/google/loader.ts

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,21 @@ const downloadGoogleFonts: FontLoader = async ({
4949
)
5050
}
5151

52-
let fontFaceDeclarations = ''
53-
for (const weight of weights) {
54-
for (const style of styles) {
55-
const fontAxes = getFontAxes(
56-
fontFamily,
57-
weight,
58-
style,
59-
selectedVariableAxes
60-
)
61-
const url = getUrl(fontFamily, fontAxes, display)
52+
const fontAxes = getFontAxes(
53+
fontFamily,
54+
weights,
55+
styles,
56+
selectedVariableAxes
57+
)
58+
const url = getUrl(fontFamily, fontAxes, display)
6259

63-
let cachedCssRequest = cssCache.get(url)
64-
const fontFaceDeclaration =
65-
cachedCssRequest ?? (await fetchCSSFromGoogleFonts(url, fontFamily))
66-
if (!cachedCssRequest) {
67-
cssCache.set(url, fontFaceDeclaration)
68-
} else {
69-
cssCache.delete(url)
70-
}
71-
fontFaceDeclarations += `${fontFaceDeclaration}\n`
72-
}
60+
let cachedCssRequest = cssCache.get(url)
61+
const fontFaceDeclarations =
62+
cachedCssRequest ?? (await fetchCSSFromGoogleFonts(url, fontFamily))
63+
if (!cachedCssRequest) {
64+
cssCache.set(url, fontFaceDeclarations)
65+
} else {
66+
cssCache.delete(url)
7367
}
7468

7569
// Find font files to download

packages/font/src/google/utils.ts

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -126,25 +126,50 @@ export function validateData(functionName: string, data: any): FontOptions {
126126

127127
export function getUrl(
128128
fontFamily: string,
129-
axes: [string, string][],
129+
axes: {
130+
wght: string[]
131+
ital: string[]
132+
variableAxes?: [string, string][]
133+
},
130134
display: string
131135
) {
136+
// Variants are all combinations of weight and style, each variant will result in a separate font file
137+
const variants: Array<[string, string][]> = []
138+
for (const wgth of axes.wght) {
139+
if (axes.ital.length === 0) {
140+
variants.push([['wght', wgth], ...(axes.variableAxes ?? [])])
141+
} else {
142+
for (const ital of axes.ital) {
143+
variants.push([
144+
['ital', ital],
145+
['wght', wgth],
146+
...(axes.variableAxes ?? []),
147+
])
148+
}
149+
}
150+
}
151+
132152
// Google api requires the axes to be sorted, starting with lowercase words
133-
axes.sort(([a], [b]) => {
134-
const aIsLowercase = a.charCodeAt(0) > 96
135-
const bIsLowercase = b.charCodeAt(0) > 96
136-
if (aIsLowercase && !bIsLowercase) return -1
137-
if (bIsLowercase && !aIsLowercase) return 1
153+
if (axes.variableAxes) {
154+
variants.forEach((variant) => {
155+
variant.sort(([a], [b]) => {
156+
const aIsLowercase = a.charCodeAt(0) > 96
157+
const bIsLowercase = b.charCodeAt(0) > 96
158+
if (aIsLowercase && !bIsLowercase) return -1
159+
if (bIsLowercase && !aIsLowercase) return 1
138160

139-
return a > b ? 1 : -1
140-
})
161+
return a > b ? 1 : -1
162+
})
163+
})
164+
}
141165

142166
return `https://fonts.googleapis.com/css2?family=${fontFamily.replace(
143167
/ /g,
144168
'+'
145-
)}:${axes.map(([key]) => key).join(',')}@${axes
146-
.map(([, val]) => val)
147-
.join(',')}&display=${display}`
169+
)}:${variants[0].map(([key]) => key).join(',')}@${variants
170+
.map((variant) => variant.map(([, val]) => val).join(','))
171+
.sort()
172+
.join(';')}&display=${display}`
148173
}
149174

150175
export async function fetchCSSFromGoogleFonts(url: string, fontFamily: string) {
@@ -192,17 +217,23 @@ export async function fetchFontFile(url: string) {
192217

193218
export function getFontAxes(
194219
fontFamily: string,
195-
weight: string,
196-
style: string,
220+
weights: string[],
221+
styles: string[],
197222
selectedVariableAxes?: string[]
198-
): [string, string][] {
223+
): {
224+
wght: string[]
225+
ital: string[]
226+
variableAxes?: [string, string][]
227+
} {
199228
const allAxes: Array<{ tag: string; min: number; max: number }> = (
200229
fontData as any
201230
)[fontFamily].axes
202-
const italicAxis: [string, string][] =
203-
style === 'italic' ? [['ital', '1']] : []
231+
const hasItalic = styles.includes('italic')
232+
const hasNormal = styles.includes('normal')
233+
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : []
204234

205-
if (weight === 'variable') {
235+
// Weights will always contain one element if it's a variable font
236+
if (weights[0] === 'variable') {
206237
if (selectedVariableAxes) {
207238
const defineAbleAxes: string[] = allAxes
208239
.map(({ tag }) => tag)
@@ -228,14 +259,25 @@ export function getFontAxes(
228259
})
229260
}
230261

231-
const variableAxes: [string, string][] = allAxes
232-
.filter(
233-
({ tag }) => tag === 'wght' || selectedVariableAxes?.includes(tag)
234-
)
235-
.map(({ tag, min, max }) => [tag, `${min}..${max}`])
262+
let weightAxis: string
263+
const variableAxes: [string, string][] = []
264+
for (const { tag, min, max } of allAxes) {
265+
if (tag === 'wght') {
266+
weightAxis = `${min}..${max}`
267+
} else if (selectedVariableAxes?.includes(tag)) {
268+
variableAxes.push([tag, `${min}..${max}`])
269+
}
270+
}
236271

237-
return [...italicAxis, ...variableAxes]
272+
return {
273+
wght: [weightAxis!],
274+
ital,
275+
variableAxes,
276+
}
238277
} else {
239-
return [...italicAxis, ['wght', weight]]
278+
return {
279+
ital,
280+
wght: weights,
281+
}
240282
}
241283
}
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import { Fraunces, Indie_Flower } from '@next/font/google'
1+
import { Fraunces, Indie_Flower, Roboto } from '@next/font/google'
22

3-
const indieFlower = Indie_Flower({ weight: '400' })
4-
const fraunces = Fraunces({ weight: '400' })
3+
const indieFlower = Indie_Flower({ weight: '400', preload: false })
4+
const fraunces = Fraunces({ weight: '400', preload: false })
5+
6+
const robotoMultiple = Roboto({
7+
weight: ['900', '100'],
8+
style: ['normal', 'italic'],
9+
})
10+
const frauncesMultiple = Fraunces({
11+
style: ['italic', 'normal'],
12+
axes: ['SOFT', 'WONK', 'opsz'],
13+
})
514

615
export default function WithFonts() {
716
return (
@@ -12,6 +21,12 @@ export default function WithFonts() {
1221
<div id="second-google-font" className={fraunces.className}>
1322
{JSON.stringify(fraunces)}
1423
</div>
24+
<div id="multiple-roboto" className={robotoMultiple.className}>
25+
{JSON.stringify(robotoMultiple)}
26+
</div>
27+
<div id="multiple-fraunces" className={frauncesMultiple.className}>
28+
{JSON.stringify(frauncesMultiple)}
29+
</div>
1530
</>
1631
)
1732
}

test/e2e/next-font/app/pages/with-local-fonts.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,31 @@ const robotoVar2 = localFont({
9999
],
100100
})
101101

102+
const robotoWithPreload = localFont({
103+
src: [
104+
{
105+
path: '../fonts/roboto/roboto-100.woff2',
106+
weight: '100',
107+
style: 'normal',
108+
},
109+
{
110+
path: '../fonts/roboto/roboto-900-italic.woff2',
111+
weight: '900',
112+
style: 'italic',
113+
},
114+
{
115+
path: '../fonts/roboto/roboto-100.woff2',
116+
weight: '100',
117+
style: 'normal',
118+
},
119+
{
120+
path: '../fonts/roboto/roboto-100-italic.woff2',
121+
weight: '900',
122+
style: 'italic',
123+
},
124+
],
125+
})
126+
102127
export default function WithFonts() {
103128
return (
104129
<>
@@ -117,6 +142,12 @@ export default function WithFonts() {
117142
<div id="roboto-local-font-var2" className={robotoVar2.className}>
118143
{JSON.stringify(robotoVar2)}
119144
</div>
145+
<div
146+
id="roboto-local-font-preload"
147+
className={robotoWithPreload.className}
148+
>
149+
{JSON.stringify(robotoWithPreload)}
150+
</div>
120151
</>
121152
)
122153
}

0 commit comments

Comments
 (0)