From 83e19a25577d0fc3dba4e189bac45755d0f43acd Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 3 Nov 2022 18:46:38 +0100 Subject: [PATCH 1/8] Fix entry creation on Windows (#42421) In Windows absolute paths start with things like `C:\` instead of `/`. This PR closes #42024 as I confirmed the reproduction app is working properly with this fix. ## 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) --- .../next/build/webpack/plugins/flight-client-entry-plugin.ts | 2 +- packages/next/build/webpack/plugins/flight-manifest-plugin.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts index bb781e778b3b..c048c8b90817 100644 --- a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts @@ -189,7 +189,7 @@ export class FlightClientEntryPlugin { dependency: layoutOrPageDependency, }) - const isAbsoluteRequest = layoutOrPageRequest[0] === '/' + const isAbsoluteRequest = path.isAbsolute(layoutOrPageRequest) // Next.js internals are put into a separate entry. if (!isAbsoluteRequest) { diff --git a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts index 99859a5bc437..00aefb985d64 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -201,7 +201,7 @@ export class FlightManifestPlugin { }, } } else { - // It is possible that there are mtuliepl modules with the same resouce, + // It is possible that there are multiple modules with the same resource, // e.g. extracted by mini-css-extract-plugin. In that case we need to // merge the chunks. manifest[resource].default.chunks = [ From e480b1d8093078c5dbf90977df01481ead794823 Mon Sep 17 00:00:00 2001 From: Lucas Rosa Date: Thu, 3 Nov 2022 14:51:27 -0300 Subject: [PATCH 2/8] Fix Docs API Reference font.md broken link (#42418) ## 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) --- docs/api-reference/next/font.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next/font.md b/docs/api-reference/next/font.md index 6d02fc1d8652..ddc6836b7ca3 100644 --- a/docs/api-reference/next/font.md +++ b/docs/api-reference/next/font.md @@ -91,7 +91,7 @@ A string value to define the CSS variable name to be used if the style is applie ### Font function arguments -For usage, review [Local Fonts](/docs/optimizing/fonts#local-fonts). +For usage, review [Local Fonts](/docs/basic-features/font-optimization.md#local-fonts). | Key | Example | Data type | Required | | ------------------------------------------- | ----------------------------------------------------------- | -------------------------------------- | -------- | From 310fe3cbc1aa5904bc834d046da69b1057b79b4a Mon Sep 17 00:00:00 2001 From: Aditya Bhaskar Sharma Date: Thu, 3 Nov 2022 23:37:01 +0530 Subject: [PATCH 3/8] fixed missing `` keyword (#42422) The keyword is apparently not being shown on the page. Adding "`" around the word will make it work. ## 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) --- docs/routing/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/routing/introduction.md b/docs/routing/introduction.md index 662c395937ff..c19a59486c9b 100644 --- a/docs/routing/introduction.md +++ b/docs/routing/introduction.md @@ -68,7 +68,7 @@ The example above uses multiple links. Each one maps a path (`href`) to a known - `/about` → `pages/about.js` - `/blog/hello-world` → `pages/blog/[slug].js` -Any `` in the viewport (initially or through scroll) will be prefetched by default (including the corresponding data) for pages using [Static Generation](/docs/basic-features/data-fetching/get-static-props.md). The corresponding data for [server-rendered](/docs/basic-features/data-fetching/get-server-side-props.md) routes is fetched _only when_ the is clicked. +Any `` in the viewport (initially or through scroll) will be prefetched by default (including the corresponding data) for pages using [Static Generation](/docs/basic-features/data-fetching/get-static-props.md). The corresponding data for [server-rendered](/docs/basic-features/data-fetching/get-server-side-props.md) routes is fetched _only when_ the `` is clicked. ### Linking to dynamic paths From 163e89648cc483eb8b709e8ee44fa88fb7771088 Mon Sep 17 00:00:00 2001 From: 7yue Date: Fri, 4 Nov 2022 02:07:22 +0800 Subject: [PATCH 4/8] examples: Fix with-turbopack typo (#42410) ## 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) --- .../app/hooks/[categorySlug]/[subCategorySlug]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-turbopack/app/hooks/[categorySlug]/[subCategorySlug]/page.tsx b/examples/with-turbopack/app/hooks/[categorySlug]/[subCategorySlug]/page.tsx index 17332209bc76..59b42fd7fec9 100644 --- a/examples/with-turbopack/app/hooks/[categorySlug]/[subCategorySlug]/page.tsx +++ b/examples/with-turbopack/app/hooks/[categorySlug]/[subCategorySlug]/page.tsx @@ -4,7 +4,7 @@ import { SkeletonCard } from '@/ui/SkeletonCard'; export default function Page({ params }: PageProps) { const category = use( - fetchSubCategory(params.categorySlug, params.subCategory), + fetchSubCategory(params.categorySlug, params.subCategorySlug), ); if (!category) return null; return ( From 73c5b77a8484fa94f77a5392dc774ddbd1af6427 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 3 Nov 2022 14:11:06 -0400 Subject: [PATCH 5/8] Fix docs for next/image upgrade guide (#42424) This fixes a typo and also clarifies when the codemod might fail to migrate properly. --- docs/advanced-features/codemods.md | 4 ++-- docs/upgrading.md | 10 +++++----- errors/next-image-upgrade-to-13.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/advanced-features/codemods.md b/docs/advanced-features/codemods.md index 02b39bdadf69..dfe8d18657c7 100644 --- a/docs/advanced-features/codemods.md +++ b/docs/advanced-features/codemods.md @@ -45,7 +45,7 @@ export default function Page() { ### `next-image-to-legacy-image` -Safely migrates existing Next.js 10, 11, 12 applications importing `next/image` to the renamed `next/legacy/image` import in Next.js 13. +This codemod safely migrates existing Next.js 10, 11, 12 applications importing `next/image` to the renamed `next/legacy/image` import in Next.js 13. For example: @@ -81,7 +81,7 @@ export default function Home() { ### `next-image-experimental` (experimental) -Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props. +This codemod dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props. Please note this codemod is experimental and only covers static usage (such as ``) but not dynamic usage (such as ``). - Removes `layout` prop and adds `style` - Removes `objectFit` prop and adds `style` diff --git a/docs/upgrading.md b/docs/upgrading.md index bd7539b659a0..86f1cd626c6f 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -34,16 +34,16 @@ You can continue using `pages` with new features that work in both directories, ### `` Component -Next.js 12 introduced new improvements to the Image Component with a temporary import: `next/future/image`. These improvements included less client-side JavaScript, easier ways to extend and style images, better accessibility, and native browser lazy loading. +Next.js 12 introduced many improvements to the Image Component with a temporary import: `next/future/image`. These improvements included less client-side JavaScript, easier ways to extend and style images, better accessibility, and native browser lazy loading. -In version 13, this new behavior is now the default for `next/image`. +Starting in Next.js 13, this new behavior is now the default for `next/image`. There are two codemods to help you migrate to the new Image Component: -- [**`next-image-to-legacy-image` codemod**](/docs/advanced-features/codemods.md#rename-instances-of-nextimage): Safely and automatically renames `next/image` imports to `next/legacy/image`. Existing components will maintain the same behavior. -- [**`next-image-experimental` codemod**](/docs/advanced-features/codemods.md#migrate-next-image-experimental-experimental): Dangerously adds inline styles and removes unused props using the experimental. This will change the behavior of existing components to match the new defaults. To use this codemod, you need to run the `next-image-to-legacy-image` codemod first. +- [next-image-to-legacy-image](/docs/advanced-features/codemods.md#rename-instances-of-nextimage): This codemod will safely and automatically rename `next/image` imports to `next/legacy/image` to maintain the same behavior as Next.js 12. We recommend running this codemod to quickly update to Next.js 13 automatically. +- [next-image-experimental](/docs/advanced-features/codemods.md#next-image-experimental-experimental): After running the previous codemod, you can optionally run this experimental codemod to upgrade `next/legacy/image` to the new `next/image`, which will remove unused props and add inline styles. Please note this codemod is experimental and only covers static usage (such as ``) but not dynamic usage (such as ``). -Alternatively, you can manually update props by following the [`next/future/image` migration guide](/docs/api-reference/next/image.md#migration). This will change the behavior of existing components to match the new defaults. +Alternatively, you can manually update by following the [migration guide](/docs/advanced-features/codemods.md#next-image-experimental-experimental) and also see the [legacy comparison](/docs/api-reference/next/legacy/image.md#comparison). ### `` Component diff --git a/errors/next-image-upgrade-to-13.md b/errors/next-image-upgrade-to-13.md index eeb42566da13..3553cc3e3a40 100644 --- a/errors/next-image-upgrade-to-13.md +++ b/errors/next-image-upgrade-to-13.md @@ -31,7 +31,7 @@ After running this codemod, you can optionally upgrade `next/legacy/image` to th npx @next/codemod next-image-experimental . ``` -Please note this second codemod is experimental and only covers static usage, not dynamic usage (such ``). +Please note this second codemod is experimental and only covers static usage (such as ``) but not dynamic usage (such as ``). ### Useful Links From 539769dddce77425c91e2bc95b579ba2724bff8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 3 Nov 2022 19:12:46 +0100 Subject: [PATCH 6/8] Mock @next/font when using next/jest (#42413) Mock `@next/font` when using `next/jest`. fixes #42379 ## 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) --- docs/testing.md | 2 +- jest.config.js | 3 +++ .../next/build/jest/__mocks__/nextFontMock.js | 12 ++++++++++++ packages/next/build/jest/jest.ts | 3 +++ packages/next/build/swc/options.js | 19 +++++++++---------- test/production/jest/index.test.ts | 8 ++++++++ 6 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 packages/next/build/jest/__mocks__/nextFontMock.js diff --git a/docs/testing.md b/docs/testing.md index 7dca84b8879a..7414761d37c3 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -293,7 +293,7 @@ module.exports = createJestConfig(customJestConfig) Under the hood, `next/jest` is automatically configuring Jest for you, including: - Setting up `transform` using [SWC](https://nextjs.org/docs/advanced-features/compiler) -- Auto mocking stylesheets (`.css`, `.module.css`, and their scss variants) and image imports +- Auto mocking stylesheets (`.css`, `.module.css`, and their scss variants), image imports and [`@next/font`](https://nextjs.org/docs/basic-features/font-optimization) - Loading `.env` (and all variants) into `process.env` - Ignoring `node_modules` from test resolving and transforms - Ignoring `.next` from test resolving diff --git a/jest.config.js b/jest.config.js index a99f0afcea12..177949779f05 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,6 +14,9 @@ const customJestConfig = { globals: { AbortSignal: global.AbortSignal, }, + moduleNameMapper: { + '@next/font/(.*)': '@next/font/$1', + }, } // createJestConfig is exported in this way to ensure that next/jest can load the Next.js config which is async diff --git a/packages/next/build/jest/__mocks__/nextFontMock.js b/packages/next/build/jest/__mocks__/nextFontMock.js new file mode 100644 index 000000000000..f076797502d8 --- /dev/null +++ b/packages/next/build/jest/__mocks__/nextFontMock.js @@ -0,0 +1,12 @@ +module.exports = new Proxy( + {}, + { + get: function getter() { + return () => ({ + className: 'className', + variable: 'variable', + style: { fontFamily: 'fontFamily' }, + }) + }, + } +) diff --git a/packages/next/build/jest/jest.ts b/packages/next/build/jest/jest.ts index ef17dc10a189..8b1e704bc1b0 100644 --- a/packages/next/build/jest/jest.ts +++ b/packages/next/build/jest/jest.ts @@ -115,6 +115,9 @@ export default function nextJest(options: { dir?: string } = {}) { // Keep .svg to it's own rule to make overriding easy '^.+\\.(svg)$': require.resolve(`./__mocks__/fileMock.js`), + // Handle @next/font + '@next/font/(.*)': require.resolve('./__mocks__/nextFontMock.js'), + // custom config comes last to ensure the above rules are matched, // fixes the case where @pages/(.*) -> src/pages/$! doesn't break // CSS/image mocks diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 6dfb154cd0e1..673d4a0dd279 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -33,7 +33,6 @@ function getBaseSWCOptions({ jsConfig, swcCacheDir, isServerLayer, - relativeFilePathFromRoot, hasServerComponents, }) { const parserConfig = getParserOptions({ filename, jsConfig }) @@ -131,15 +130,6 @@ function getBaseSWCOptions({ isServer: !!isServerLayer, } : false, - fontLoaders: - nextConfig?.experimental?.fontLoaders && relativeFilePathFromRoot - ? { - fontLoaders: nextConfig.experimental.fontLoaders.map( - ({ loader }) => loader - ), - relativeFilePathFromRoot, - } - : null, } } @@ -255,6 +245,15 @@ export function getLoaderSWCOptions({ hasServerComponents, }) + if (nextConfig?.experimental?.fontLoaders && relativeFilePathFromRoot) { + baseOptions.fontLoaders = { + fontLoaders: nextConfig.experimental.fontLoaders.map( + ({ loader }) => loader + ), + relativeFilePathFromRoot, + } + } + const isNextDist = nextDistPath.test(filename) if (isServer) { diff --git a/test/production/jest/index.test.ts b/test/production/jest/index.test.ts index 5b831c902609..67db89fb5db3 100644 --- a/test/production/jest/index.test.ts +++ b/test/production/jest/index.test.ts @@ -21,6 +21,11 @@ describe('next/jest', () => { import Image from "next/image"; import img from "../public/vercel.svg"; import styles from "../styles/index.module.css"; + import localFont from "@next/font/local"; + import { Inter } from "@next/font/google"; + + const inter = Inter(); + const myFont = localFont({ src: "./my-font.woff2" }); const Comp = dynamic(() => import("../components/comp"), { loading: () =>

Loading...

, @@ -32,6 +37,7 @@ describe('next/jest', () => { logo logo 2

hello world

+

hello world

} `, @@ -118,8 +124,10 @@ describe('next/jest', () => { expect(router.push._isMockFunction).toBeTruthy() }) `, + 'pages/my-font.woff2': 'fake font', }, dependencies: { + '@next/font': 'canary', jest: '27.4.7', '@testing-library/jest-dom': '5.16.1', '@testing-library/react': '12.1.2', From 6edeb9d43ee93c6b37265c025be39dcf0643c2cf Mon Sep 17 00:00:00 2001 From: Shu Uesugi Date: Thu, 3 Nov 2022 11:20:15 -0700 Subject: [PATCH 7/8] Fix font-optimization.md syntax errors (#42403) Fixes syntax errors introduced in https://github.com/vercel/next.js/pull/42266 ## 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) Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- docs/basic-features/font-optimization.md | 29 ++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/basic-features/font-optimization.md b/docs/basic-features/font-optimization.md index 3d78e78295ad..ac5119f92372 100644 --- a/docs/basic-features/font-optimization.md +++ b/docs/basic-features/font-optimization.md @@ -28,8 +28,9 @@ Import the font you would like to use from `@next/font/google` as a function. We To use the font in all your pages, add it to [`_app.js` file](https://nextjs.org/docs/advanced-features/custom-app) under `/pages` as shown below: -```js:pages/_app.js -import { Inter } from '@next/font/google'; +```js +// pages/_app.js +import { Inter } from '@next/font/google' // If loading a variable font, you don't need to specify the font weight const inter = Inter() @@ -45,8 +46,9 @@ export default function MyApp({ Component, pageProps }) { If you can't use a variable font, you will **need to specify a weight**: -```js:pages/_app.js -import { Roboto } from '@next/font/google'; +```js +// pages/_app.js +import { Roboto } from '@next/font/google' const roboto = Roboto({ weight: '400', @@ -65,10 +67,11 @@ export default function MyApp({ Component, pageProps }) { You can also use the font without a wrapper and `className` by injecting it inside the `` as follows: -```js:pages/_app.js -import { Inter } from '@next/font/google'; +```js +// pages/_app.js +import { Inter } from '@next/font/google' -const inter = Inter(); +const inter = Inter() export default function MyApp({ Component, pageProps }) { return ( @@ -110,8 +113,9 @@ This can be done in 2 ways: - On a font per font basis by adding it to the function call - ```js:pages/_app.js - const inter = Inter({ subsets: ["latin"] }); + ```js + // pages/_app.js + const inter = Inter({ subsets: ['latin'] }) ``` - Globally for all your fonts in your `next.config.js` @@ -135,11 +139,12 @@ View the [Font API Reference](/docs/api-reference/next/font.md#nextfontgoogle) f Import `@next/font/local` and specify the `src` of your local font file. We recommend using [**variable fonts**](https://fonts.google.com/variablefonts) for the best performance and flexibility. -```js:pages/_app.js -import localFont from '@next/font/local'; +```js +// pages/_app.js +import localFont from '@next/font/local' // Font files can be colocated inside of `pages` -const myFont = localFont({ src: './my-font.woff2' }); +const myFont = localFont({ src: './my-font.woff2' }) export default function MyApp({ Component, pageProps }) { return ( From 8fa78a5e3cf9a70d0d9079bbae3a6d5ef5936573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 3 Nov 2022 19:21:32 +0100 Subject: [PATCH 8/8] 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) --- packages/font/src/google/loader.ts | 34 +- packages/font/src/google/utils.ts | 90 +++-- .../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 ++- test/unit/google-font-loader.test.ts | 82 ++--- 7 files changed, 501 insertions(+), 121 deletions(-) diff --git a/packages/font/src/google/loader.ts b/packages/font/src/google/loader.ts index b835c439ade0..a4692077d364 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 5060ebcc29fa..16baec887f90 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 cd6428a4ec3a..39ede912aa31 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 ac78e3065b26..37f218859d24 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 40b9da03a2d1..f1fa0f4f3843 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 8768d8a966a4..d4a933b524bf 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 cbd690989c8f..3a5ff41620e5 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', () => {