From 78c5be4e3271658ff7e79131e9af355f9a00468c Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 31 Jan 2023 19:39:36 +0100 Subject: [PATCH 1/7] Add support for navigating to external urls (#45388) Adds support for `router.push('https://google.com')`, `router.replace('https://google.com')`, and `redirect('https://google.com')`. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/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` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) 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`](https://github.com/vercel/next.js/blob/canary/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: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../next/src/client/components/app-router.tsx | 20 ++++++- .../reducers/navigate-reducer.ts | 31 +++++++++-- .../router-reducer/router-reducer-types.ts | 1 + packages/next/src/client/link.tsx | 52 +++++++++++++------ .../app/app/link-external/push/page.js | 11 ++++ .../app/app/link-external/replace/page.js | 11 ++++ .../app-dir/app/app/redirect/external/page.js | 6 +++ test/e2e/app-dir/app/index.test.ts | 28 +++++++++- 8 files changed, 138 insertions(+), 22 deletions(-) create mode 100644 test/e2e/app-dir/app/app/link-external/push/page.js create mode 100644 test/e2e/app-dir/app/app/link-external/replace/page.js create mode 100644 test/e2e/app-dir/app/app/redirect/external/page.js diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index faac24db783de..f3b50d9143ba4 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -96,6 +96,10 @@ function findHeadInCache( return undefined } +function isExternalURL(url: URL) { + return url.origin !== location.origin +} + /** * The global router that wraps the application components. */ @@ -180,9 +184,12 @@ function Router({ navigateType: 'push' | 'replace', forceOptimisticNavigation: boolean ) => { + const url = new URL(href, location.origin) + return dispatch({ type: ACTION_NAVIGATE, - url: new URL(href, location.origin), + url, + isExternalUrl: isExternalURL(url), forceOptimisticNavigation, navigateType, cache: { @@ -205,6 +212,10 @@ function Router({ } prefetched.add(href) const url = new URL(href, location.origin) + // External urls can't be prefetched in the same way. + if (isExternalURL(url)) { + return + } try { const routerTree = window.history.state?.tree || initialTree const serverResponse = await fetchServerResponse( @@ -261,7 +272,12 @@ function Router({ useEffect(() => { // When mpaNavigation flag is set do a hard navigation to the new url. if (pushRef.mpaNavigation) { - window.location.href = canonicalUrl + const location = window.location + if (pushRef.pendingPush) { + location.assign(canonicalUrl) + } else { + location.replace(canonicalUrl) + } return } diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts index d6a7dfe55ad4e..ea63b6d1a8b8a 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts @@ -22,11 +22,36 @@ export function navigateReducer( state: ReadonlyReducerState, action: NavigateAction ): ReducerState { - const { url, navigateType, cache, mutable, forceOptimisticNavigation } = - action + const { + url, + isExternalUrl, + navigateType, + cache, + mutable, + forceOptimisticNavigation, + } = action + const pendingPush = navigateType === 'push' + + if (isExternalUrl) { + return { + // Set href. + canonicalUrl: url.toString(), + pushRef: { + pendingPush, + mpaNavigation: true, + }, + // All navigation requires scroll and focus management to trigger. + focusAndScrollRef: { apply: false }, + // Apply cache. + cache: state.cache, + prefetchCache: state.prefetchCache, + // Apply patched router state. + tree: state.tree, + } + } + const { pathname, search } = url const href = createHrefFromUrl(url) - const pendingPush = navigateType === 'push' const isForCurrentTree = JSON.stringify(mutable.previousTree) === JSON.stringify(state.tree) diff --git a/packages/next/src/client/components/router-reducer/router-reducer-types.ts b/packages/next/src/client/components/router-reducer/router-reducer-types.ts index d8063c15de3df..998454fba8ddd 100644 --- a/packages/next/src/client/components/router-reducer/router-reducer-types.ts +++ b/packages/next/src/client/components/router-reducer/router-reducer-types.ts @@ -65,6 +65,7 @@ export interface RefreshAction { export interface NavigateAction { type: typeof ACTION_NAVIGATE url: URL + isExternalUrl: boolean navigateType: 'push' | 'replace' forceOptimisticNavigation: boolean cache: CacheNode diff --git a/packages/next/src/client/link.tsx b/packages/next/src/client/link.tsx index 967a9cda6c200..419a237651bca 100644 --- a/packages/next/src/client/link.tsx +++ b/packages/next/src/client/link.tsx @@ -117,13 +117,15 @@ function prefetch( router: NextRouter | AppRouterInstance, href: string, as: string, - options: PrefetchOptions + options: PrefetchOptions, + isAppRouter: boolean ): void { if (typeof window === 'undefined') { return } - if (!isLocalURL(href)) { + // app-router supports external urls out of the box so it shouldn't short-circuit here as support for e.g. `replace` is added in the app-router. + if (!isAppRouter && !isLocalURL(href)) { return } @@ -191,7 +193,12 @@ function linkClicked( // anchors inside an svg have a lowercase nodeName const isAnchorNodeName = nodeName.toUpperCase() === 'A' - if (isAnchorNodeName && (isModifiedEvent(e) || !isLocalURL(href))) { + if ( + isAnchorNodeName && + (isModifiedEvent(e) || + // app-router supports external urls out of the box so it shouldn't short-circuit here as support for e.g. `replace` is added in the app-router. + (!isAppRouter && !isLocalURL(href))) + ) { // ignore click for browser’s default behavior return } @@ -537,7 +544,7 @@ const Link = React.forwardRef( } // Prefetch the URL. - prefetch(router, href, as, { locale }) + prefetch(router, href, as, { locale }, isAppRouter) }, [ as, href, @@ -546,6 +553,7 @@ const Link = React.forwardRef( prefetchEnabled, pagesRouter?.locale, router, + isAppRouter, ]) const childProps: { @@ -619,12 +627,18 @@ const Link = React.forwardRef( return } - prefetch(router, href, as, { - locale, - priority: true, - // @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642} - bypassPrefetchedCheck: true, - }) + prefetch( + router, + href, + as, + { + locale, + priority: true, + // @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642} + bypassPrefetchedCheck: true, + }, + isAppRouter + ) }, onTouchStart(e) { if (!legacyBehavior && typeof onTouchStartProp === 'function') { @@ -647,12 +661,18 @@ const Link = React.forwardRef( return } - prefetch(router, href, as, { - locale, - priority: true, - // @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642} - bypassPrefetchedCheck: true, - }) + prefetch( + router, + href, + as, + { + locale, + priority: true, + // @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642} + bypassPrefetchedCheck: true, + }, + isAppRouter + ) }, } diff --git a/test/e2e/app-dir/app/app/link-external/push/page.js b/test/e2e/app-dir/app/app/link-external/push/page.js new file mode 100644 index 0000000000000..6a59349f5c978 --- /dev/null +++ b/test/e2e/app-dir/app/app/link-external/push/page.js @@ -0,0 +1,11 @@ +import Link from 'next/link' + +export default function Page() { + return ( + <> + + External Link + + + ) +} diff --git a/test/e2e/app-dir/app/app/link-external/replace/page.js b/test/e2e/app-dir/app/app/link-external/replace/page.js new file mode 100644 index 0000000000000..a660f48d4bb60 --- /dev/null +++ b/test/e2e/app-dir/app/app/link-external/replace/page.js @@ -0,0 +1,11 @@ +import Link from 'next/link' + +export default function Page() { + return ( + <> + + External Link + + + ) +} diff --git a/test/e2e/app-dir/app/app/redirect/external/page.js b/test/e2e/app-dir/app/app/redirect/external/page.js new file mode 100644 index 0000000000000..7eb691adeef93 --- /dev/null +++ b/test/e2e/app-dir/app/app/redirect/external/page.js @@ -0,0 +1,6 @@ +import { redirect } from 'next/navigation' + +export default function Page() { + redirect('https://example.vercel.sh/') + return <> +} diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index b8ba84c1def02..c5f5bdc042656 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -639,6 +639,26 @@ createNextDescribe( await browser.close() } }) + + it('should push to external url', async () => { + const browser = await next.browser('/link-external/push') + expect(await browser.eval('window.history.length')).toBe(2) + await browser.elementByCss('#external-link').click() + expect(await browser.waitForElementByCss('h1').text()).toBe( + 'Example Domain' + ) + expect(await browser.eval('window.history.length')).toBe(3) + }) + + it('should replace to external url', async () => { + const browser = await next.browser('/link-external/replace') + expect(await browser.eval('window.history.length')).toBe(2) + await browser.elementByCss('#external-link').click() + expect(await browser.waitForElementByCss('h1').text()).toBe( + 'Example Domain' + ) + expect(await browser.eval('window.history.length')).toBe(2) + }) }) describe('server components', () => { @@ -2502,7 +2522,6 @@ createNextDescribe( ) }) - // TODO-APP: Enable in development it('should redirect client-side', async () => { const browser = await next.browser('/redirect/client-side') await browser @@ -2514,6 +2533,13 @@ createNextDescribe( 'Result Page' ) }) + + it('should redirect to external url', async () => { + const browser = await next.browser('/redirect/external') + expect(await browser.waitForElementByCss('h1').text()).toBe( + 'Example Domain' + ) + }) }) describe('next.config.js redirects', () => { From 326485c06ec8c9de54df69b2ab5f27021a1cf273 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 31 Jan 2023 19:49:07 +0100 Subject: [PATCH 2/7] Export the Metadata type (#45445) This allows people to manually type their exported `metadata` field: CleanShot 2023-01-31 at 15 43 00@2x ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/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` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) 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`](https://github.com/vercel/next.js/blob/canary/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/next/src/lib/metadata/types/metadata-interface.ts | 2 +- packages/next/types/index.d.ts | 3 +++ test/e2e/app-dir/metadata/app/title/page.tsx | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/next/src/lib/metadata/types/metadata-interface.ts b/packages/next/src/lib/metadata/types/metadata-interface.ts index 5d36236283660..5f2a08b659004 100644 --- a/packages/next/src/lib/metadata/types/metadata-interface.ts +++ b/packages/next/src/lib/metadata/types/metadata-interface.ts @@ -32,7 +32,7 @@ import { ResolvedTwitterMetadata, Twitter } from './twitter-types' export interface Metadata { // origin and base path for absolute urls for various metadata links such as // opengraph-image - metadataBase: null | URL + metadataBase?: null | URL // The Document title title?: null | string | TemplateString diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index 6d1f9d4411a45..c24ea09d49596 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -28,6 +28,9 @@ export type ServerRuntime = 'nodejs' | 'experimental-edge' | 'edge' | undefined // @ts-ignore This path is generated at build time and conflicts otherwise export { NextConfig } from '../dist/server/config' +// @ts-ignore This path is generated at build time and conflicts otherwise +export { Metadata } from '../dist/lib/metadata/types/metadata-interface' + // Extend the React types with missing properties declare module 'react' { // support diff --git a/test/e2e/app-dir/metadata/app/title/page.tsx b/test/e2e/app-dir/metadata/app/title/page.tsx index e2e9c35731332..795b4b8b9dce9 100644 --- a/test/e2e/app-dir/metadata/app/title/page.tsx +++ b/test/e2e/app-dir/metadata/app/title/page.tsx @@ -1,5 +1,6 @@ import React from 'react' import Link from 'next/link' +import type { Metadata } from 'next' export default function Page() { return ( @@ -11,6 +12,6 @@ export default function Page() { ) } -export const metadata = { +export const metadata: Metadata = { title: 'this is the page title', } From 48e06e23401a41ccbb5e3e3e14c0e59e319cf0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3bal=20Dom=C3=ADnguez?= Date: Tue, 31 Jan 2023 20:01:24 +0100 Subject: [PATCH 3/7] docs: move getStaticProps before getStaticPaths (#45357) --- docs/manifest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 1a956ba9bbdc2..76405ea62a0eb 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -39,14 +39,14 @@ "title": "getServerSideProps", "path": "/docs/basic-features/data-fetching/get-server-side-props.md" }, - { - "title": "getStaticPaths", - "path": "/docs/basic-features/data-fetching/get-static-paths.md" - }, { "title": "getStaticProps", "path": "/docs/basic-features/data-fetching/get-static-props.md" }, + { + "title": "getStaticPaths", + "path": "/docs/basic-features/data-fetching/get-static-paths.md" + }, { "title": "Incremental Static Regeneration", "path": "/docs/basic-features/data-fetching/incremental-static-regeneration.md" From fb62d7e8cf009ac33dba2ba160ac10bb8ce71571 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 31 Jan 2023 21:12:06 +0100 Subject: [PATCH 4/7] v13.1.7-canary.1 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/font/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 17 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lerna.json b/lerna.json index 5613d6cbae14e..1aea589e63e71 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.1.7-canary.0" + "version": "13.1.7-canary.1" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 87b469d78e993..59e2890f29d9a 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 5339129786fc7..ca7d7db5fc995 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -12,7 +12,7 @@ "test-pack": "cd ../../ && pnpm test-pack eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.1.7-canary.0", + "@next/eslint-plugin-next": "13.1.7-canary.1", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 237a0125e2241..f8128e9d35ecd 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index a3c983a7746f1..ef355451b5102 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index e048fe5aa96dd..37dce0c1fd3c8 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index c9b13f5a1a13c..519ac56d827ca 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index cff4a99224daf..ae537b369014d 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 3a07a90f22cd0..877ce11309c5d 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 5583ffd832c0c..afda53079374c 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index e68751e71b176..b87f47890f8ed 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index dc4f6557c4b69..617e78383ef6c 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 33f4370d9478f..890fa13003293 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "private": true, "scripts": { "clean": "rm -rf ./native/*", diff --git a/packages/next/package.json b/packages/next/package.json index 4279f16058264..6be1d34c1e40e 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -77,7 +77,7 @@ ] }, "dependencies": { - "@next/env": "13.1.7-canary.0", + "@next/env": "13.1.7-canary.1", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -127,11 +127,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.13.3", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.1.7-canary.0", - "@next/polyfill-nomodule": "13.1.7-canary.0", - "@next/react-dev-overlay": "13.1.7-canary.0", - "@next/react-refresh-utils": "13.1.7-canary.0", - "@next/swc": "13.1.7-canary.0", + "@next/polyfill-module": "13.1.7-canary.1", + "@next/polyfill-nomodule": "13.1.7-canary.1", + "@next/react-dev-overlay": "13.1.7-canary.1", + "@next/react-refresh-utils": "13.1.7-canary.1", + "@next/swc": "13.1.7-canary.1", "@segment/ajv-human-errors": "2.1.2", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index a76899f086bed..78faf2260f649 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index fe2120f5aae39..42b0f6de9da25 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "13.1.7-canary.0", + "version": "13.1.7-canary.1", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae2417eb688da..b5b4edeb40835 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -438,7 +438,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.7-canary.0 + '@next/eslint-plugin-next': 13.1.7-canary.1 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -510,12 +510,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.7-canary.0 - '@next/polyfill-module': 13.1.7-canary.0 - '@next/polyfill-nomodule': 13.1.7-canary.0 - '@next/react-dev-overlay': 13.1.7-canary.0 - '@next/react-refresh-utils': 13.1.7-canary.0 - '@next/swc': 13.1.7-canary.0 + '@next/env': 13.1.7-canary.1 + '@next/polyfill-module': 13.1.7-canary.1 + '@next/polyfill-nomodule': 13.1.7-canary.1 + '@next/react-dev-overlay': 13.1.7-canary.1 + '@next/react-refresh-utils': 13.1.7-canary.1 + '@next/swc': 13.1.7-canary.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 From e8fcf8b569341d639757ff86cf26667e8b1be159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Tue, 31 Jan 2023 23:16:13 +0100 Subject: [PATCH 5/7] Add data attributes on @next/font usage (#45296) Adds `data-next-font` data attribute to the preload tag if added by `@next/font`. ```js // Using `size-adjust` fallback font. // Not using `size-adjust` fallback font. ``` If no fonts are preloaded, the tag is added on the preconnect tag. Fixes NEXT-350 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/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` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) 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`](https://github.com/vercel/next.js/blob/canary/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 | 3 +- packages/font/src/local/loader.ts | 7 ++- packages/next/font/index.d.ts | 7 ++- .../webpack/loaders/next-font-loader/index.ts | 11 +++- .../plugins/font-loader-manifest-plugin.ts | 16 +++++ packages/next/src/pages/_document.tsx | 10 +++- packages/next/src/server/app-render.tsx | 12 +++- test/e2e/app-dir/next-font/next-font.test.ts | 36 +++++++---- test/e2e/next-font/app/pages/with-fallback.js | 10 ++++ test/e2e/next-font/basepath.test.ts | 3 +- test/e2e/next-font/index.test.ts | 59 ++++++++++++++----- .../with-font-declarations-file.test.ts | 18 ++++-- .../next-font/without-preloaded-fonts.test.ts | 3 + 13 files changed, 155 insertions(+), 40 deletions(-) diff --git a/packages/font/src/google/loader.ts b/packages/font/src/google/loader.ts index 17e612810a223..6c07f0d3d1c88 100644 --- a/packages/font/src/google/loader.ts +++ b/packages/font/src/google/loader.ts @@ -155,7 +155,8 @@ const downloadGoogleFonts: FontLoader = async ({ const selfHostedFileUrl = emitFontFile( fontFileBuffer, ext, - preloadFontFile + preloadFontFile, + !!adjustFontFallbackMetrics ) return { diff --git a/packages/font/src/local/loader.ts b/packages/font/src/local/loader.ts index 24dcbba716473..1f0079b99bb15 100644 --- a/packages/font/src/local/loader.ts +++ b/packages/font/src/local/loader.ts @@ -75,7 +75,12 @@ const fetchFonts: FontLoader = async ({ src.map(async ({ path, style, weight, ext, format }) => { const resolved = await resolve(path) const fileBuffer = await promisify(loaderContext.fs.readFile)(resolved) - const fontUrl = emitFontFile(fileBuffer, ext, preload) + const fontUrl = emitFontFile( + fileBuffer, + ext, + preload, + typeof adjustFontFallback === 'undefined' || !!adjustFontFallback + ) let fontMetadata: any try { diff --git a/packages/next/font/index.d.ts b/packages/next/font/index.d.ts index 7abc1cf4c0f62..400900e124405 100644 --- a/packages/next/font/index.d.ts +++ b/packages/next/font/index.d.ts @@ -11,7 +11,12 @@ export type FontLoader = (options: { variableName: string data: any[] config: any - emitFontFile: (content: Buffer, ext: string, preload: boolean) => string + emitFontFile: ( + content: Buffer, + ext: string, + preload: boolean, + isUsingSizeAdjust?: boolean + ) => string resolve: (src: string) => string isDev: boolean isServer: boolean diff --git a/packages/next/src/build/webpack/loaders/next-font-loader/index.ts b/packages/next/src/build/webpack/loaders/next-font-loader/index.ts index 4694d4f00ba57..be7c30463e04f 100644 --- a/packages/next/src/build/webpack/loaders/next-font-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-font-loader/index.ts @@ -59,12 +59,19 @@ export default async function nextFontLoader(this: any) { }) ) - const emitFontFile = (content: Buffer, ext: string, preload: boolean) => { + const emitFontFile = ( + content: Buffer, + ext: string, + preload: boolean, + isUsingSizeAdjust?: boolean + ) => { const opts = { context: this.rootContext, content } const interpolatedName = loaderUtils.interpolateName( this, // Font files ending with .p.(woff|woff2|eot|ttf|otf) are preloaded - `static/media/[hash]${preload ? '.p' : ''}.${ext}`, + `static/media/[hash]${isUsingSizeAdjust ? '-s' : ''}${ + preload ? '.p' : '' + }.${ext}`, opts ) const outputPath = `${assetPrefix}/_next/${interpolatedName}` diff --git a/packages/next/src/build/webpack/plugins/font-loader-manifest-plugin.ts b/packages/next/src/build/webpack/plugins/font-loader-manifest-plugin.ts index 6b8b963b8b719..898f5140129ee 100644 --- a/packages/next/src/build/webpack/plugins/font-loader-manifest-plugin.ts +++ b/packages/next/src/build/webpack/plugins/font-loader-manifest-plugin.ts @@ -9,6 +9,8 @@ export type FontLoaderManifest = { app: { [moduleRequest: string]: string[] } + appUsingSizeAdjust: boolean + pagesUsingSizeAdjust: boolean } const PLUGIN_NAME = 'FontLoaderManifestPlugin' @@ -50,6 +52,8 @@ export class FontLoaderManifestPlugin { const fontLoaderManifest: FontLoaderManifest = { pages: {}, app: {}, + appUsingSizeAdjust: false, + pagesUsingSizeAdjust: false, } if (this.appDirEnabled) { @@ -60,6 +64,12 @@ export class FontLoaderManifestPlugin { /\.(woff|woff2|eot|ttf|otf)$/.test(file) ) + if (!fontLoaderManifest.appUsingSizeAdjust) { + fontLoaderManifest.appUsingSizeAdjust = fontFiles.some((file) => + file.includes('-s') + ) + } + // Font files ending with .p.(woff|woff2|eot|ttf|otf) are preloaded const preloadedFontFiles: string[] = fontFiles.filter( (file: string) => /\.p.(woff|woff2|eot|ttf|otf)$/.test(file) @@ -86,6 +96,12 @@ export class FontLoaderManifestPlugin { /\.(woff|woff2|eot|ttf|otf)$/.test(file) ) + if (!fontLoaderManifest.pagesUsingSizeAdjust) { + fontLoaderManifest.pagesUsingSizeAdjust = fontFiles.some((file) => + file.includes('-s') + ) + } + // Font files ending with .p.(woff|woff2|eot|ttf|otf) are preloaded const preloadedFontFiles: string[] = fontFiles.filter( (file: string) => /\.p.(woff|woff2|eot|ttf|otf)$/.test(file) diff --git a/packages/next/src/pages/_document.tsx b/packages/next/src/pages/_document.tsx index 0733e5e077a17..9381623c54154 100644 --- a/packages/next/src/pages/_document.tsx +++ b/packages/next/src/pages/_document.tsx @@ -382,7 +382,14 @@ function getFontLoaderLinks( return { preconnect: preconnectToSelf ? ( - + ) : null, preload: preloadedFontFiles ? preloadedFontFiles.map((fontFile) => { @@ -395,6 +402,7 @@ function getFontLoaderLinks( as="font" type={`font/${ext}`} crossOrigin="anonymous" + data-next-font={fontFile.includes('-s') ? 'size-adjust' : ''} /> ) }) diff --git a/packages/next/src/server/app-render.tsx b/packages/next/src/server/app-render.tsx index b42b21b4c0ba7..ffaecbdc068a6 100644 --- a/packages/next/src/server/app-render.tsx +++ b/packages/next/src/server/app-render.tsx @@ -1492,7 +1492,14 @@ export async function renderToHTMLOrFlight( return ( <> {preloadedFontFiles?.length === 0 ? ( - + ) : null} {preloadedFontFiles ? preloadedFontFiles.map((fontFile) => { @@ -1505,6 +1512,9 @@ export async function renderToHTMLOrFlight( as="font" type={`font/${ext}`} crossOrigin="anonymous" + data-next-font={ + fontFile.includes('-s') ? 'size-adjust' : '' + } /> ) }) diff --git a/test/e2e/app-dir/next-font/next-font.test.ts b/test/e2e/app-dir/next-font/next-font.test.ts index 36e7228ec6c2b..8bfc02c5144ad 100644 --- a/test/e2e/app-dir/next-font/next-font.test.ts +++ b/test/e2e/app-dir/next-font/next-font.test.ts @@ -223,23 +223,26 @@ createNextDescribe( expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2', + href: '/_next/static/media/e9b9dc0d8ba35f48-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/b61859a50be14c53.p.woff2', + href: '/_next/static/media/b61859a50be14c53-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(2).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/b2104791981359ae.p.woff2', + href: '/_next/static/media/b2104791981359ae-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) @@ -254,24 +257,27 @@ createNextDescribe( expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2', + href: '/_next/static/media/e9b9dc0d8ba35f48-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/e1053f04babc7571.p.woff2', + href: '/_next/static/media/e1053f04babc7571-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(2).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/feab2c68f2a8e9a4.p.woff2', + href: '/_next/static/media/feab2c68f2a8e9a4-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) @@ -286,17 +292,19 @@ createNextDescribe( expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2', + href: '/_next/static/media/e9b9dc0d8ba35f48-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/75c5faeeb9c86969.p.woff2', + href: '/_next/static/media/75c5faeeb9c86969-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) @@ -311,17 +319,19 @@ createNextDescribe( expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2', + href: '/_next/static/media/e9b9dc0d8ba35f48-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: '', - href: '/_next/static/media/568e4c6d8123c4d6.p.woff2', + href: '/_next/static/media/568e4c6d8123c4d6-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) }) @@ -334,6 +344,12 @@ createNextDescribe( // Preconnect expect($('link[rel="preconnect"]').length).toBe(1) + expect($('link[rel="preconnect"]').get(0).attribs).toEqual({ + crossorigin: 'anonymous', + href: '/', + rel: 'preconnect', + 'data-next-font': 'size-adjust', + }) // Preload expect($('link[as="font"]').length).toBe(0) } diff --git a/test/e2e/next-font/app/pages/with-fallback.js b/test/e2e/next-font/app/pages/with-fallback.js index 8a5e258266c52..eeebf33ccecd6 100644 --- a/test/e2e/next-font/app/pages/with-fallback.js +++ b/test/e2e/next-font/app/pages/with-fallback.js @@ -1,3 +1,4 @@ +import localFont from '@next/font/local' import { Open_Sans } from '@next/font/google' const openSans = Open_Sans({ fallback: ['system-ui', 'Arial'], @@ -5,6 +6,12 @@ const openSans = Open_Sans({ adjustFontFallback: false, }) +const myFont = localFont({ + fallback: ['system-ui', 'Arial'], + src: '../fonts/my-font.woff2', + adjustFontFallback: false, +}) + export default function WithFonts() { return ( <> @@ -21,6 +28,9 @@ export default function WithFonts() { > {JSON.stringify(openSans)} +
+ {JSON.stringify(myFont)} +
) } diff --git a/test/e2e/next-font/basepath.test.ts b/test/e2e/next-font/basepath.test.ts index bff516e58b6b0..5573ba99ba9fd 100644 --- a/test/e2e/next-font/basepath.test.ts +++ b/test/e2e/next-font/basepath.test.ts @@ -46,9 +46,10 @@ describe('@next/font/google basepath', () => { expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/dashboard/_next/static/media/0812efcfaefec5ea.p.woff2', + href: '/dashboard/_next/static/media/0812efcfaefec5ea-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) }) diff --git a/test/e2e/next-font/index.test.ts b/test/e2e/next-font/index.test.ts index cbc3a14ab9fd6..986a9ba443f10 100644 --- a/test/e2e/next-font/index.test.ts +++ b/test/e2e/next-font/index.test.ts @@ -306,16 +306,18 @@ describe('@next/font/google', () => { expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/0812efcfaefec5ea.p.woff2', + href: '/_next/static/media/0812efcfaefec5ea-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/675c25f648fd6a30.p.woff2', + href: '/_next/static/media/675c25f648fd6a30-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) @@ -331,9 +333,10 @@ describe('@next/font/google', () => { expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/0812efcfaefec5ea.p.woff2', + href: '/_next/static/media/0812efcfaefec5ea-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) }) @@ -351,11 +354,11 @@ describe('@next/font/google', () => { .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', + '/_next/static/media/02205c9944024f15-s.p.woff2', + '/_next/static/media/0812efcfaefec5ea-s.p.woff2', + '/_next/static/media/1deec1af325840fd-s.p.woff2', + '/_next/static/media/ab6fdae82d1a8d92-s.p.woff2', + '/_next/static/media/d55edb6f37902ebf-s.p.woff2', ]) }) @@ -374,14 +377,14 @@ describe('@next/font/google', () => { .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/9ac01b894b856187.p.woff2', - '/_next/static/media/c9baea324111137d.p.woff2', - '/_next/static/media/fb68b4558e2a718e.p.woff2', + '/_next/static/media/0812efcfaefec5ea-s.p.woff2', + '/_next/static/media/4f3dcdf40b3ca86d-s.p.woff2', + '/_next/static/media/560a6db6ac485cb1-s.p.woff2', + '/_next/static/media/686d1702f12625fe-s.p.woff2', + '/_next/static/media/86d92167ff02c708-s.p.woff2', + '/_next/static/media/9ac01b894b856187-s.p.woff2', + '/_next/static/media/c9baea324111137d-s.p.woff2', + '/_next/static/media/fb68b4558e2a718e-s.p.woff2', ]) }) @@ -398,11 +401,35 @@ describe('@next/font/google', () => { // From _app expect($('link[as="font"]').length).toBe(1) expect($('link[as="font"]').get(0).attribs).toEqual({ + as: 'font', + crossorigin: 'anonymous', + href: '/_next/static/media/0812efcfaefec5ea-s.p.woff2', + rel: 'preload', + type: 'font/woff2', + 'data-next-font': 'size-adjust', + }) + }) + + test('font without size adjust', async () => { + const html = await renderViaHTTP(next.url, '/with-fallback') + const $ = cheerio.load(html) + + expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', href: '/_next/static/media/0812efcfaefec5ea.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': '', + }) + + expect($('link[as="font"]').get(2).attribs).toEqual({ + as: 'font', + crossorigin: 'anonymous', + href: '/_next/static/media/ab6fdae82d1a8d92.p.woff2', + rel: 'preload', + type: 'font/woff2', + 'data-next-font': '', }) }) }) diff --git a/test/e2e/next-font/with-font-declarations-file.test.ts b/test/e2e/next-font/with-font-declarations-file.test.ts index 80b3130178af1..fcfb44beb75b1 100644 --- a/test/e2e/next-font/with-font-declarations-file.test.ts +++ b/test/e2e/next-font/with-font-declarations-file.test.ts @@ -64,17 +64,19 @@ describe('@next/font/google with-font-declarations-file', () => { expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/0812efcfaefec5ea.p.woff2', + href: '/_next/static/media/0812efcfaefec5ea-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) // From /inter expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/4a7f86e553ee7e51.p.woff2', + href: '/_next/static/media/4a7f86e553ee7e51-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) } }) @@ -96,17 +98,19 @@ describe('@next/font/google with-font-declarations-file', () => { expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/0812efcfaefec5ea.p.woff2', + href: '/_next/static/media/0812efcfaefec5ea-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) // From /roboto expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/9a7e84b4dd095b33.p.woff2', + href: '/_next/static/media/9a7e84b4dd095b33-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) } }) @@ -128,17 +132,19 @@ describe('@next/font/google with-font-declarations-file', () => { expect($('link[as="font"]').get(0).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/0812efcfaefec5ea.p.woff2', + href: '/_next/static/media/0812efcfaefec5ea-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) // From /local-font expect($('link[as="font"]').get(1).attribs).toEqual({ as: 'font', crossorigin: 'anonymous', - href: '/_next/static/media/2a931eed088772c9.p.woff2', + href: '/_next/static/media/2a931eed088772c9-s.p.woff2', rel: 'preload', type: 'font/woff2', + 'data-next-font': 'size-adjust', }) } }) diff --git a/test/e2e/next-font/without-preloaded-fonts.test.ts b/test/e2e/next-font/without-preloaded-fonts.test.ts index 2c7cf366c5a2b..a2d71bf730807 100644 --- a/test/e2e/next-font/without-preloaded-fonts.test.ts +++ b/test/e2e/next-font/without-preloaded-fonts.test.ts @@ -49,6 +49,7 @@ describe('@next/font/google without-preloaded-fonts without _app', () => { crossorigin: 'anonymous', href: '/', rel: 'preconnect', + 'data-next-font': 'size-adjust', }) // Preload @@ -108,6 +109,7 @@ describe('@next/font/google no preloads with _app', () => { crossorigin: 'anonymous', href: '/', rel: 'preconnect', + 'data-next-font': 'size-adjust', }) // Preload @@ -124,6 +126,7 @@ describe('@next/font/google no preloads with _app', () => { crossorigin: 'anonymous', href: '/', rel: 'preconnect', + 'data-next-font': 'size-adjust', }) // Preload From a47841b0ace72b7ac8acdc247895dcf73db86caa Mon Sep 17 00:00:00 2001 From: vinay <94120295+vinaykulk621@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:36:59 +0530 Subject: [PATCH 6/7] Some changes in grammar (#45467) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/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` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) 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`](https://github.com/vercel/next.js/blob/canary/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) --- .github/actions/issue-validator/canary.md | 2 +- .github/actions/issue-validator/repro.md | 2 +- .github/actions/next-stats-action/src/prepare/action-info.js | 2 +- .github/actions/next-stats-action/src/run/benchmark-url.js | 2 +- .github/pull_request_template.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/issue-validator/canary.md b/.github/actions/issue-validator/canary.md index 5d78d6246ba83..9f27fff657478 100644 --- a/.github/actions/issue-validator/canary.md +++ b/.github/actions/issue-validator/canary.md @@ -24,7 +24,7 @@ If your issue has not been resolved in that time and it has been closed/locked, ### **I did not open this issue, but it is relevant to me, what can I do to help?** -Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please **do not** comment "I have the same issue" without repro steps). Then, we can sort issues by votes to prioritize. +Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please **do not** comment "I have the same issue" without reproduction steps). Then, we can sort issues by votes to prioritize. ### **I think my reproduction is good enough, why aren't you looking into it quicker?** diff --git a/.github/actions/issue-validator/repro.md b/.github/actions/issue-validator/repro.md index c4cdcb090e68e..0dbfa09a35ad9 100644 --- a/.github/actions/issue-validator/repro.md +++ b/.github/actions/issue-validator/repro.md @@ -20,7 +20,7 @@ If your issue has _not_ been resolved in that time and it has been closed/locked ### **I did not open this issue, but it is relevant to me, what can I do to help?** -Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please **do not** comment "I have the same issue" without repro steps). Then, we can sort issues by votes to prioritize. +Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please **do not** comment "I have the same issue" without reproduction steps). Then, we can sort issues by votes to prioritize. ### **I think my reproduction is good enough, why aren't you looking into it quicker?** diff --git a/.github/actions/next-stats-action/src/prepare/action-info.js b/.github/actions/next-stats-action/src/prepare/action-info.js index fff1ffc955cb3..83aa1deea8005 100644 --- a/.github/actions/next-stats-action/src/prepare/action-info.js +++ b/.github/actions/next-stats-action/src/prepare/action-info.js @@ -69,7 +69,7 @@ module.exports = function actionInfo() { if (releaseTypes.has(info.actionName)) { info.isRelease = true } else { - // Since GITHUB_REPOSITORY and REF might not match the fork + // since GITHUB_REPOSITORY and REF might not match the fork // use event data to get repository and ref info const prData = event['pull_request'] diff --git a/.github/actions/next-stats-action/src/run/benchmark-url.js b/.github/actions/next-stats-action/src/run/benchmark-url.js index e92956a8c8dcf..956ecefe9b580 100644 --- a/.github/actions/next-stats-action/src/run/benchmark-url.js +++ b/.github/actions/next-stats-action/src/run/benchmark-url.js @@ -4,7 +4,7 @@ const parseField = (stdout = '', field = '') => { return stdout.split(field).pop().trim().split(/\s/).shift().trim() } -// benchmark a url +// benchmark an url async function benchmarkUrl( url = '', options = { diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e700d87c999e6..7820ed25bc373 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ ## Bug From e01f0e4c4a63d55836f12b3c4055e87a4e7e03dc Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 1 Feb 2023 11:00:52 +0100 Subject: [PATCH 7/7] Remove leftover server.js files in test suite (#45408) Removes leftover files that were used for `target: 'serverless'` which has been removed in Next.js 13. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/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` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) 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`](https://github.com/vercel/next.js/blob/canary/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) --- test/integration/custom-routes/server.js | 36 ------ .../dynamic-optional-routing/server.js | 46 ------- .../fixtures/with-google/server.js | 120 ------------------ .../fixtures/with-typekit/server.js | 120 ------------------ .../getserversideprops-preview/server.js | 41 ------ test/integration/prerender-preview/server.js | 41 ------ test/integration/revalidate-as-path/server.js | 38 ------ .../root-optional-revalidate/server.js | 27 ---- test/lib/next-test-utils.js | 13 +- 9 files changed, 2 insertions(+), 480 deletions(-) delete mode 100644 test/integration/custom-routes/server.js delete mode 100644 test/integration/dynamic-optional-routing/server.js delete mode 100644 test/integration/font-optimization/fixtures/with-google/server.js delete mode 100644 test/integration/font-optimization/fixtures/with-typekit/server.js delete mode 100644 test/integration/getserversideprops-preview/server.js delete mode 100644 test/integration/prerender-preview/server.js delete mode 100644 test/integration/revalidate-as-path/server.js delete mode 100644 test/integration/root-optional-revalidate/server.js diff --git a/test/integration/custom-routes/server.js b/test/integration/custom-routes/server.js deleted file mode 100644 index d31590fd3d09d..0000000000000 --- a/test/integration/custom-routes/server.js +++ /dev/null @@ -1,36 +0,0 @@ -const path = require('path') -const http = require('http') - -const server = http.createServer((req, res) => { - const pagePath = (page) => path.join('.next/serverless/pages/', page) - const render = (page) => { - require(`./${pagePath(page)}`).render(req, res) - } - const apiCall = (page) => { - require(`./${pagePath(page)}`).default(req, res) - } - - switch (req.url) { - case '/blog/post-1': { - return render('/blog/[post]') - } - case '/query-rewrite/first/second': { - return render('/with-params') - } - case '/api-hello-param/first': { - return apiCall('/api/hello') - } - case '/api-dynamic-param/first': { - return apiCall('/api/dynamic/[slug]') - } - default: { - res.statusCode(404) - return res.end('404') - } - } -}) - -const port = process.env.PORT || 3000 -server.listen(port, () => { - console.log('ready on', port) -}) diff --git a/test/integration/dynamic-optional-routing/server.js b/test/integration/dynamic-optional-routing/server.js deleted file mode 100644 index d27d2143570de..0000000000000 --- a/test/integration/dynamic-optional-routing/server.js +++ /dev/null @@ -1,46 +0,0 @@ -const path = require('path') -const http = require('http') -const url = require('url') - -const server = http.createServer((req, res) => { - const render = async (page) => { - const mod = require(`./${path.join('.next/serverless/pages/', page)}`) - try { - return await (mod.render || mod.default || mod)(req, res) - } catch (err) { - res.statusCode = 500 - return res.end('internal error') - } - } - - const { pathname } = url.parse(req.url) - - switch (pathname) { - case '/about': - return render('/about.js') - case '/api/post': - case '/api/post/hello': - case '/api/post/hello/world': { - return render('/api/post/[[...slug]].js') - } - case '/nested': - case '/nested/hello': - case '/nested/hello/world': { - return render('/nested/[[...optionalName]].js') - } - case '/': - case '/hello': - case '/hello/world': { - return render('/[[...optionalName]].js') - } - default: { - res.statusCode = 404 - return res.end('404') - } - } -}) - -const port = process.env.PORT || 3000 -server.listen(port, () => { - console.log('ready on', port) -}) diff --git a/test/integration/font-optimization/fixtures/with-google/server.js b/test/integration/font-optimization/fixtures/with-google/server.js deleted file mode 100644 index 64921f037eac9..0000000000000 --- a/test/integration/font-optimization/fixtures/with-google/server.js +++ /dev/null @@ -1,120 +0,0 @@ -const http = require('http') -const url = require('url') -const fs = require('fs') -const path = require('path') -const server = http.createServer(async (req, res) => { - let { pathname } = url.parse(req.url) - pathname = pathname.replace(/\/$/, '') - let isDataReq = false - if (pathname.startsWith('/_next/data')) { - isDataReq = true - pathname = pathname - .replace(`/_next/data/${process.env.BUILD_ID}/`, '/') - .replace(/\.json$/, '') - } - console.log('serving', pathname) - - if (pathname === '/favicon.ico') { - res.statusCode = 404 - return res.end() - } - - if (pathname.startsWith('/_next/static/')) { - let prom = Promise.resolve() - if (pathname.endsWith('.css')) { - prom = new Promise((resolve) => setTimeout(resolve, 20000)) - } - prom.then(() => { - res.write( - fs.readFileSync( - path.join( - __dirname, - './.next/static/', - decodeURI(pathname.slice('/_next/static/'.length)) - ), - 'utf8' - ) - ) - return res.end() - }) - } else { - if (pathname === '') { - pathname = '/index' - } - const ext = isDataReq ? 'json' : 'html' - if ( - fs.existsSync( - path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`) - ) - ) { - res.write( - fs.readFileSync( - path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`), - 'utf8' - ) - ) - return res.end() - } - - let re - try { - re = require(`./.next/serverless/pages${pathname}`) - } catch { - const d = decodeURI(pathname) - if ( - fs.existsSync( - path.join(__dirname, `./.next/serverless/pages${d}.${ext}`) - ) - ) { - res.write( - fs.readFileSync( - path.join(__dirname, `./.next/serverless/pages${d}.${ext}`), - 'utf8' - ) - ) - return res.end() - } - - const routesManifest = require('./.next/routes-manifest.json') - const { dynamicRoutes } = routesManifest - dynamicRoutes.some(({ page, regex }) => { - if (new RegExp(regex).test(pathname)) { - if ( - fs.existsSync( - path.join(__dirname, `./.next/serverless/pages${page}.${ext}`) - ) - ) { - res.write( - fs.readFileSync( - path.join(__dirname, `./.next/serverless/pages${page}.${ext}`), - 'utf8' - ) - ) - res.end() - return true - } - - re = require(`./.next/serverless/pages${page}`) - return true - } - return false - }) - } - if (!res.finished) { - try { - return await (typeof re.render === 'function' - ? re.render(req, res) - : re.default(req, res)) - } catch (e) { - console.log('FAIL_FUNCTION', e) - res.statusCode = 500 - res.write('FAIL_FUNCTION') - res.end() - } - } - } -}) - -server.listen(process.env.PORT, () => { - console.log('ready on', process.env.PORT) -}) diff --git a/test/integration/font-optimization/fixtures/with-typekit/server.js b/test/integration/font-optimization/fixtures/with-typekit/server.js deleted file mode 100644 index 64921f037eac9..0000000000000 --- a/test/integration/font-optimization/fixtures/with-typekit/server.js +++ /dev/null @@ -1,120 +0,0 @@ -const http = require('http') -const url = require('url') -const fs = require('fs') -const path = require('path') -const server = http.createServer(async (req, res) => { - let { pathname } = url.parse(req.url) - pathname = pathname.replace(/\/$/, '') - let isDataReq = false - if (pathname.startsWith('/_next/data')) { - isDataReq = true - pathname = pathname - .replace(`/_next/data/${process.env.BUILD_ID}/`, '/') - .replace(/\.json$/, '') - } - console.log('serving', pathname) - - if (pathname === '/favicon.ico') { - res.statusCode = 404 - return res.end() - } - - if (pathname.startsWith('/_next/static/')) { - let prom = Promise.resolve() - if (pathname.endsWith('.css')) { - prom = new Promise((resolve) => setTimeout(resolve, 20000)) - } - prom.then(() => { - res.write( - fs.readFileSync( - path.join( - __dirname, - './.next/static/', - decodeURI(pathname.slice('/_next/static/'.length)) - ), - 'utf8' - ) - ) - return res.end() - }) - } else { - if (pathname === '') { - pathname = '/index' - } - const ext = isDataReq ? 'json' : 'html' - if ( - fs.existsSync( - path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`) - ) - ) { - res.write( - fs.readFileSync( - path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`), - 'utf8' - ) - ) - return res.end() - } - - let re - try { - re = require(`./.next/serverless/pages${pathname}`) - } catch { - const d = decodeURI(pathname) - if ( - fs.existsSync( - path.join(__dirname, `./.next/serverless/pages${d}.${ext}`) - ) - ) { - res.write( - fs.readFileSync( - path.join(__dirname, `./.next/serverless/pages${d}.${ext}`), - 'utf8' - ) - ) - return res.end() - } - - const routesManifest = require('./.next/routes-manifest.json') - const { dynamicRoutes } = routesManifest - dynamicRoutes.some(({ page, regex }) => { - if (new RegExp(regex).test(pathname)) { - if ( - fs.existsSync( - path.join(__dirname, `./.next/serverless/pages${page}.${ext}`) - ) - ) { - res.write( - fs.readFileSync( - path.join(__dirname, `./.next/serverless/pages${page}.${ext}`), - 'utf8' - ) - ) - res.end() - return true - } - - re = require(`./.next/serverless/pages${page}`) - return true - } - return false - }) - } - if (!res.finished) { - try { - return await (typeof re.render === 'function' - ? re.render(req, res) - : re.default(req, res)) - } catch (e) { - console.log('FAIL_FUNCTION', e) - res.statusCode = 500 - res.write('FAIL_FUNCTION') - res.end() - } - } - } -}) - -server.listen(process.env.PORT, () => { - console.log('ready on', process.env.PORT) -}) diff --git a/test/integration/getserversideprops-preview/server.js b/test/integration/getserversideprops-preview/server.js deleted file mode 100644 index c35347a489518..0000000000000 --- a/test/integration/getserversideprops-preview/server.js +++ /dev/null @@ -1,41 +0,0 @@ -const http = require('http') -const url = require('url') -const fs = require('fs') -const path = require('path') -const server = http.createServer((req, res) => { - let { pathname } = url.parse(req.url) - if (pathname.startsWith('/_next/data')) { - pathname = pathname - .replace(`/_next/data/${process.env.BUILD_ID}/`, '/') - .replace(/\.json$/, '') - } - console.log('serving', pathname) - - if (pathname === '/favicon.ico') { - res.statusCode = 404 - return res.end() - } - - if (pathname.startsWith('/_next/static/')) { - res.write( - fs.readFileSync( - path.join( - __dirname, - './.next/static/', - pathname.slice('/_next/static/'.length) - ), - 'utf8' - ) - ) - return res.end() - } else { - const re = require(`./.next/serverless/pages${pathname}`) - return typeof re.render === 'function' - ? re.render(req, res) - : re.default(req, res) - } -}) - -server.listen(process.env.PORT, () => { - console.log('ready on', process.env.PORT) -}) diff --git a/test/integration/prerender-preview/server.js b/test/integration/prerender-preview/server.js deleted file mode 100644 index c35347a489518..0000000000000 --- a/test/integration/prerender-preview/server.js +++ /dev/null @@ -1,41 +0,0 @@ -const http = require('http') -const url = require('url') -const fs = require('fs') -const path = require('path') -const server = http.createServer((req, res) => { - let { pathname } = url.parse(req.url) - if (pathname.startsWith('/_next/data')) { - pathname = pathname - .replace(`/_next/data/${process.env.BUILD_ID}/`, '/') - .replace(/\.json$/, '') - } - console.log('serving', pathname) - - if (pathname === '/favicon.ico') { - res.statusCode = 404 - return res.end() - } - - if (pathname.startsWith('/_next/static/')) { - res.write( - fs.readFileSync( - path.join( - __dirname, - './.next/static/', - pathname.slice('/_next/static/'.length) - ), - 'utf8' - ) - ) - return res.end() - } else { - const re = require(`./.next/serverless/pages${pathname}`) - return typeof re.render === 'function' - ? re.render(req, res) - : re.default(req, res) - } -}) - -server.listen(process.env.PORT, () => { - console.log('ready on', process.env.PORT) -}) diff --git a/test/integration/revalidate-as-path/server.js b/test/integration/revalidate-as-path/server.js deleted file mode 100644 index 0a75c6aa5a425..0000000000000 --- a/test/integration/revalidate-as-path/server.js +++ /dev/null @@ -1,38 +0,0 @@ -const http = require('http') -const url = require('url') - -const render = (pagePath, req, res) => { - const mod = require(`./.next/serverless/pages/${pagePath}`) - return (mod.render || mod.default || mod)(req, res) -} - -const { BUILD_ID } = process.env - -const server = http.createServer(async (req, res) => { - try { - const { pathname } = url.parse(req.url) - - switch (pathname) { - case '/index': - case '/': - case `/_next/data/${BUILD_ID}/index.json`: - return render('index.js', req, res) - case '/another/index': - case '/another': - case `/_next/data/${BUILD_ID}/another/index.json`: - return render('another/index.js', req, res) - default: { - res.statusCode = 404 - res.end('not found') - } - } - } catch (err) { - console.error('failed to render', err) - res.statusCode = 500 - res.end(err.message) - } -}) - -server.listen(process.env.PORT, () => { - console.log('ready on', process.env.PORT) -}) diff --git a/test/integration/root-optional-revalidate/server.js b/test/integration/root-optional-revalidate/server.js deleted file mode 100644 index aa513c139e055..0000000000000 --- a/test/integration/root-optional-revalidate/server.js +++ /dev/null @@ -1,27 +0,0 @@ -const path = require('path') -const http = require('http') - -const server = http.createServer(async (req, res) => { - const render = async (page) => { - const mod = require(`./${path.join('.next/serverless/pages/', page)}`) - try { - return await (mod.render || mod.default || mod)(req, res) - } catch (err) { - res.statusCode = 500 - return res.end('internal error') - } - } - - try { - await render('/[[...slug]].js') - } catch (err) { - console.error('failed to render', err) - res.statusCode = 500 - res.end('Internal Error') - } -}) - -const port = process.env.PORT || 3000 -server.listen(port, () => { - console.log('ready on', port) -}) diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index 11c41e67809dd..98279de98e55e 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -769,22 +769,13 @@ export function readNextBuildClientPageFile(appDir, page) { export function getPagesManifest(dir) { const serverFile = path.join(dir, '.next/server/pages-manifest.json') - if (existsSync(serverFile)) { - return readJson(serverFile) - } - return readJson(path.join(dir, '.next/serverless/pages-manifest.json')) + return readJson(serverFile) } export function updatePagesManifest(dir, content) { const serverFile = path.join(dir, '.next/server/pages-manifest.json') - if (existsSync(serverFile)) { - return writeFile(serverFile, content) - } - return writeFile( - path.join(dir, '.next/serverless/pages-manifest.json'), - content - ) + return writeFile(serverFile, content) } export function getPageFileFromPagesManifest(dir, page) {