Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google fonts single request #42406

Merged
merged 5 commits into from Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 14 additions & 20 deletions packages/font/src/google/loader.ts
Expand Up @@ -49,27 +49,21 @@ const downloadGoogleFonts: FontLoader = async ({
)
}

let fontFaceDeclarations = ''
for (const weight of weights) {
for (const style of styles) {
const fontAxes = getFontAxes(
fontFamily,
weight,
style,
selectedVariableAxes
)
const url = getUrl(fontFamily, fontAxes, display)
const fontAxes = getFontAxes(
fontFamily,
weights,
styles,
selectedVariableAxes
)
const url = getUrl(fontFamily, fontAxes, display)

let cachedCssRequest = cssCache.get(url)
const fontFaceDeclaration =
cachedCssRequest ?? (await fetchCSSFromGoogleFonts(url, fontFamily))
if (!cachedCssRequest) {
cssCache.set(url, fontFaceDeclaration)
} else {
cssCache.delete(url)
}
fontFaceDeclarations += `${fontFaceDeclaration}\n`
}
let cachedCssRequest = cssCache.get(url)
const fontFaceDeclarations =
cachedCssRequest ?? (await fetchCSSFromGoogleFonts(url, fontFamily))
if (!cachedCssRequest) {
cssCache.set(url, fontFaceDeclarations)
} else {
cssCache.delete(url)
}

// Find font files to download
Expand Down
90 changes: 66 additions & 24 deletions packages/font/src/google/utils.ts
Expand Up @@ -126,25 +126,50 @@ export function validateData(functionName: string, data: any): FontOptions {

export function getUrl(
fontFamily: string,
axes: [string, string][],
axes: {
wght: string[]
ital: string[]
variableAxes?: [string, string][]
},
display: string
) {
// Variants are all combinations of weight and style, each variant will result in a separate font file
const variants: Array<[string, string][]> = []
for (const wgth of axes.wght) {
if (axes.ital.length === 0) {
variants.push([['wght', wgth], ...(axes.variableAxes ?? [])])
} else {
for (const ital of axes.ital) {
variants.push([
['ital', ital],
['wght', wgth],
...(axes.variableAxes ?? []),
])
}
}
}

// Google api requires the axes to be sorted, starting with lowercase words
axes.sort(([a], [b]) => {
const aIsLowercase = a.charCodeAt(0) > 96
const bIsLowercase = b.charCodeAt(0) > 96
if (aIsLowercase && !bIsLowercase) return -1
if (bIsLowercase && !aIsLowercase) return 1
if (axes.variableAxes) {
variants.forEach((variant) => {
variant.sort(([a], [b]) => {
const aIsLowercase = a.charCodeAt(0) > 96
const bIsLowercase = b.charCodeAt(0) > 96
if (aIsLowercase && !bIsLowercase) return -1
if (bIsLowercase && !aIsLowercase) return 1

return a > b ? 1 : -1
})
return a > b ? 1 : -1
})
})
}

return `https://fonts.googleapis.com/css2?family=${fontFamily.replace(
/ /g,
'+'
)}:${axes.map(([key]) => key).join(',')}@${axes
.map(([, val]) => val)
.join(',')}&display=${display}`
)}:${variants[0].map(([key]) => key).join(',')}@${variants
.map((variant) => variant.map(([, val]) => val).join(','))
.sort()
.join(';')}&display=${display}`
}

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

export function getFontAxes(
fontFamily: string,
weight: string,
style: string,
weights: string[],
styles: string[],
selectedVariableAxes?: string[]
): [string, string][] {
): {
wght: string[]
ital: string[]
variableAxes?: [string, string][]
} {
const allAxes: Array<{ tag: string; min: number; max: number }> = (
fontData as any
)[fontFamily].axes
const italicAxis: [string, string][] =
style === 'italic' ? [['ital', '1']] : []
const hasItalic = styles.includes('italic')
const hasNormal = styles.includes('normal')
const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : []

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

const variableAxes: [string, string][] = allAxes
.filter(
({ tag }) => tag === 'wght' || selectedVariableAxes?.includes(tag)
)
.map(({ tag, min, max }) => [tag, `${min}..${max}`])
let weightAxis: string
const variableAxes: [string, string][] = []
for (const { tag, min, max } of allAxes) {
if (tag === 'wght') {
weightAxis = `${min}..${max}`
} else if (selectedVariableAxes?.includes(tag)) {
variableAxes.push([tag, `${min}..${max}`])
}
}

return [...italicAxis, ...variableAxes]
return {
wght: [weightAxis!],
ital,
variableAxes,
}
} else {
return [...italicAxis, ['wght', weight]]
return {
ital,
wght: weights,
}
}
}
21 changes: 18 additions & 3 deletions test/e2e/next-font/app/pages/with-google-fonts.js
@@ -1,7 +1,16 @@
import { Fraunces, Indie_Flower } from '@next/font/google'
import { Fraunces, Indie_Flower, Roboto } from '@next/font/google'

const indieFlower = Indie_Flower({ weight: '400' })
const fraunces = Fraunces({ weight: '400' })
const indieFlower = Indie_Flower({ weight: '400', preload: false })
const fraunces = Fraunces({ weight: '400', preload: false })

const robotoMultiple = Roboto({
weight: ['900', '100'],
style: ['normal', 'italic'],
})
const frauncesMultiple = Fraunces({
style: ['italic', 'normal'],
axes: ['SOFT', 'WONK', 'opsz'],
})

export default function WithFonts() {
return (
Expand All @@ -12,6 +21,12 @@ export default function WithFonts() {
<div id="second-google-font" className={fraunces.className}>
{JSON.stringify(fraunces)}
</div>
<div id="multiple-roboto" className={robotoMultiple.className}>
{JSON.stringify(robotoMultiple)}
</div>
<div id="multiple-fraunces" className={frauncesMultiple.className}>
{JSON.stringify(frauncesMultiple)}
</div>
</>
)
}
31 changes: 31 additions & 0 deletions test/e2e/next-font/app/pages/with-local-fonts.js
Expand Up @@ -99,6 +99,31 @@ const robotoVar2 = localFont({
],
})

const robotoWithPreload = localFont({
src: [
{
path: '../fonts/roboto/roboto-100.woff2',
weight: '100',
style: 'normal',
},
{
path: '../fonts/roboto/roboto-900-italic.woff2',
weight: '900',
style: 'italic',
},
{
path: '../fonts/roboto/roboto-100.woff2',
weight: '100',
style: 'normal',
},
{
path: '../fonts/roboto/roboto-100-italic.woff2',
weight: '900',
style: 'italic',
},
],
})

export default function WithFonts() {
return (
<>
Expand All @@ -117,6 +142,12 @@ export default function WithFonts() {
<div id="roboto-local-font-var2" className={robotoVar2.className}>
{JSON.stringify(robotoVar2)}
</div>
<div
id="roboto-local-font-preload"
className={robotoWithPreload.className}
>
{JSON.stringify(robotoWithPreload)}
</div>
</>
)
}