diff --git a/packages/font/src/google/loader.ts b/packages/font/src/google/loader.ts index 6a4c3a19b0664c8..8633a00c83f6f3d 100644 --- a/packages/font/src/google/loader.ts +++ b/packages/font/src/google/loader.ts @@ -34,7 +34,9 @@ const downloadGoogleFonts: FontLoader = async ({ data, config, emitFontFile, + isDev, isServer, + loaderContext, }) => { const subsets = config?.subsets || [] @@ -69,80 +71,7 @@ const downloadGoogleFonts: FontLoader = async ({ ) const url = getUrl(fontFamily, fontAxes, display) - 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 - const fontFiles: Array<{ - googleFontFileUrl: string - preloadFontFile: boolean - }> = [] - let currentSubset = '' - for (const line of fontFaceDeclarations.split('\n')) { - // Each @font-face has the subset above it in a comment - const newSubset = /\/\* (.+?) \*\//.exec(line)?.[1] - if (newSubset) { - currentSubset = newSubset - } else { - const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1] - if ( - googleFontFileUrl && - !fontFiles.some( - (foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl - ) - ) { - fontFiles.push({ - googleFontFileUrl, - preloadFontFile: - !!preload && (callSubsets ?? subsets).includes(currentSubset), - }) - } - } - } - - // Download font files - const downloadedFiles = await Promise.all( - fontFiles.map(async ({ googleFontFileUrl, preloadFontFile }) => { - let cachedFontRequest = fontCache.get(googleFontFileUrl) - const fontFileBuffer = - cachedFontRequest ?? (await fetchFontFile(googleFontFileUrl)) - if (!cachedFontRequest) { - fontCache.set(googleFontFileUrl, fontFileBuffer) - } else { - fontCache.delete(googleFontFileUrl) - } - - const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(googleFontFileUrl)![1] - // Emit font file to .next/static/fonts - const selfHostedFileUrl = emitFontFile( - fontFileBuffer, - ext, - preloadFontFile - ) - - return { - googleFontFileUrl, - selfHostedFileUrl, - } - }) - ) - - // Replace @font-face sources with self-hosted files - let updatedCssResponse = fontFaceDeclarations - for (const { googleFontFileUrl, selfHostedFileUrl } of downloadedFiles) { - updatedCssResponse = updatedCssResponse.replace( - new RegExp(escapeStringRegexp(googleFontFileUrl), 'g'), - selfHostedFileUrl - ) - } - - // Add fallback font + // Find fallback font metrics let adjustFontFallbackMetrics: AdjustFontFallback | undefined if (adjustFontFallback) { try { @@ -164,16 +93,136 @@ const downloadGoogleFonts: FontLoader = async ({ } } - return { - css: updatedCssResponse, - fallbackFonts: fallback, - weight: - weights.length === 1 && weights[0] !== 'variable' - ? weights[0] - : undefined, - style: styles.length === 1 ? styles[0] : undefined, - variable, - adjustFontFallback: adjustFontFallbackMetrics, + try { + const hasCachedCSS = cssCache.has(url) + const fontFaceDeclarations = hasCachedCSS + ? cssCache.get(url) + : await fetchCSSFromGoogleFonts(url, fontFamily).catch(() => null) + if (!hasCachedCSS) { + cssCache.set(url, fontFaceDeclarations) + } else { + cssCache.delete(url) + } + if (fontFaceDeclarations === null) { + throw new Error(`Failed to fetch \`${fontFamily}\` from Google Fonts.`) + } + + // Find font files to download + const fontFiles: Array<{ + googleFontFileUrl: string + preloadFontFile: boolean + }> = [] + let currentSubset = '' + for (const line of fontFaceDeclarations.split('\n')) { + // Each @font-face has the subset above it in a comment + const newSubset = /\/\* (.+?) \*\//.exec(line)?.[1] + if (newSubset) { + currentSubset = newSubset + } else { + const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1] + if ( + googleFontFileUrl && + !fontFiles.some( + (foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl + ) + ) { + fontFiles.push({ + googleFontFileUrl, + preloadFontFile: + !!preload && (callSubsets ?? subsets).includes(currentSubset), + }) + } + } + } + + // Download font files + const downloadedFiles = await Promise.all( + fontFiles.map(async ({ googleFontFileUrl, preloadFontFile }) => { + const hasCachedFont = fontCache.has(googleFontFileUrl) + const fontFileBuffer = hasCachedFont + ? fontCache.get(googleFontFileUrl) + : await fetchFontFile(googleFontFileUrl).catch(() => null) + if (!hasCachedFont) { + fontCache.set(googleFontFileUrl, fontFileBuffer) + } else { + fontCache.delete(googleFontFileUrl) + } + if (fontFileBuffer === null) { + throw new Error( + `Failed to fetch \`${fontFamily}\` from Google Fonts.` + ) + } + + const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(googleFontFileUrl)![1] + // Emit font file to .next/static/fonts + const selfHostedFileUrl = emitFontFile( + fontFileBuffer, + ext, + preloadFontFile + ) + + return { + googleFontFileUrl, + selfHostedFileUrl, + } + }) + ) + + // Replace @font-face sources with self-hosted files + let updatedCssResponse = fontFaceDeclarations + for (const { googleFontFileUrl, selfHostedFileUrl } of downloadedFiles) { + updatedCssResponse = updatedCssResponse.replace( + new RegExp(escapeStringRegexp(googleFontFileUrl), 'g'), + selfHostedFileUrl + ) + } + + return { + css: updatedCssResponse, + fallbackFonts: fallback, + weight: + weights.length === 1 && weights[0] !== 'variable' + ? weights[0] + : undefined, + style: styles.length === 1 ? styles[0] : undefined, + variable, + adjustFontFallback: adjustFontFallbackMetrics, + } + } catch (err) { + loaderContext.cacheable(false) + if (isDev) { + if (isServer) { + Log.error( + `Failed to download \`${fontFamily}\` from Google Fonts. Using fallback font instead.` + ) + } + + // In dev we should return the fallback font instead of throwing an error + let css = `@font-face { + font-family: '${fontFamily} Fallback'; + src: local("${adjustFontFallbackMetrics?.fallbackFont ?? 'Arial'}");` + if (adjustFontFallbackMetrics) { + css += ` + ascent-override:${adjustFontFallbackMetrics.ascentOverride}; + descent-override:${adjustFontFallbackMetrics.descentOverride}; + line-gap-override:${adjustFontFallbackMetrics.lineGapOverride}; + size-adjust:${adjustFontFallbackMetrics.sizeAdjust};` + } + css += '\n}' + + return { + css, + fallbackFonts: fallback, + weight: + weights.length === 1 && weights[0] !== 'variable' + ? weights[0] + : undefined, + style: styles.length === 1 ? styles[0] : undefined, + variable, + } + } else { + throw err + } } } diff --git a/packages/font/src/local/loader.ts b/packages/font/src/local/loader.ts index 15bcb649409033d..5028ce498a63a86 100644 --- a/packages/font/src/local/loader.ts +++ b/packages/font/src/local/loader.ts @@ -57,7 +57,7 @@ const fetchFonts: FontLoader = async ({ data, emitFontFile, resolve, - fs, + loaderContext, }) => { const { src, @@ -74,7 +74,7 @@ const fetchFonts: FontLoader = async ({ const fontFiles = await Promise.all( src.map(async ({ path, style, weight, ext, format }) => { const resolved = await resolve(path) - const fileBuffer = await promisify(fs.readFile)(resolved) + const fileBuffer = await promisify(loaderContext.fs.readFile)(resolved) const fontUrl = emitFontFile(fileBuffer, ext, preload) let fontMetadata: any diff --git a/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts b/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts index 0da13432aba48ab..30be3b1cefa193d 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts @@ -59,6 +59,7 @@ export function getFontLoader( loaders.push({ loader: 'next-font-loader', options: { + isDev: ctx.isDevelopment, isServer: ctx.isServer, assetPrefix: ctx.assetPrefix, fontLoaderOptions, diff --git a/packages/next/build/webpack/loaders/next-font-loader/index.ts b/packages/next/build/webpack/loaders/next-font-loader/index.ts index 8750532db6ed4bc..ad64f097466f6c1 100644 --- a/packages/next/build/webpack/loaders/next-font-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-font-loader/index.ts @@ -13,6 +13,7 @@ export default async function nextFontLoader(this: any) { return fontLoaderSpan.traceAsyncFn(async () => { const callback = this.async() const { + isDev, isServer, assetPrefix, fontLoaderOptions, @@ -79,8 +80,9 @@ export default async function nextFontLoader(this: any) { ), src.startsWith('.') ? src : `./${src}` ), - fs: this.fs, + isDev, isServer, + loaderContext: this, }) const { postcss } = await getPostcss() diff --git a/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts b/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts index 04391a9cf0e71a0..577fc98d4b17d6f 100644 --- a/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts +++ b/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts @@ -51,7 +51,7 @@ const postcssFontLoaderPlugn = ({ } if (!fontFamily) { - throw new Error('Font loaders must have exactly one font family') + throw new Error("Font loaders must return one or more @font-face's") } // Add fallback font with override values diff --git a/packages/next/font/index.d.ts b/packages/next/font/index.d.ts index ce4c25884e53e48..c01dcff68f979d0 100644 --- a/packages/next/font/index.d.ts +++ b/packages/next/font/index.d.ts @@ -19,8 +19,9 @@ export type FontLoader = (options: { config: any emitFontFile: (content: Buffer, ext: string, preload: boolean) => string resolve: (src: string) => string - fs: any + isDev: boolean isServer: boolean + loaderContext: any }) => Promise<{ css: string fallbackFonts?: string[] diff --git a/test/e2e/next-font/google-fetch-error.test.ts b/test/e2e/next-font/google-fetch-error.test.ts new file mode 100644 index 000000000000000..582b4bf87a7900e --- /dev/null +++ b/test/e2e/next-font/google-fetch-error.test.ts @@ -0,0 +1,76 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { join } from 'path' +import webdriver from 'next-webdriver' + +const mockedGoogleFontResponses = require.resolve( + './google-font-mocked-responses.js' +) + +describe('@next/font/google fetch error', () => { + const isDev = (global as any).isNextDev + let next: NextInstance + + if ((global as any).isNextDeploy) { + it('should skip next deploy for now', () => {}) + return + } + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, 'google-fetch-error/pages')), + 'next.config.js': new FileRef( + join(__dirname, 'google-fetch-error/next.config.js') + ), + }, + dependencies: { + '@next/font': 'canary', + }, + env: { + NEXT_FONT_GOOGLE_MOCKED_RESPONSES: mockedGoogleFontResponses, + }, + skipStart: true, + }) + }) + afterAll(() => next.destroy()) + + if (isDev) { + it('should use a fallback font in dev', async () => { + await next.start() + const outputIndex = next.cliOutput.length + const browser = await webdriver(next.url, '/') + + const ascentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Inter_Fallback")).ascentOverride' + ) + expect(ascentOverride).toBe('90%') + + const descentOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Inter_Fallback")).descentOverride' + ) + expect(descentOverride).toBe('22.43%') + + const lineGapOverride = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Inter_Fallback")).lineGapOverride' + ) + expect(lineGapOverride).toBe('0%') + + const sizeAdjust = await browser.eval( + 'Array.from(document.fonts.values()).find(font => font.family.includes("Inter_Fallback")).sizeAdjust' + ) + expect(sizeAdjust).toBe('107.64%') + + expect(next.cliOutput.slice(outputIndex)).toInclude( + 'Failed to download `Inter` from Google Fonts. Using fallback font instead.' + ) + }) + } else { + it('should error when not in dev', async () => { + await expect(next.start()).rejects.toThrow('next build failed') + expect(next.cliOutput).toInclude( + 'Failed to fetch `Inter` from Google Fonts.' + ) + }) + } +}) diff --git a/test/e2e/next-font/google-fetch-error/next.config.js b/test/e2e/next-font/google-fetch-error/next.config.js new file mode 100644 index 000000000000000..16e3ab6f546a21d --- /dev/null +++ b/test/e2e/next-font/google-fetch-error/next.config.js @@ -0,0 +1,9 @@ +module.exports = { + experimental: { + fontLoaders: [ + { + loader: '@next/font/google', + }, + ], + }, +} diff --git a/test/e2e/next-font/google-fetch-error/pages/index.js b/test/e2e/next-font/google-fetch-error/pages/index.js new file mode 100644 index 000000000000000..229c3d6631c4304 --- /dev/null +++ b/test/e2e/next-font/google-fetch-error/pages/index.js @@ -0,0 +1,10 @@ +import { Inter } from '@next/font/google' +const inter = Inter({ weight: '400', subsets: ['latin'] }) + +export default function Page() { + return ( +

+ {JSON.stringify(inter)} +

+ ) +} diff --git a/test/e2e/next-font/google-font-mocked-responses.js b/test/e2e/next-font/google-font-mocked-responses.js index 894e3998a2b7f23..5499d96f4ff370f 100644 --- a/test/e2e/next-font/google-font-mocked-responses.js +++ b/test/e2e/next-font/google-font-mocked-responses.js @@ -936,4 +936,6 @@ module.exports = { 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=Inter:wght@400&display=optional': + null, } diff --git a/test/unit/google-font-loader.test.ts b/test/unit/google-font-loader.test.ts index f1b21b9a1b4928b..80096c884535341 100644 --- a/test/unit/google-font-loader.test.ts +++ b/test/unit/google-font-loader.test.ts @@ -108,7 +108,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -119,28 +120,6 @@ describe('@next/font/google loader', () => { }) describe('Errors', () => { - test('Failed to fetch', async () => { - fetch.mockResolvedValue({ - ok: false, - }) - - await expect( - loader({ - functionName: 'Alkalami', - data: [{ weight: '400' }], - config: { subsets: [] }, - emitFontFile: jest.fn(), - resolve: jest.fn(), - fs: {} as any, - isServer: true, - variableName: 'myFont', - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Failed to fetch font \`Alkalami\`. - URL: https://fonts.googleapis.com/css2?family=Alkalami:wght@400&display=optional" - `) - }) - test('Missing function name', async () => { await expect( loader({ @@ -149,7 +128,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -166,7 +146,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -183,7 +164,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -201,7 +183,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -219,7 +202,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -237,7 +221,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -255,7 +240,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -272,7 +258,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -289,7 +276,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -307,7 +295,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -325,7 +314,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) @@ -379,7 +369,8 @@ describe('@next/font/google loader', () => { config: { subsets: [] }, emitFontFile: jest.fn(), resolve: jest.fn(), - fs: {} as any, + loaderContext: {} as any, + isDev: false, isServer: true, variableName: 'myFont', }) diff --git a/test/unit/local-font-loader.test.ts b/test/unit/local-font-loader.test.ts index 3a1b38a1cda0c99..8219097051ee14d 100644 --- a/test/unit/local-font-loader.test.ts +++ b/test/unit/local-font-loader.test.ts @@ -9,11 +9,14 @@ describe('@next/font/local', () => { config: {}, emitFontFile: () => '/_next/static/media/my-font.woff2', resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { - readFile: (_, cb) => cb(null, 'fontdata'), - }, + loaderContext: { + fs: { + readFile: (_, cb) => cb(null, 'fontdata'), + }, + } as any, }) expect(css).toMatchInlineSnapshot(` @@ -33,11 +36,14 @@ describe('@next/font/local', () => { config: {}, emitFontFile: () => '/_next/static/media/my-font.woff2', resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { - readFile: (_, cb) => cb(null, 'fontdata'), - }, + loaderContext: { + fs: { + readFile: (_, cb) => cb(null, 'fontdata'), + }, + } as any, }) expect(css).toMatchInlineSnapshot(` @@ -67,11 +73,14 @@ describe('@next/font/local', () => { config: {}, emitFontFile: () => '/_next/static/media/my-font.woff2', resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { - readFile: (_, cb) => cb(null, 'fontdata'), - }, + loaderContext: { + fs: { + readFile: (_, cb) => cb(null, 'fontdata'), + }, + } as any, }) expect(css).toMatchInlineSnapshot(` @@ -117,11 +126,14 @@ describe('@next/font/local', () => { config: {}, emitFontFile: (buffer) => `/_next/static/media/my-font.woff2`, resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { - readFile: (path, cb) => cb(null, path), - }, + loaderContext: { + fs: { + readFile: (path, cb) => cb(null, path), + }, + } as any, }) expect(css).toMatchInlineSnapshot(` @@ -186,11 +198,14 @@ describe('@next/font/local', () => { config: {}, emitFontFile: (buffer) => `/_next/static/media/my-font.woff2`, resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { - readFile: (path, cb) => cb(null, path), - }, + loaderContext: { + fs: { + readFile: (path, cb) => cb(null, path), + }, + } as any, }) expect(css).toMatchInlineSnapshot(` @@ -230,9 +245,10 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: {}, + loaderContext: {} as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"@next/font/local has no named exports"` @@ -247,9 +263,10 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: {}, + loaderContext: {} as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Missing required \`src\` property"` @@ -264,9 +281,10 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn().mockResolvedValue(''), + isDev: false, isServer: true, variableName: 'myFont', - fs: {}, + loaderContext: {} as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unexpected file \`./font/font-file.abc\`"` @@ -281,9 +299,10 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: {}, + loaderContext: {} as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot(` "Invalid display value \`invalid\`. @@ -305,9 +324,10 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: {}, + loaderContext: {} as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Invalid declaration prop: \`src\`"` @@ -322,9 +342,10 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: {}, + loaderContext: {} as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unexpected empty \`src\` array."` @@ -349,10 +370,13 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { readFile: (path, cb) => cb(null, path) }, - }) + loaderContext: { + fs: { readFile: (path, cb) => cb(null, path) }, + } as any, + } as any) ).rejects.toThrowErrorMatchingInlineSnapshot(` "Invalid weight value in src array: \`abc\`. Expected \`normal\`, \`bold\` or a number." @@ -377,9 +401,12 @@ describe('@next/font/local', () => { config: {}, emitFontFile: jest.fn(), resolve: jest.fn(), + isDev: false, isServer: true, variableName: 'myFont', - fs: { readFile: (path, cb) => cb(null, path) }, + loaderContext: { + fs: { readFile: (path, cb) => cb(null, path) }, + } as any, }) ).rejects.toThrowErrorMatchingInlineSnapshot(` "Invalid weight value in src array: \`100 abc\`.