diff --git a/packages/font/src/google/loader.ts b/packages/font/src/google/loader.ts
index b835c439ade0b6e..a4692077d364b66 100644
--- a/packages/font/src/google/loader.ts
+++ b/packages/font/src/google/loader.ts
@@ -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
diff --git a/packages/font/src/google/utils.ts b/packages/font/src/google/utils.ts
index 5060ebcc29fa8cb..16baec887f9030b 100644
--- a/packages/font/src/google/utils.ts
+++ b/packages/font/src/google/utils.ts
@@ -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) {
@@ -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)
@@ -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,
+ }
}
}
diff --git a/test/e2e/next-font/app/pages/with-google-fonts.js b/test/e2e/next-font/app/pages/with-google-fonts.js
index cd6428a4ec3a544..39ede912aa31abe 100644
--- a/test/e2e/next-font/app/pages/with-google-fonts.js
+++ b/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 (
@@ -12,6 +21,12 @@ export default function WithFonts() {
{JSON.stringify(fraunces)}
+
+ {JSON.stringify(robotoMultiple)}
+
+
+ {JSON.stringify(frauncesMultiple)}
+
>
)
}
diff --git a/test/e2e/next-font/app/pages/with-local-fonts.js b/test/e2e/next-font/app/pages/with-local-fonts.js
index ac78e3065b26e7a..37f218859d243c3 100644
--- a/test/e2e/next-font/app/pages/with-local-fonts.js
+++ b/test/e2e/next-font/app/pages/with-local-fonts.js
@@ -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 (
<>
@@ -117,6 +142,12 @@ export default function WithFonts() {
{JSON.stringify(robotoVar2)}
+
+ {JSON.stringify(robotoWithPreload)}
+
>
)
}
diff --git a/test/e2e/next-font/google-font-mocked-responses.js b/test/e2e/next-font/google-font-mocked-responses.js
index 40b9da03a2d1e67..f1fa0f4f3843c2e 100644
--- a/test/e2e/next-font/google-font-mocked-responses.js
+++ b/test/e2e/next-font/google-font-mocked-responses.js
@@ -543,4 +543,314 @@ module.exports = {
)}) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}`,
+ 'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1;1,9..144,100..900,0..100,0..1&display=optional': `
+ /* vietnamese */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: italic;
+ font-weight: 100 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUT8FyLNQOQZAnv9ZwNpOQkzP9Ddt2Wew.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: italic;
+ font-weight: 100 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUT8FyLNQOQZAnv9ZwNpOUkzP9Ddt2Wew.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: italic;
+ font-weight: 100 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUT8FyLNQOQZAnv9ZwNpOskzP9Ddt0.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUV8FyLNQOQZAnv9ZwHlOkuy91BRtw.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUV8FyLNQOQZAnv9ZwGlOkuy91BRtw.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Fraunces';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/fraunces/v24/6NUV8FyLNQOQZAnv9ZwIlOkuy91B.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+ `,
+ 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,900;1,100;1,900&display=optional': `
+ /* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEz0dL-vwnYh2eg.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEzQdL-vwnYh2eg.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEzwdL-vwnYh2eg.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEzMdL-vwnYh2eg.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEz8dL-vwnYh2eg.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEz4dL-vwnYh2eg.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrEzAdL-vwnYg.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc3CsTYl4BOQ3o.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc-CsTYl4BOQ3o.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc2CsTYl4BOQ3o.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc5CsTYl4BOQ3o.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc1CsTYl4BOQ3o.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc0CsTYl4BOQ3o.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto';
+ font-style: italic;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOjCnqEu92Fr1Mu51TLBCc6CsTYl4BO.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxFIzIXKMnyrYk.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxMIzIXKMnyrYk.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxEIzIXKMnyrYk.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxLIzIXKMnyrYk.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxHIzIXKMnyrYk.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxGIzIXKMnyrYk.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 100;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxIIzIXKMny.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfCRc4AMP6lbBP.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfABc4AMP6lbBP.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfCBc4AMP6lbBP.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBxc4AMP6lbBP.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfCxc4AMP6lbBP.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfChc4AMP6lbBP.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 900;
+ font-display: optional;
+ src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBBc4AMP6lQ.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+ `,
}
diff --git a/test/e2e/next-font/index.test.ts b/test/e2e/next-font/index.test.ts
index 8768d8a966a4f45..d4a933b524bf4fb 100644
--- a/test/e2e/next-font/index.test.ts
+++ b/test/e2e/next-font/index.test.ts
@@ -335,23 +335,43 @@ describe('@next/font/google', () => {
expect($('link[rel="preconnect"]').length).toBe(0)
// Preload
- expect($('link[as="font"]').length).toBe(2)
- // _app
- expect($('link[as="font"]').get(0).attribs).toEqual({
- as: 'font',
- crossorigin: 'anonymous',
- href: '/_next/static/media/0812efcfaefec5ea.p.woff2',
- rel: 'preload',
- type: 'font/woff2',
- })
- // with-local-fonts
- expect($('link[as="font"]').get(1).attribs).toEqual({
- as: 'font',
- crossorigin: 'anonymous',
- href: '/_next/static/media/ab6fdae82d1a8d92.p.woff2',
- rel: 'preload',
- type: 'font/woff2',
- })
+ expect($('link[as="font"]').length).toBe(5)
+ expect(
+ Array.from($('link[as="font"]'))
+ .map((el) => el.attribs.href)
+ .sort()
+ ).toEqual([
+ '/_next/static/media/02205c9944024f15.p.woff2',
+ '/_next/static/media/0812efcfaefec5ea.p.woff2',
+ '/_next/static/media/1deec1af325840fd.p.woff2',
+ '/_next/static/media/ab6fdae82d1a8d92.p.woff2',
+ '/_next/static/media/d55edb6f37902ebf.p.woff2',
+ ])
+ })
+
+ test('google fonts with multiple weights/styles', async () => {
+ const html = await renderViaHTTP(next.url, '/with-google-fonts')
+ const $ = cheerio.load(html)
+
+ // Preconnect
+ expect($('link[rel="preconnect"]').length).toBe(0)
+
+ // Preload
+ expect($('link[as="font"]').length).toBe(7)
+
+ expect(
+ Array.from($('link[as="font"]'))
+ .map((el) => el.attribs.href)
+ .sort()
+ ).toEqual([
+ '/_next/static/media/0812efcfaefec5ea.p.woff2',
+ '/_next/static/media/4f3dcdf40b3ca86d.p.woff2',
+ '/_next/static/media/560a6db6ac485cb1.p.woff2',
+ '/_next/static/media/686d1702f12625fe.p.woff2',
+ '/_next/static/media/86d92167ff02c708.p.woff2',
+ '/_next/static/media/c9baea324111137d.p.woff2',
+ '/_next/static/media/fb68b4558e2a718e.p.woff2',
+ ])
})
})
diff --git a/test/unit/google-font-loader.test.ts b/test/unit/google-font-loader.test.ts
index cbd690989c8f003..3a5ff41620e51a6 100644
--- a/test/unit/google-font-loader.test.ts
+++ b/test/unit/google-font-loader.test.ts
@@ -73,6 +73,30 @@ describe('@next/font/google loader', () => {
{ weight: '400' },
'https://fonts.googleapis.com/css2?family=Molle:ital,wght@1,400&display=optional',
],
+ [
+ 'Roboto',
+ { weight: ['500', '300', '400'], style: ['normal', 'italic'] },
+ 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=optional',
+ ],
+ [
+ 'Roboto Mono',
+ { style: ['italic', 'normal'] },
+ 'https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=optional',
+ ],
+ [
+ 'Fraunces',
+ {
+ style: ['normal', 'italic'],
+ axes: ['WONK', 'opsz', 'SOFT'],
+ },
+ 'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1;1,9..144,100..900,0..100,0..1&display=optional',
+ ],
+
+ [
+ 'Poppins',
+ { weight: ['900', '400', '100'] },
+ 'https://fonts.googleapis.com/css2?family=Poppins:wght@100;400;900&display=optional',
+ ],
])('%s', async (functionName: string, data: any, url: string) => {
fetch.mockResolvedValue({
ok: true,
@@ -88,66 +112,10 @@ describe('@next/font/google loader', () => {
isServer: true,
variableName: 'myFont',
})
- expect(css).toBe('OK\n')
+ expect(css).toBe('OK')
expect(fetch).toHaveBeenCalledTimes(1)
expect(fetch).toHaveBeenCalledWith(url, expect.any(Object))
})
-
- test('Multiple weights and styles', async () => {
- let i = 1
- fetch.mockResolvedValue({
- ok: true,
- text: async () => `${i++}`,
- })
-
- const { css } = await loader({
- functionName: 'Roboto',
- data: [
- {
- weight: ['300', '400', '500'],
- style: ['normal', 'italic'],
- },
- ],
- config: { subsets: [] },
- emitFontFile: jest.fn(),
- resolve: jest.fn(),
- fs: {} as any,
- isServer: true,
- variableName: 'myFont',
- })
- expect(css).toBe('1\n2\n3\n4\n5\n6\n')
- expect(fetch).toHaveBeenCalledTimes(6)
- expect(fetch).toHaveBeenNthCalledWith(
- 1,
- 'https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=optional',
- expect.any(Object)
- )
- expect(fetch).toHaveBeenNthCalledWith(
- 2,
- 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@1,300&display=optional',
- expect.any(Object)
- )
- expect(fetch).toHaveBeenNthCalledWith(
- 3,
- 'https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=optional',
- expect.any(Object)
- )
- expect(fetch).toHaveBeenNthCalledWith(
- 4,
- 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@1,400&display=optional',
- expect.any(Object)
- )
- expect(fetch).toHaveBeenNthCalledWith(
- 5,
- 'https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=optional',
- expect.any(Object)
- )
- expect(fetch).toHaveBeenNthCalledWith(
- 6,
- 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@1,500&display=optional',
- expect.any(Object)
- )
- })
})
describe('Errors', () => {