From 5c04ea67b4042c28e73c71112ae58937e1834075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 3 Nov 2022 11:51:54 +0100 Subject: [PATCH 1/3] Make one request for all variants --- packages/font/src/google/loader.ts | 34 +++++------ packages/font/src/google/utils.ts | 90 ++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 44 deletions(-) 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, + } } } From 18ffb2dfdacbdc245203d54bbb8c662f51206287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 3 Nov 2022 11:52:22 +0100 Subject: [PATCH 2/3] Update unit tests --- test/unit/google-font-loader.test.ts | 81 +++++++++------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/test/unit/google-font-loader.test.ts b/test/unit/google-font-loader.test.ts index 0348f44941209bb..d716fd61b26a9a5 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, @@ -87,65 +111,10 @@ describe('@next/font/google loader', () => { fs: {} as any, isServer: true, }) - 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, - }) - 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', () => { From 1f5bfccdf5b1d830410036bad497d9300f076797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 3 Nov 2022 11:54:28 +0100 Subject: [PATCH 3/3] add preload tests --- .../next-font/app/pages/with-google-fonts.js | 21 +- .../next-font/app/pages/with-local-fonts.js | 31 ++ .../next-font/google-font-mocked-responses.js | 310 ++++++++++++++++++ test/e2e/next-font/index.test.ts | 54 ++- 4 files changed, 396 insertions(+), 20 deletions(-) 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 2e40797aa232df8..927c1ff7e6bfb63 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', + ]) }) })