From d61b0761efae09bd9cb1201ff134ed8950d9deca Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sat, 14 Jan 2023 18:34:42 +0800 Subject: [PATCH 01/56] Use sequentialRead while encoding images with sharp (#44881) This can reduce memory usage and might improve performance on some systems. Related: https://github.com/Brooooooklyn/Image/pull/34#issuecomment-1382182686 --- packages/next/src/server/image-optimizer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/next/src/server/image-optimizer.ts b/packages/next/src/server/image-optimizer.ts index 6dc952e90c4bd..8960e99022103 100644 --- a/packages/next/src/server/image-optimizer.ts +++ b/packages/next/src/server/image-optimizer.ts @@ -408,7 +408,9 @@ export async function optimizeImage({ let optimizedBuffer = buffer if (sharp) { // Begin sharp transformation logic - const transformer = sharp(buffer) + const transformer = sharp(buffer, { + sequentialRead: true, + }) transformer.rotate() From c35aec8d9309e97b7df5df24a4a86865545fa514 Mon Sep 17 00:00:00 2001 From: Raja Vijaya Saradhi Ch Date: Sun, 15 Jan 2023 22:47:49 +0000 Subject: [PATCH 02/56] Removes title warning from cms example (#44894) Warning: Title element received an array with more than 1 element as children. ## 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: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- examples/blog-starter/pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/blog-starter/pages/index.tsx b/examples/blog-starter/pages/index.tsx index 3a5c9a380c9f5..0dc17c88e77bf 100644 --- a/examples/blog-starter/pages/index.tsx +++ b/examples/blog-starter/pages/index.tsx @@ -19,7 +19,7 @@ export default function Index({ allPosts }: Props) { <> - Next.js Blog Example with {CMS_NAME} + {`Next.js Blog Example with ${CMS_NAME}`} From 196957fd681d96464b2563e341521beba0d5f449 Mon Sep 17 00:00:00 2001 From: GJunior <37253958+rortan134@users.noreply.github.com> Date: Mon, 16 Jan 2023 02:08:11 +0100 Subject: [PATCH 03/56] Next/Script documentation beforeInteractive inconsistency (#44898) Co-authored-by: JJ Kasper Closes https://github.com/vercel/next.js/issues/43566 --- docs/api-reference/next/script.md | 23 ++++++++++++++--------- packages/create-next-app/index.ts | 1 - 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/api-reference/next/script.md b/docs/api-reference/next/script.md index b28a3903af2d1..2cf7c1d3d7e8b 100644 --- a/docs/api-reference/next/script.md +++ b/docs/api-reference/next/script.md @@ -71,22 +71,27 @@ Scripts that load with the `beforeInteractive` strategy are injected into the in Scripts denoted with this strategy are preloaded and fetched before any first-party code, but their execution does not block page hydration from occuring. -`beforeInteractive` scripts must be placed inside `pages/_app.js` and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). +`beforeInteractive` scripts must be placed inside `pages/_document.js` and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side). **This strategy should only be used for critical scripts that need to be fetched before any part of the page becomes interactive.** ```jsx +import { Html, Head, Main, NextScript } from 'next/document' import Script from 'next/script' -export default function MyApp({ Component, pageProps }) { +export default function Document() { return ( - <> - "'` - ) + it('throws when escape characters are included in nonce', async () => { + const res = await fetchWithPolicy( + `script-src 'nonce-">"'` + ) - expect(res.status).toBe(500) - }) - }) + expect(res.status).toBe(500) + }) + } + ) describe('template component', () => { it('should render the template that holds state in a client component and reset on navigation', async () => { diff --git a/test/e2e/app-dir/interpolability-with-pages/navigation.test.ts b/test/e2e/app-dir/interpolability-with-pages/navigation.test.ts index 90ba0b09a927f..0d0e85d65e893 100644 --- a/test/e2e/app-dir/interpolability-with-pages/navigation.test.ts +++ b/test/e2e/app-dir/interpolability-with-pages/navigation.test.ts @@ -41,31 +41,34 @@ describe('navigation between pages and app dir', () => { expect(await browser.elementById('app-page').text()).toBe('App Page') }) - it('It should be able to navigate pages -> app and go back an forward', async () => { - const browser = await webdriver(next.url, '/pages') - await browser - .elementById('link-to-app') - .click() - .waitForElementByCss('#app-page') - await browser.back().waitForElementByCss('#pages-page') - expect(await browser.hasElementByCssSelector('#app-page')).toBeFalse() - expect(await browser.elementById('pages-page').text()).toBe('Pages Page') - await browser.forward().waitForElementByCss('#app-page') - expect(await browser.hasElementByCssSelector('#pages-page')).toBeFalse() - expect(await browser.elementById('app-page').text()).toBe('App Page') - }) + // TODO: re-enable after 404 transition bug is addressed + if (!(global as any).isNextDeploy) { + it('It should be able to navigate pages -> app and go back an forward', async () => { + const browser = await webdriver(next.url, '/pages') + await browser + .elementById('link-to-app') + .click() + .waitForElementByCss('#app-page') + await browser.back().waitForElementByCss('#pages-page') + expect(await browser.hasElementByCssSelector('#app-page')).toBeFalse() + expect(await browser.elementById('pages-page').text()).toBe('Pages Page') + await browser.forward().waitForElementByCss('#app-page') + expect(await browser.hasElementByCssSelector('#pages-page')).toBeFalse() + expect(await browser.elementById('app-page').text()).toBe('App Page') + }) - it('It should be able to navigate app -> pages and go back and forward', async () => { - const browser = await webdriver(next.url, '/app') - await browser - .elementById('link-to-pages') - .click() - .waitForElementByCss('#pages-page') - await browser.back().waitForElementByCss('#app-page') - expect(await browser.hasElementByCssSelector('#pages-page')).toBeFalse() - expect(await browser.elementById('app-page').text()).toBe('App Page') - await browser.forward().waitForElementByCss('#pages-page') - expect(await browser.hasElementByCssSelector('#app-page')).toBeFalse() - expect(await browser.elementById('pages-page').text()).toBe('Pages Page') - }) + it('It should be able to navigate app -> pages and go back and forward', async () => { + const browser = await webdriver(next.url, '/app') + await browser + .elementById('link-to-pages') + .click() + .waitForElementByCss('#pages-page') + await browser.back().waitForElementByCss('#app-page') + expect(await browser.hasElementByCssSelector('#pages-page')).toBeFalse() + expect(await browser.elementById('app-page').text()).toBe('App Page') + await browser.forward().waitForElementByCss('#pages-page') + expect(await browser.hasElementByCssSelector('#app-page')).toBeFalse() + expect(await browser.elementById('pages-page').text()).toBe('Pages Page') + }) + } }) diff --git a/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts b/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts index ae79abb789b98..93ae8da0e302e 100644 --- a/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts +++ b/test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts @@ -13,6 +13,11 @@ createNextDescribe( }, }, ({ next }) => { + // TODO: investigate test failures on deploy + if ((global as any).isNextDeploy) { + it('should skip for deploy', () => {}) + return + } /** * All test will use a link/button to navigate to '/*-before' which should be redirected by correct redirect/rewrite to '/*-after' */ diff --git a/test/e2e/middleware-rewrites/test/index.test.ts b/test/e2e/middleware-rewrites/test/index.test.ts index 9910abbf92dba..ca92ebedeb768 100644 --- a/test/e2e/middleware-rewrites/test/index.test.ts +++ b/test/e2e/middleware-rewrites/test/index.test.ts @@ -103,6 +103,11 @@ describe('Middleware Rewrite', () => { }) it('should have props for afterFiles rewrite to SSG page', async () => { + // TODO: investigate test failure during client navigation + // on deployment + if ((global as any).isNextDeploy) { + return + } let browser = await webdriver(next.url, '/') await browser.eval(`next.router.push("/afterfiles-rewrite-ssg")`) From e78c6ec2a5271180707170f94580a7682ef46b9b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 16 Jan 2023 19:02:59 -0500 Subject: [PATCH 13/56] Replace eslint rule for `no-shadow` with `typescript-eslint/no-shadow` (#44936) --- .eslintrc.json | 3 +++ packages/next/src/build/index.ts | 4 ++-- packages/next/src/build/webpack-config.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 19d382aeb7941..b40e889bddedd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -158,6 +158,9 @@ { "files": ["packages/**/*.tsx", "packages/**/*.ts"], "rules": { + // Note: you must disable the base rule as it can report incorrect errors + "no-shadow": "off", + "@typescript-eslint/no-shadow": ["warn", { "builtinGlobals": false }], "@typescript-eslint/no-unused-vars": [ "warn", { diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index ffb0b7cc7126a..d9d27ca2fe74d 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1325,9 +1325,9 @@ export default async function build( runtimeEnvConfig ) - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow let isNextImageImported: boolean | undefined - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow let hasSsrAmpPages = false const computedManifestData = await computeFromManifest( diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index b2f0412b887a2..3bd45534c530b 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -2448,7 +2448,7 @@ export default async function getBaseWebpackConfig( devtoolRevertWarning(originalDevtool) } - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow const webpack5Config = webpackConfig as webpack.Configuration // disable lazy compilation of entries as next.js has it's own method here From 8a8a70e428f68ccfc6657e79c512e62489d585ce Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 16 Jan 2023 16:13:09 -0800 Subject: [PATCH 14/56] v13.1.3-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 500929b997f90..8c71bb74bc6f4 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.1.3-canary.0" + "version": "13.1.3-canary.1" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index d3becf191d288..3a1fd12d18358 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.3-canary.0", + "version": "13.1.3-canary.1", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 1316f184c3e67..b2afde0af0a28 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.3-canary.0", + "version": "13.1.3-canary.1", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.1.3-canary.0", + "@next/eslint-plugin-next": "13.1.3-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 55607ff535010..16f6d3b132cff 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.3-canary.0", + "version": "13.1.3-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 364b309425a91..1bca7cd170d4d 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.1.3-canary.0", + "version": "13.1.3-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 147cc69018165..c913516aec201 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.3-canary.0", + "version": "13.1.3-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 cba24cccfbd57..26b4c5738edab 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.1.3-canary.0", + "version": "13.1.3-canary.1", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 137a050cf3084..3131d445304fc 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.1.3-canary.0", + "version": "13.1.3-canary.1", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index c17b5b8455b29..4e55c077383c7 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.1.3-canary.0", + "version": "13.1.3-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 c1ff52ea1feb9..41a7471d1e744 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.3-canary.0", + "version": "13.1.3-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 4354f1ad6de17..ddd67ab4a24a7 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.3-canary.0", + "version": "13.1.3-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 bc4557f6a1fe7..fb1776829c213 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.3-canary.0", + "version": "13.1.3-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 3ea4b46f11b60..b310cb4b12021 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.1.3-canary.0", + "version": "13.1.3-canary.1", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin,rustls-tls --js false native", diff --git a/packages/next/package.json b/packages/next/package.json index 9ded1b63d1505..f2e1555ba6ff7 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.1.3-canary.0", + "version": "13.1.3-canary.1", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -76,7 +76,7 @@ ] }, "dependencies": { - "@next/env": "13.1.3-canary.0", + "@next/env": "13.1.3-canary.1", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -126,11 +126,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.13.3", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.1.3-canary.0", - "@next/polyfill-nomodule": "13.1.3-canary.0", - "@next/react-dev-overlay": "13.1.3-canary.0", - "@next/react-refresh-utils": "13.1.3-canary.0", - "@next/swc": "13.1.3-canary.0", + "@next/polyfill-module": "13.1.3-canary.1", + "@next/polyfill-nomodule": "13.1.3-canary.1", + "@next/react-dev-overlay": "13.1.3-canary.1", + "@next/react-refresh-utils": "13.1.3-canary.1", + "@next/swc": "13.1.3-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 8729b279f5d63..087210c4fb03b 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.3-canary.0", + "version": "13.1.3-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 f3b266b7bcf31..b3187579ae325 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.3-canary.0", + "version": "13.1.3-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 48d3ebbb6c24f..e3b649303a30a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -440,7 +440,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.3-canary.0 + '@next/eslint-plugin-next': 13.1.3-canary.1 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -512,12 +512,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.3-canary.0 - '@next/polyfill-module': 13.1.3-canary.0 - '@next/polyfill-nomodule': 13.1.3-canary.0 - '@next/react-dev-overlay': 13.1.3-canary.0 - '@next/react-refresh-utils': 13.1.3-canary.0 - '@next/swc': 13.1.3-canary.0 + '@next/env': 13.1.3-canary.1 + '@next/polyfill-module': 13.1.3-canary.1 + '@next/polyfill-nomodule': 13.1.3-canary.1 + '@next/react-dev-overlay': 13.1.3-canary.1 + '@next/react-refresh-utils': 13.1.3-canary.1 + '@next/swc': 13.1.3-canary.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 From 66094ef6da25f86839be6971918ba9ab5dc13cd4 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 17 Jan 2023 15:21:49 +0100 Subject: [PATCH 15/56] Ensure rootLayoutIncluded is set on partial render (#44958) --- packages/next/src/server/app-render.tsx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/app-render.tsx b/packages/next/src/server/app-render.tsx index 3b7f113f90ea9..e24438e9e30ad 100644 --- a/packages/next/src/server/app-render.tsx +++ b/packages/next/src/server/app-render.tsx @@ -1148,7 +1148,7 @@ export async function renderToHTMLOrFlight( createSegmentPath: CreateSegmentPath loaderTree: LoaderTree parentParams: { [key: string]: any } - rootLayoutIncluded?: boolean + rootLayoutIncluded: boolean firstItem?: boolean }): Promise<{ Component: React.ComponentType }> => { const layoutOrPagePath = layout?.[1] || page?.[1] @@ -1500,6 +1500,7 @@ export async function renderToHTMLOrFlight( flightRouterState, parentRendered, rscPayloadHead, + rootLayoutIncluded, }: { createSegmentPath: CreateSegmentPath loaderTreeToFilter: LoaderTree @@ -1508,10 +1509,22 @@ export async function renderToHTMLOrFlight( flightRouterState?: FlightRouterState parentRendered?: boolean rscPayloadHead: React.ReactNode + rootLayoutIncluded: boolean }): Promise => { - const [segment, parallelRoutes] = loaderTreeToFilter + const [segment, parallelRoutes, { layout }] = loaderTreeToFilter + const isLayout = typeof layout !== 'undefined' const parallelRoutesKeys = Object.keys(parallelRoutes) + /** + * Checks if the current segment is a root layout. + */ + const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded + /** + * Checks if the current segment or any level above it has a root layout. + */ + const rootLayoutIncludedAtThisLevelOrAbove = + rootLayoutIncluded || rootLayoutAtThisLevel + // Because this function walks to a deeper point in the tree to start rendering we have to track the dynamic parameters up to the point where rendering starts const segmentParam = getDynamicParamFromSegment(segment) const currentParams = @@ -1559,6 +1572,8 @@ export async function renderToHTMLOrFlight( loaderTree: loaderTreeToFilter, parentParams: currentParams, firstItem: isFirst, + // This is intentionally not "rootLayoutIncludedAtThisLevelOrAbove" as createComponentTree starts at the current level and does a check for "rootLayoutAtThisLevel" too. + rootLayoutIncluded: rootLayoutIncluded, } ) return @@ -1588,6 +1603,7 @@ export async function renderToHTMLOrFlight( parentRendered: parentRendered || renderComponentsOnThisLevel, isFirst: false, rscPayloadHead, + rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, }) if (typeof path[path.length - 1] !== 'string') { @@ -1610,6 +1626,7 @@ export async function renderToHTMLOrFlight( flightRouterState: providedFlightRouterState, isFirst: true, rscPayloadHead, + rootLayoutIncluded: false, }) ).slice(1), ] @@ -1688,6 +1705,7 @@ export async function renderToHTMLOrFlight( loaderTree: loaderTree, parentParams: {}, firstItem: true, + rootLayoutIncluded: false, }) const initialTree = createFlightRouterStateFromLoaderTree(loaderTree) From 4f410e43fb5e02d4ff12ceadc47c39f9d41b47c3 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 17 Jan 2023 15:47:33 +0100 Subject: [PATCH 16/56] v13.1.3-canary.2 --- 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 8c71bb74bc6f4..06af6cc7dae51 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.1.3-canary.1" + "version": "13.1.3-canary.2" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 3a1fd12d18358..465b38b3d8ea0 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.3-canary.1", + "version": "13.1.3-canary.2", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index b2afde0af0a28..b1677b68296e0 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.3-canary.1", + "version": "13.1.3-canary.2", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.1.3-canary.1", + "@next/eslint-plugin-next": "13.1.3-canary.2", "@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 16f6d3b132cff..391e950240aef 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.3-canary.1", + "version": "13.1.3-canary.2", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 1bca7cd170d4d..29ae689997ce3 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.1.3-canary.1", + "version": "13.1.3-canary.2", "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 c913516aec201..252cb58845c13 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.3-canary.1", + "version": "13.1.3-canary.2", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 26b4c5738edab..55c8b6ad6e0f4 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.1.3-canary.1", + "version": "13.1.3-canary.2", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 3131d445304fc..fbff2ee1331d0 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.1.3-canary.1", + "version": "13.1.3-canary.2", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 4e55c077383c7..fb1eb68661d7a 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.1.3-canary.1", + "version": "13.1.3-canary.2", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 41a7471d1e744..d040c2c8921b4 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.3-canary.1", + "version": "13.1.3-canary.2", "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 ddd67ab4a24a7..d2054e6e94f46 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.3-canary.1", + "version": "13.1.3-canary.2", "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 fb1776829c213..056e5f2b0277a 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.3-canary.1", + "version": "13.1.3-canary.2", "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 b310cb4b12021..fd535f1b3e6b6 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.1.3-canary.1", + "version": "13.1.3-canary.2", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin,rustls-tls --js false native", diff --git a/packages/next/package.json b/packages/next/package.json index f2e1555ba6ff7..123700af8f958 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.1.3-canary.1", + "version": "13.1.3-canary.2", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -76,7 +76,7 @@ ] }, "dependencies": { - "@next/env": "13.1.3-canary.1", + "@next/env": "13.1.3-canary.2", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -126,11 +126,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.13.3", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.1.3-canary.1", - "@next/polyfill-nomodule": "13.1.3-canary.1", - "@next/react-dev-overlay": "13.1.3-canary.1", - "@next/react-refresh-utils": "13.1.3-canary.1", - "@next/swc": "13.1.3-canary.1", + "@next/polyfill-module": "13.1.3-canary.2", + "@next/polyfill-nomodule": "13.1.3-canary.2", + "@next/react-dev-overlay": "13.1.3-canary.2", + "@next/react-refresh-utils": "13.1.3-canary.2", + "@next/swc": "13.1.3-canary.2", "@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 087210c4fb03b..3636464e52bff 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.3-canary.1", + "version": "13.1.3-canary.2", "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 b3187579ae325..52cabf0bf59e3 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.3-canary.1", + "version": "13.1.3-canary.2", "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 e3b649303a30a..19f8cd9bc6627 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -440,7 +440,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.3-canary.1 + '@next/eslint-plugin-next': 13.1.3-canary.2 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -512,12 +512,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.3-canary.1 - '@next/polyfill-module': 13.1.3-canary.1 - '@next/polyfill-nomodule': 13.1.3-canary.1 - '@next/react-dev-overlay': 13.1.3-canary.1 - '@next/react-refresh-utils': 13.1.3-canary.1 - '@next/swc': 13.1.3-canary.1 + '@next/env': 13.1.3-canary.2 + '@next/polyfill-module': 13.1.3-canary.2 + '@next/polyfill-nomodule': 13.1.3-canary.2 + '@next/react-dev-overlay': 13.1.3-canary.2 + '@next/react-refresh-utils': 13.1.3-canary.2 + '@next/swc': 13.1.3-canary.2 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 From 8739e61960415861ffb24f2cb790d37070f3db1b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 08:47:16 -0800 Subject: [PATCH 17/56] Update build env for test timings (#44970) --- .github/workflows/build_test_deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index a650b96bc4bc5..5ae376d6373c8 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -82,8 +82,10 @@ jobs: pnpm-store-${{ hashFiles('pnpm-lock.yaml') }} - run: pnpm install + + - run: TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} node run-tests.js --timings --write-timings -g 1/1 + - run: pnpm run build - - run: node run-tests.js --timings --write-timings -g 1/1 - id: check-release run: | From 5b5cc339919f8de4edf3e1ff946e478bbaa61091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Tue, 17 Jan 2023 17:52:50 +0100 Subject: [PATCH 18/56] Fix force-static description in next-typescript (#44951) --- packages/next/src/server/next-typescript.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/next-typescript.ts b/packages/next/src/server/next-typescript.ts index 2835fd0b3c912..81aa69fd6c247 100644 --- a/packages/next/src/server/next-typescript.ts +++ b/packages/next/src/server/next-typescript.ts @@ -63,7 +63,7 @@ const API_DOCS: Record< '"error"': 'This errors if any dynamic Hooks or fetches are used. (This is equivalent to `getStaticProps`.)', '"force-static"': - 'This forces caching of all fetches and returns empty values from `useCookies`, `useHeaders` and `useSearchParams`.', + 'This forces caching of all fetches and returns empty values from `cookies`, `headers` and `useSearchParams`.', }, link: 'https://beta.nextjs.org/docs/api-reference/segment-config#dynamic', }, From a443bb9bde414108171b2ec5893d98bc3f60a02a Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 09:02:25 -0800 Subject: [PATCH 19/56] Update to skip test timings on docs change (#44971) --- .github/workflows/build_test_deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 5ae376d6373c8..4c6eca1ae7820 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -84,6 +84,7 @@ jobs: - run: pnpm install - run: TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} node run-tests.js --timings --write-timings -g 1/1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }} - run: pnpm run build From 1e0b5bf23d172c104ca826982ca0da07efa7d55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Djakovi=C4=87?= Date: Tue, 17 Jan 2023 18:11:16 +0100 Subject: [PATCH 20/56] Fix `with-stencil` example (#44964) Co-authored-by: JJ Kasper --- examples/with-stencil/packages/test-component/package.json | 2 ++ examples/with-stencil/packages/web-app/package.json | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/with-stencil/packages/test-component/package.json b/examples/with-stencil/packages/test-component/package.json index 4115150573794..1a12ad220e3dd 100644 --- a/examples/with-stencil/packages/test-component/package.json +++ b/examples/with-stencil/packages/test-component/package.json @@ -1,4 +1,6 @@ { + "name": "test-component", + "version": "1.0.0", "private": true, "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/examples/with-stencil/packages/web-app/package.json b/examples/with-stencil/packages/web-app/package.json index 2bd3a4ce885ad..75e91db0ea17e 100644 --- a/examples/with-stencil/packages/web-app/package.json +++ b/examples/with-stencil/packages/web-app/package.json @@ -1,4 +1,6 @@ { + "name": "web-app", + "version": "1.0.0", "private": true, "scripts": { "dev": "next", From b04fc99e6ada9c4fe09165c6a924526057afa57a Mon Sep 17 00:00:00 2001 From: Minju Kim <77158595+deli-ght@users.noreply.github.com> Date: Wed, 18 Jan 2023 02:14:06 +0900 Subject: [PATCH 21/56] update redirects.md (#44960) --- docs/api-reference/next.config.js/redirects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next.config.js/redirects.md b/docs/api-reference/next.config.js/redirects.md index ab5b48402dd40..68564f6de1088 100644 --- a/docs/api-reference/next.config.js/redirects.md +++ b/docs/api-reference/next.config.js/redirects.md @@ -251,7 +251,7 @@ module.exports = { { // does not add /docs since basePath: false is set source: '/without-basePath', - destination: '/another', + destination: 'https://example.com', basePath: false, permanent: false, }, From 3a9bfe60d228fc2fd8fe65b76d49a0d21df4ecc7 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 17 Jan 2023 19:36:00 +0100 Subject: [PATCH 22/56] Fix CSS imports being tracked multiple times (#44938) Currently the way our renderer injects CSS is to first track CSS imports on the module level, and then render these links on each layer. However, in a complex application it's possible that one CSS being imported in many modules, and in multiple layouts. This causes an issue of duplication. And if there are many rules the order could be messed up by that. This PR deduplicates CSS resources used by one entry (layout, page, error, ...) and all its parent layouts. If an entry is rendered, all its ancestors are rendered too. See test case for more details. Currently those two tests will all fail. Fixes #42862. ## Bug - [ ] Related issues linked using `fixes #number` - [x] 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) --- .../build/webpack/loaders/next-app-loader.ts | 44 +++++++----- .../plugins/flight-client-entry-plugin.ts | 22 +++--- packages/next/src/server/app-render.tsx | 70 ++++++++++++++++--- .../app/css/css-duplicate-2/client/page.js | 7 ++ .../app/app/css/css-duplicate-2/layout.js | 5 ++ .../app/css/css-duplicate-2/server/page.js | 5 ++ .../app/css/css-duplicate-2/style.module.css | 3 + test/e2e/app-dir/app/index.test.ts | 21 ++++++ 8 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 test/e2e/app-dir/app/app/css/css-duplicate-2/client/page.js create mode 100644 test/e2e/app-dir/app/app/css/css-duplicate-2/layout.js create mode 100644 test/e2e/app-dir/app/app/css/css-duplicate-2/server/page.js create mode 100644 test/e2e/app-dir/app/app/css/css-duplicate-2/style.module.css diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index 2fade9d19f356..b6ee93ff9ec23 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -49,7 +49,9 @@ async function createTreeCodeFromPath({ async function createSubtreePropsFromSegmentPath( segments: string[] - ): Promise { + ): Promise<{ + treeCode: string + }> { const segmentPath = segments.join('/') // Existing tree are the children of the current segment @@ -78,10 +80,9 @@ async function createTreeCodeFromPath({ } const parallelSegmentPath = segmentPath + '/' + parallelSegment - const subtree = await createSubtreePropsFromSegmentPath([ - ...segments, - parallelSegment, - ]) + const { treeCode: subtreeCode } = await createSubtreePropsFromSegmentPath( + [...segments, parallelSegment] + ) // `page` is not included here as it's added above. const filePaths = await Promise.all( @@ -93,10 +94,11 @@ async function createTreeCodeFromPath({ }) ) + const layoutPath = filePaths.find( + ([type, path]) => type === 'layout' && !!path + )?.[1] if (!rootLayout) { - rootLayout = filePaths.find( - ([type, path]) => type === 'layout' && !!path - )?.[1] + rootLayout = layoutPath } if (!globalError) { @@ -107,7 +109,7 @@ async function createTreeCodeFromPath({ props[parallelKey] = `[ '${parallelSegment}', - ${subtree}, + ${subtreeCode}, { ${filePaths .filter(([, filePath]) => filePath !== undefined) @@ -115,6 +117,7 @@ async function createTreeCodeFromPath({ if (filePath === undefined) { return '' } + return `'${file}': [() => import(/* webpackMode: "eager" */ ${JSON.stringify( filePath )}), ${JSON.stringify(filePath)}],` @@ -124,15 +127,22 @@ async function createTreeCodeFromPath({ ]` } - return `{ - ${Object.entries(props) - .map(([key, value]) => `${key}: ${value}`) - .join(',\n')} - }` + return { + treeCode: `{ + ${Object.entries(props) + .map(([key, value]) => `${key}: ${value}`) + .join(',\n')} + }`, + } } - const tree = await createSubtreePropsFromSegmentPath([]) - return [`const tree = ${tree}.children;`, pages, rootLayout, globalError] + const { treeCode } = await createSubtreePropsFromSegmentPath([]) + return { + treeCode: `const tree = ${treeCode}.children;`, + pages, + rootLayout, + globalError, + } } function createAbsolutePath(appDir: string, pathToTurnAbsolute: string) { @@ -220,7 +230,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{ } } - const [treeCode, pages, rootLayout, globalError] = + const { treeCode, pages, rootLayout, globalError } = await createTreeCodeFromPath({ pagePath, resolve: resolver, diff --git a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts index be2293d82ae94..f40345dcf7754 100644 --- a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts @@ -236,7 +236,7 @@ export class FlightClientEntryPlugin { // After optimizing all the modules, we collect the CSS that are still used // by the certain chunk. compilation.hooks.afterOptimizeModules.tap(PLUGIN_NAME, () => { - const cssImportsForChunk: Record = {} + const cssImportsForChunk: Record> = {} if (this.isEdgeServer) { edgeServerCSSManifest = {} } else { @@ -252,7 +252,7 @@ export class FlightClientEntryPlugin { const modId = resource if (modId) { if (regexCSS.test(modId)) { - cssImportsForChunk[entryName].push(modId) + cssImportsForChunk[entryName].add(modId) } } } @@ -266,7 +266,7 @@ export class FlightClientEntryPlugin { const entryName = path.join(this.appDir, '..', chunk.name) if (!cssImportsForChunk[entryName]) { - cssImportsForChunk[entryName] = [] + cssImportsForChunk[entryName] = new Set() } const chunkModules = compilation.chunkGraph.getChunkModulesIterable( @@ -285,7 +285,7 @@ export class FlightClientEntryPlugin { const entryCSSInfo: Record = cssManifest.__entry_css_mods__ || {} - entryCSSInfo[entryName] = cssImportsForChunk[entryName] + entryCSSInfo[entryName] = [...cssImportsForChunk[entryName]] Object.assign(cssManifest, { __entry_css_mods__: entryCSSInfo, @@ -318,12 +318,18 @@ export class FlightClientEntryPlugin { } }) + const tracked = new Set() for (const connection of compilation.moduleGraph.getOutgoingConnections( entryModule )) { const entryDependency = connection.dependency const entryRequest = connection.dependency.request + // It is possible that the same entry is added multiple times in the + // connection graph. We can just skip these to speed up the process. + if (tracked.has(entryRequest)) continue + tracked.add(entryRequest) + const [, cssImports] = this.collectClientComponentsAndCSSForDependency({ entryRequest, @@ -403,7 +409,7 @@ export class FlightClientEntryPlugin { */ const visitedBySegment: { [segment: string]: Set } = {} const clientComponentImports: ClientComponentImports = [] - const serverCSSImports: CssImports = {} + const CSSImports: CssImports = {} const filterClientComponents = ( dependencyToFilter: any, @@ -451,8 +457,8 @@ export class FlightClientEntryPlugin { } } - serverCSSImports[entryRequest] = serverCSSImports[entryRequest] || [] - serverCSSImports[entryRequest].push(modRequest) + CSSImports[entryRequest] = CSSImports[entryRequest] || [] + CSSImports[entryRequest].push(modRequest) } // Check if request is for css file. @@ -483,7 +489,7 @@ export class FlightClientEntryPlugin { // Traverse the module graph to find all client components. filterClientComponents(dependency, false) - return [clientComponentImports, serverCSSImports] + return [clientComponentImports, CSSImports] } injectClientEntryAndSSRModules({ diff --git a/packages/next/src/server/app-render.tsx b/packages/next/src/server/app-render.tsx index e24438e9e30ad..80981fac3d2d5 100644 --- a/packages/next/src/server/app-render.tsx +++ b/packages/next/src/server/app-render.tsx @@ -704,7 +704,9 @@ function getCssInlinedLinkTags( serverComponentManifest: FlightManifest, serverCSSManifest: FlightCSSManifest, filePath: string, - serverCSSForEntries: string[] + serverCSSForEntries: string[], + injectedCSS: Set, + collectNewCSSImports?: boolean ): string[] { const layoutOrPageCssModules = serverCSSManifest[filePath] @@ -721,12 +723,26 @@ function getCssInlinedLinkTags( for (const mod of layoutOrPageCssModules) { // We only include the CSS if it's a global CSS, or it is used by this // entrypoint. - if (serverCSSForEntries.includes(mod) || !/\.module\.css/.test(mod)) { - const modData = serverComponentManifest[mod] - if (modData) { - for (const chunk of modData.default.chunks) { - if (cssFilesForEntry.has(chunk)) { - chunks.add(chunk) + if ( + serverCSSForEntries.includes(mod) || + !/\.module\.(css|sass|scss)$/.test(mod) + ) { + // If the CSS is already injected by a parent layer, we don't need + // to inject it again. + if (!injectedCSS.has(mod)) { + const modData = serverComponentManifest[mod] + if (modData) { + for (const chunk of modData.default.chunks) { + // If the current entry in the final tree-shaked bundle has that CSS + // chunk, it means that it's actually used. We should include it. + if (cssFilesForEntry.has(chunk)) { + chunks.add(chunk) + // This might be a new layout, and to make it more efficient and + // not introducing another loop, we mutate the set directly. + if (collectNewCSSImports) { + injectedCSS.add(mod) + } + } } } } @@ -1098,16 +1114,19 @@ export async function renderToHTMLOrFlight( filePath, getComponent, shouldPreload, + injectedCSS, }: { filePath: string getComponent: () => any shouldPreload?: boolean + injectedCSS: Set }): Promise => { const cssHrefs = getCssInlinedLinkTags( serverComponentManifest, serverCSSManifest, filePath, - serverCSSForEntries + serverCSSForEntries, + injectedCSS ) const styles = cssHrefs @@ -1144,20 +1163,26 @@ export async function renderToHTMLOrFlight( parentParams, firstItem, rootLayoutIncluded, + injectedCSS, }: { createSegmentPath: CreateSegmentPath loaderTree: LoaderTree parentParams: { [key: string]: any } rootLayoutIncluded: boolean firstItem?: boolean + injectedCSS: Set }): Promise<{ Component: React.ComponentType }> => { const layoutOrPagePath = layout?.[1] || page?.[1] + + const injectedCSSWithCurrentLayout = new Set(injectedCSS) const stylesheets: string[] = layoutOrPagePath ? getCssInlinedLinkTags( serverComponentManifest, serverCSSManifest!, layoutOrPagePath, - serverCSSForEntries + serverCSSForEntries, + injectedCSSWithCurrentLayout, + true ) : [] @@ -1176,6 +1201,7 @@ export async function renderToHTMLOrFlight( filePath: template[1], getComponent: template[0], shouldPreload: true, + injectedCSS: injectedCSSWithCurrentLayout, }) : [React.Fragment] @@ -1183,6 +1209,7 @@ export async function renderToHTMLOrFlight( ? await createComponentAndStyles({ filePath: error[1], getComponent: error[0], + injectedCSS: injectedCSSWithCurrentLayout, }) : [] @@ -1190,6 +1217,7 @@ export async function renderToHTMLOrFlight( ? await createComponentAndStyles({ filePath: loading[1], getComponent: loading[0], + injectedCSS: injectedCSSWithCurrentLayout, }) : [] @@ -1215,6 +1243,7 @@ export async function renderToHTMLOrFlight( ? await createComponentAndStyles({ filePath: notFound[1], getComponent: notFound[0], + injectedCSS: injectedCSSWithCurrentLayout, }) : rootLayoutAtThisLevel ? [DefaultNotFound] @@ -1354,6 +1383,7 @@ export async function renderToHTMLOrFlight( loaderTree: parallelRoutes[parallelRouteKey], parentParams: currentParams, rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, + injectedCSS: injectedCSSWithCurrentLayout, }) const childProp: ChildProp = { @@ -1500,6 +1530,7 @@ export async function renderToHTMLOrFlight( flightRouterState, parentRendered, rscPayloadHead, + injectedCSS, rootLayoutIncluded, }: { createSegmentPath: CreateSegmentPath @@ -1509,6 +1540,7 @@ export async function renderToHTMLOrFlight( flightRouterState?: FlightRouterState parentRendered?: boolean rscPayloadHead: React.ReactNode + injectedCSS: Set rootLayoutIncluded: boolean }): Promise => { const [segment, parallelRoutes, { layout }] = loaderTreeToFilter @@ -1572,6 +1604,7 @@ export async function renderToHTMLOrFlight( loaderTree: loaderTreeToFilter, parentParams: currentParams, firstItem: isFirst, + injectedCSS, // This is intentionally not "rootLayoutIncludedAtThisLevelOrAbove" as createComponentTree starts at the current level and does a check for "rootLayoutAtThisLevel" too. rootLayoutIncluded: rootLayoutIncluded, } @@ -1584,6 +1617,22 @@ export async function renderToHTMLOrFlight( ] } + // If we are not rendering on this level we need to check if the current + // segment has a layout. If so, we need to track all the used CSS to make + // the result consistent. + const layoutPath = layout?.[1] + const injectedCSSWithCurrentLayout = new Set(injectedCSS) + if (layoutPath) { + getCssInlinedLinkTags( + serverComponentManifest, + serverCSSManifest!, + layoutPath, + serverCSSForEntries, + injectedCSSWithCurrentLayout, + true + ) + } + // Walk through all parallel routes. for (const parallelRouteKey of parallelRoutesKeys) { const parallelRoute = parallelRoutes[parallelRouteKey] @@ -1603,6 +1652,7 @@ export async function renderToHTMLOrFlight( parentRendered: parentRendered || renderComponentsOnThisLevel, isFirst: false, rscPayloadHead, + injectedCSS: injectedCSSWithCurrentLayout, rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, }) @@ -1626,6 +1676,7 @@ export async function renderToHTMLOrFlight( flightRouterState: providedFlightRouterState, isFirst: true, rscPayloadHead, + injectedCSS: new Set(), rootLayoutIncluded: false, }) ).slice(1), @@ -1705,6 +1756,7 @@ export async function renderToHTMLOrFlight( loaderTree: loaderTree, parentParams: {}, firstItem: true, + injectedCSS: new Set(), rootLayoutIncluded: false, }) const initialTree = createFlightRouterStateFromLoaderTree(loaderTree) diff --git a/test/e2e/app-dir/app/app/css/css-duplicate-2/client/page.js b/test/e2e/app-dir/app/app/css/css-duplicate-2/client/page.js new file mode 100644 index 0000000000000..59a1fdae2f1bc --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-duplicate-2/client/page.js @@ -0,0 +1,7 @@ +'use client' + +import styles from '../style.module.css' + +export default function Page() { + return
Hello
+} diff --git a/test/e2e/app-dir/app/app/css/css-duplicate-2/layout.js b/test/e2e/app-dir/app/app/css/css-duplicate-2/layout.js new file mode 100644 index 0000000000000..bb5affda2c4e4 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-duplicate-2/layout.js @@ -0,0 +1,5 @@ +import styles from './style.module.css' + +export default function Layout({ children }) { + return
{children}
+} diff --git a/test/e2e/app-dir/app/app/css/css-duplicate-2/server/page.js b/test/e2e/app-dir/app/app/css/css-duplicate-2/server/page.js new file mode 100644 index 0000000000000..fa11f6dbd9511 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-duplicate-2/server/page.js @@ -0,0 +1,5 @@ +import styles from '../style.module.css' + +export default function Page() { + return
Hello
+} diff --git a/test/e2e/app-dir/app/app/css/css-duplicate-2/style.module.css b/test/e2e/app-dir/app/app/css/css-duplicate-2/style.module.css new file mode 100644 index 0000000000000..36d9096c8210e --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-duplicate-2/style.module.css @@ -0,0 +1,3 @@ +.foo::before { + content: '_randomized_string_for_testing_'; +} diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index fdae03d32ff97..3df9b4b1a939e 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -1466,6 +1466,27 @@ createNextDescribe( if (isDev) { describe('multiple entries', () => { + it('should only inject the same style once if used by different layers', async () => { + const browser = await next.browser('/css/css-duplicate-2/client') + expect( + await browser.eval( + `[...document.styleSheets].filter(({ cssRules }) => + [...cssRules].some(({ cssText }) => (cssText||'').includes('_randomized_string_for_testing_')) + ).length` + ) + ).toBe(1) + }) + + it('should only include the same style once in the flight data', async () => { + const initialHtml = await next.render('/css/css-duplicate-2/server') + + // Even if it's deduped by Float, it should still only be included once in the payload. + // There are two matches, one for the rendered and one for the flight data. + expect( + initialHtml.match(/duplicate-2_style_module_css\.css/g).length + ).toBe(2) + }) + it('should only load chunks for the css module that is used by the specific entrypoint', async () => { // Visit /b first await next.render('/css/css-duplicate/b') From aeef38e80e8a5d5b90a0c290f9d1b64d62ff9846 Mon Sep 17 00:00:00 2001 From: Jesse Zhu Date: Wed, 18 Jan 2023 03:56:53 +0900 Subject: [PATCH 23/56] Use withoutEnlargement instead of sharp.metadata() (#44890) --- packages/next/src/server/image-optimizer.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/next/src/server/image-optimizer.ts b/packages/next/src/server/image-optimizer.ts index 8960e99022103..8d35257ae0cda 100644 --- a/packages/next/src/server/image-optimizer.ts +++ b/packages/next/src/server/image-optimizer.ts @@ -417,11 +417,9 @@ export async function optimizeImage({ if (height) { transformer.resize(width, height) } else { - const { width: metaWidth } = await transformer.metadata() - - if (metaWidth && metaWidth > width) { - transformer.resize(width) - } + transformer.resize(width, undefined, { + withoutEnlargement: true, + }) } if (contentType === AVIF) { From 098305024681c15166ad8d9af6a06acda16a316f Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 11:11:06 -0800 Subject: [PATCH 24/56] Add AsyncLocalStorage to edge-runtime APIs (#44975) --- docs/api-reference/edge-runtime.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api-reference/edge-runtime.md b/docs/api-reference/edge-runtime.md index 0a2704132b84e..51e199bffa0cd 100644 --- a/docs/api-reference/edge-runtime.md +++ b/docs/api-reference/edge-runtime.md @@ -111,6 +111,10 @@ The Next.js Edge Runtime is based on standard Web APIs, which is used by [Middle - [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) - [`WebAssembly`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly) +## Next.js Specific Polyfills + +- [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) + ## Environment Variables You can use `process.env` to access [Environment Variables](/docs/basic-features/environment-variables.md) for both `next dev` and `next build`. From 2bc8e740c7550af2e63c766dc24065dd78b89fdd Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 12:35:07 -0800 Subject: [PATCH 25/56] Update res.revalidate to pass through headers (#44981) --- packages/next/src/server/api-utils/node.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/next/src/server/api-utils/node.ts b/packages/next/src/server/api-utils/node.ts index ebaa95abc6fe0..3dd2b99367252 100644 --- a/packages/next/src/server/api-utils/node.ts +++ b/packages/next/src/server/api-utils/node.ts @@ -353,6 +353,15 @@ function setPreviewData( return res } +const filteredRevalidateHeaders = new Set([ + 'content-type', + 'content-encoding', + 'accept', + 'accept-language', + 'accept-encoding', + 'connection', +]) + async function revalidate( urlPath: string, opts: { @@ -366,7 +375,7 @@ async function revalidate( `Invalid urlPath provided to revalidate(), must be a path e.g. /blog/post-1, received ${urlPath}` ) } - const revalidateHeaders = { + const revalidateHeaders: HeadersInit = { [PRERENDER_REVALIDATE_HEADER]: context.previewModeId, ...(opts.unstable_onlyGenerated ? { @@ -375,14 +384,17 @@ async function revalidate( : {}), } + for (const key of Object.keys(req.headers)) { + if (!filteredRevalidateHeaders.has(key) && !key.startsWith('x-vercel')) { + revalidateHeaders[key] = req.headers[key] as string + } + } + try { if (context.trustHostHeader) { const res = await fetch(`https://${req.headers.host}${urlPath}`, { method: 'HEAD', - headers: { - ...revalidateHeaders, - cookie: req.headers.cookie || '', - }, + headers: revalidateHeaders, }) // we use the cache header to determine successful revalidate as // a non-200 status code can be returned from a successful revalidate From f741b888dd8ba39b84efb021f0edf0f1cf1fae33 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 12:39:36 -0800 Subject: [PATCH 26/56] v13.1.3-canary.3 --- 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 06af6cc7dae51..2c75ad2987d61 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.1.3-canary.2" + "version": "13.1.3-canary.3" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 465b38b3d8ea0..b98406f64a1ed 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.3-canary.2", + "version": "13.1.3-canary.3", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index b1677b68296e0..cb5a6c65cebcb 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.3-canary.2", + "version": "13.1.3-canary.3", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.1.3-canary.2", + "@next/eslint-plugin-next": "13.1.3-canary.3", "@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 391e950240aef..9837f5cd932db 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.3-canary.2", + "version": "13.1.3-canary.3", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 29ae689997ce3..5af4185007e97 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.1.3-canary.2", + "version": "13.1.3-canary.3", "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 252cb58845c13..b62532e66f23a 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.3-canary.2", + "version": "13.1.3-canary.3", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 55c8b6ad6e0f4..d58207f3f0341 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.1.3-canary.2", + "version": "13.1.3-canary.3", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index fbff2ee1331d0..a7d976b9366e6 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.1.3-canary.2", + "version": "13.1.3-canary.3", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index fb1eb68661d7a..5e341d2a257e2 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.1.3-canary.2", + "version": "13.1.3-canary.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index d040c2c8921b4..00caadd05868b 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.3-canary.2", + "version": "13.1.3-canary.3", "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 d2054e6e94f46..262a10189418a 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.3-canary.2", + "version": "13.1.3-canary.3", "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 056e5f2b0277a..a4602ec0fb215 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.3-canary.2", + "version": "13.1.3-canary.3", "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 fd535f1b3e6b6..9f2e402ab8dfd 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.1.3-canary.2", + "version": "13.1.3-canary.3", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin,rustls-tls --js false native", diff --git a/packages/next/package.json b/packages/next/package.json index 123700af8f958..fb1c85bbc6c72 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.1.3-canary.2", + "version": "13.1.3-canary.3", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -76,7 +76,7 @@ ] }, "dependencies": { - "@next/env": "13.1.3-canary.2", + "@next/env": "13.1.3-canary.3", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -126,11 +126,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.13.3", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.1.3-canary.2", - "@next/polyfill-nomodule": "13.1.3-canary.2", - "@next/react-dev-overlay": "13.1.3-canary.2", - "@next/react-refresh-utils": "13.1.3-canary.2", - "@next/swc": "13.1.3-canary.2", + "@next/polyfill-module": "13.1.3-canary.3", + "@next/polyfill-nomodule": "13.1.3-canary.3", + "@next/react-dev-overlay": "13.1.3-canary.3", + "@next/react-refresh-utils": "13.1.3-canary.3", + "@next/swc": "13.1.3-canary.3", "@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 3636464e52bff..5a006d389f90a 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.3-canary.2", + "version": "13.1.3-canary.3", "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 52cabf0bf59e3..3d29199d3b7b6 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.3-canary.2", + "version": "13.1.3-canary.3", "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 19f8cd9bc6627..f582d2d9a93fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -440,7 +440,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.3-canary.2 + '@next/eslint-plugin-next': 13.1.3-canary.3 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -512,12 +512,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.3-canary.2 - '@next/polyfill-module': 13.1.3-canary.2 - '@next/polyfill-nomodule': 13.1.3-canary.2 - '@next/react-dev-overlay': 13.1.3-canary.2 - '@next/react-refresh-utils': 13.1.3-canary.2 - '@next/swc': 13.1.3-canary.2 + '@next/env': 13.1.3-canary.3 + '@next/polyfill-module': 13.1.3-canary.3 + '@next/polyfill-nomodule': 13.1.3-canary.3 + '@next/react-dev-overlay': 13.1.3-canary.3 + '@next/react-refresh-utils': 13.1.3-canary.3 + '@next/swc': 13.1.3-canary.3 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 From 034ee8ac8bc3d42bbf9e92ce111f46ea6ce78a5f Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 13:14:43 -0800 Subject: [PATCH 27/56] Update some flakey acceptance tests (#44984) --- .../ReactRefreshLogBox-scss.test.ts | 4 +-- .../acceptance-app/ReactRefreshLogBox.test.ts | 22 ++++++++-------- .../ReactRefreshLogBoxMisc.test.ts | 8 +++--- .../acceptance-app/ReactRefreshModule.test.ts | 4 +-- .../ReactRefreshRegression.test.ts | 15 ++++++----- .../ReactRefreshLogBox-app-doc.test.ts | 8 +++--- .../ReactRefreshLogBox-scss.test.ts | 2 +- .../acceptance/ReactRefreshLogBox.test.ts | 26 +++++++++---------- .../acceptance/ReactRefreshLogBoxMisc.test.ts | 8 +++--- .../acceptance/ReactRefreshModule.test.ts | 4 +-- .../acceptance/ReactRefreshRegression.test.ts | 15 ++++++----- 11 files changed, 61 insertions(+), 55 deletions(-) diff --git a/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts b/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts index 2d9328fe15e66..2524348db8e82 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts @@ -38,7 +38,7 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Syntax error await session.patch('index.module.scss', `.button { font-size: :5px; }`) @@ -48,7 +48,7 @@ describe('ReactRefreshLogBox app', () => { // Fix syntax error await session.patch('index.module.scss', `.button { font-size: 5px; }`) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Not local error await session.patch('index.module.scss', `button { font-size: 5px; }`) diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts index 686374f2f7f6e..70ecdaa79ed54 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts @@ -107,7 +107,7 @@ for (const variant of ['default', 'turbo']) { /Count: 1/ ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -169,7 +169,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(await session.hasErrorToast()).toBe(false) expect( @@ -180,7 +180,7 @@ for (const variant of ['default', 'turbo']) { await session.evaluate(() => document.querySelector('p').textContent) ).toBe('Count: 2') - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(await session.hasErrorToast()).toBe(false) await cleanup() @@ -242,7 +242,7 @@ for (const variant of ['default', 'turbo']) { // TODO-APP: re-enable when error recovery doesn't reload the page. // expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('Hello') @@ -526,7 +526,7 @@ for (const variant of ['default', 'turbo']) { ) // Expected: this fixes the problem - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -701,7 +701,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -752,7 +752,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello') @@ -784,7 +784,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello new') @@ -810,7 +810,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Syntax error await session.patch('index.module.css', `.button {`) @@ -1219,7 +1219,7 @@ for (const variant of ['default', 'turbo']) { () => browser.elementByCss('.nextjs-toast-errors').text(), /4 errors/ ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Add Component error await session.patch( @@ -1328,7 +1328,7 @@ for (const variant of ['default', 'turbo']) { expect(await browser.waitForElementByCss('#text').text()).toBe( 'Hello world' ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Re-add error await session.patch( diff --git a/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts b/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts index 83768772c3e9a..6e8b1deb48ff4 100644 --- a/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts @@ -88,7 +88,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -112,7 +112,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -136,7 +136,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -160,7 +160,7 @@ describe.skip('ReactRefreshLogBox app', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', diff --git a/test/development/acceptance-app/ReactRefreshModule.test.ts b/test/development/acceptance-app/ReactRefreshModule.test.ts index 614148ace38e6..ba562fe1908ee 100644 --- a/test/development/acceptance-app/ReactRefreshModule.test.ts +++ b/test/development/acceptance-app/ReactRefreshModule.test.ts @@ -20,7 +20,7 @@ describe('ReactRefreshModule app', () => { it('should allow any variable names', async () => { const { session, cleanup } = await sandbox(next, new Map([])) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) const variables = [ '_a', @@ -39,7 +39,7 @@ describe('ReactRefreshModule app', () => { return null }` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(next.cliOutput).not.toContain( `'${variable}' has already been declared` ) diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts index 216047ee35162..56a2856407eb9 100644 --- a/test/development/acceptance-app/ReactRefreshRegression.test.ts +++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts @@ -3,6 +3,7 @@ import { sandbox } from './helpers' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import path from 'path' +import { check } from 'next-test-utils' describe('ReactRefreshRegression app', () => { let next: NextInstance @@ -80,7 +81,7 @@ describe('ReactRefreshRegression app', () => { ) // Verify no hydration mismatch: - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -250,9 +251,11 @@ describe('ReactRefreshRegression app', () => { ` ) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('0') + await check( + () => session.evaluate(() => document.querySelector('p').textContent), + '0' + ) + await session.evaluate(() => document.querySelector('button').click()) expect( await session.evaluate(() => document.querySelector('p').textContent) @@ -356,7 +359,7 @@ describe('ReactRefreshRegression app', () => { let didNotReload = await session.patch('app/content.mdx', `Hello Foo!`) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate( () => document.querySelector('#content').textContent @@ -365,7 +368,7 @@ describe('ReactRefreshRegression app', () => { didNotReload = await session.patch('app/content.mdx', `Hello Bar!`) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate( () => document.querySelector('#content').textContent diff --git a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts index 6edbe5938cd30..120af5a9178b4 100644 --- a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts @@ -41,7 +41,7 @@ for (const variant of ['default', 'turbo']) { export default MyApp ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -89,7 +89,7 @@ for (const variant of ['default', 'turbo']) { export default MyDocument ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -120,7 +120,7 @@ for (const variant of ['default', 'turbo']) { export default MyApp ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -187,7 +187,7 @@ for (const variant of ['default', 'turbo']) { export default MyDocument ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) }) diff --git a/test/development/acceptance/ReactRefreshLogBox-scss.test.ts b/test/development/acceptance/ReactRefreshLogBox-scss.test.ts index 93fcaf5cba6d1..2c83053ea5cab 100644 --- a/test/development/acceptance/ReactRefreshLogBox-scss.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-scss.test.ts @@ -37,7 +37,7 @@ describe.skip('ReactRefreshLogBox', () => { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Syntax error await session.patch('index.module.scss', `.button { font-size: :5px; }`) diff --git a/test/development/acceptance/ReactRefreshLogBox.test.ts b/test/development/acceptance/ReactRefreshLogBox.test.ts index 7fdb666390d70..8f17aa8ba1a36 100644 --- a/test/development/acceptance/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox.test.ts @@ -100,7 +100,7 @@ for (const variant of ['default', 'turbo']) { /Count: 1/ ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -162,7 +162,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) @@ -172,7 +172,7 @@ for (const variant of ['default', 'turbo']) { await session.evaluate(() => document.querySelector('p').textContent) ).toBe('Count: 2') - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -231,7 +231,7 @@ for (const variant of ['default', 'turbo']) { ) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('Hello') @@ -422,7 +422,7 @@ for (const variant of ['default', 'turbo']) { ) // Expected: this fixes the problem - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -597,7 +597,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -648,7 +648,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello') @@ -680,7 +680,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello new') @@ -706,7 +706,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) // Syntax error await session.patch('index.module.css', `.button {`) @@ -748,7 +748,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.evaluate(() => document.querySelector('button').click()) expect(await session.hasRedbox(true)).toBe(true) @@ -794,7 +794,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.evaluate(() => document.querySelector('button').click()) expect(await session.hasRedbox(true)).toBe(true) @@ -840,7 +840,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.evaluate(() => document.querySelector('button').click()) expect(await session.hasRedbox(true)).toBe(true) @@ -886,7 +886,7 @@ for (const variant of ['default', 'turbo']) { ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.evaluate(() => document.querySelector('button').click()) expect(await session.hasRedbox(true)).toBe(true) diff --git a/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts b/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts index 738e7a5f79b92..486fd88498525 100644 --- a/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts @@ -83,7 +83,7 @@ describe.skip('ReactRefreshLogBox', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -107,7 +107,7 @@ describe.skip('ReactRefreshLogBox', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -131,7 +131,7 @@ describe.skip('ReactRefreshLogBox', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', @@ -155,7 +155,7 @@ describe.skip('ReactRefreshLogBox', () => { } ` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', diff --git a/test/development/acceptance/ReactRefreshModule.test.ts b/test/development/acceptance/ReactRefreshModule.test.ts index 6dae853adc77c..58321126cb6ff 100644 --- a/test/development/acceptance/ReactRefreshModule.test.ts +++ b/test/development/acceptance/ReactRefreshModule.test.ts @@ -15,7 +15,7 @@ describe('ReactRefreshModule', () => { it('should allow any variable names', async () => { const { session, cleanup } = await sandbox(next, new Map([])) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) const variables = [ '_a', @@ -33,7 +33,7 @@ describe('ReactRefreshModule', () => { return null }` ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect(next.cliOutput).not.toContain( `'${variable}' has already been declared` ) diff --git a/test/development/acceptance/ReactRefreshRegression.test.ts b/test/development/acceptance/ReactRefreshRegression.test.ts index 912f9238c0342..35def651e0b61 100644 --- a/test/development/acceptance/ReactRefreshRegression.test.ts +++ b/test/development/acceptance/ReactRefreshRegression.test.ts @@ -2,6 +2,7 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' +import { check } from 'next-test-utils' describe('ReactRefreshRegression', () => { let next: NextInstance @@ -76,7 +77,7 @@ describe('ReactRefreshRegression', () => { ) // Verify no hydration mismatch: - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) await cleanup() }) @@ -231,9 +232,11 @@ describe('ReactRefreshRegression', () => { ` ) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('0') + await check( + () => session.evaluate(() => document.querySelector('p').textContent), + '0' + ) + await session.evaluate(() => document.querySelector('button').click()) expect( await session.evaluate(() => document.querySelector('p').textContent) @@ -319,7 +322,7 @@ describe('ReactRefreshRegression', () => { let didNotReload = await session.patch('pages/index.mdx', `Hello Foo!`) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate( () => document.querySelector('#__next').textContent @@ -328,7 +331,7 @@ describe('ReactRefreshRegression', () => { didNotReload = await session.patch('pages/index.mdx', `Hello Bar!`) expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate( () => document.querySelector('#__next').textContent From 6d09f0b80c6ce3e956733492a9c56bf1159f513b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 14:15:53 -0800 Subject: [PATCH 28/56] Add experimental.allowedRevalidateHeaderKeys config (#44985) --- packages/next/src/server/api-utils/node.ts | 18 ++++++++---------- packages/next/src/server/config-schema.ts | 3 +++ packages/next/src/server/config-shared.ts | 1 + packages/next/src/server/next-server.ts | 5 ++++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/next/src/server/api-utils/node.ts b/packages/next/src/server/api-utils/node.ts index 3dd2b99367252..55dd33bc308e3 100644 --- a/packages/next/src/server/api-utils/node.ts +++ b/packages/next/src/server/api-utils/node.ts @@ -181,6 +181,7 @@ export async function parseBody( type ApiContext = __ApiPreviewProps & { trustHostHeader?: boolean + allowedRevalidateHeaderKeys?: string[] revalidate?: (_req: IncomingMessage, _res: ServerResponse) => Promise } @@ -353,15 +354,6 @@ function setPreviewData( return res } -const filteredRevalidateHeaders = new Set([ - 'content-type', - 'content-encoding', - 'accept', - 'accept-language', - 'accept-encoding', - 'connection', -]) - async function revalidate( urlPath: string, opts: { @@ -383,9 +375,15 @@ async function revalidate( } : {}), } + const allowedRevalidateHeaderKeys = [ + ...(context.allowedRevalidateHeaderKeys || []), + ...(context.trustHostHeader + ? ['cookie', 'x-vercel-protection-bypass'] + : []), + ] for (const key of Object.keys(req.headers)) { - if (!filteredRevalidateHeaders.has(key) && !key.startsWith('x-vercel')) { + if (allowedRevalidateHeaderKeys.includes(key)) { revalidateHeaders[key] = req.headers[key] as string } } diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 304abb361839b..0daf8649bd72d 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -229,6 +229,9 @@ const configSchema = { adjustFontFallbacksWithSizeAdjust: { type: 'boolean', }, + allowedRevalidateHeaderKeys: { + type: 'array', + }, amp: { additionalProperties: false, properties: { diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 5471fc29a49c4..4801d0933efbd 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -79,6 +79,7 @@ export interface NextJsWebpackConfig { } export interface ExperimentalConfig { + allowedRevalidateHeaderKeys?: string[] fetchCache?: boolean optimisticClientCache?: boolean middlewarePrefetch?: 'strict' | 'flexible' diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 98ea6ffc67c7d..df71efb5662d7 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -810,7 +810,10 @@ export default class NextNodeServer extends BaseServer { new NodeNextResponse(newRes) ), // internal config so is not typed - trustHostHeader: (this.nextConfig.experimental as any).trustHostHeader, + trustHostHeader: (this.nextConfig.experimental as Record) + .trustHostHeader, + allowedRevalidateHeaderKeys: + this.nextConfig.experimental.allowedRevalidateHeaderKeys, }, this.minimalMode, this.renderOpts.dev, From 8f0acdf781dc69575c236fd6e785ae3e78c85808 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 16:00:22 -0800 Subject: [PATCH 29/56] Gracefully handle telemetry not being available (#44986) --- .../build/webpack/plugins/middleware-plugin.ts | 6 +++--- .../src/telemetry/events/swc-load-failure.ts | 2 +- packages/next/src/trace/report/to-telemetry.ts | 3 ++- packages/next/src/trace/shared.ts | 9 ++++++++- test/e2e/yarn-pnp/test/utils.ts | 17 +++++++++++++---- test/e2e/yarn-pnp/test/with-eslint.test.ts | 2 +- test/e2e/yarn-pnp/test/with-mdx.test.ts | 2 +- test/e2e/yarn-pnp/test/with-next-sass.test.ts | 4 +++- 8 files changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/next/src/build/webpack/plugins/middleware-plugin.ts b/packages/next/src/build/webpack/plugins/middleware-plugin.ts index faaa98e38cee3..0b957799c0069 100644 --- a/packages/next/src/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/src/build/webpack/plugins/middleware-plugin.ts @@ -607,7 +607,7 @@ function getExtractMetadata(params: { return async () => { metadataByEntry.clear() const resolver = compilation.resolverFactory.get('normal') - const telemetry: Telemetry = traceGlobals.get('telemetry') + const telemetry: Telemetry | undefined = traceGlobals.get('telemetry') for (const [entryName, entry] of compilation.entries) { if (entry.options.runtime !== EDGE_RUNTIME_WEBPACK) { @@ -683,7 +683,7 @@ function getExtractMetadata(params: { } if (edgeFunctionConfig?.config?.unstable_allowDynamicGlobs) { - telemetry.record({ + telemetry?.record({ eventName: 'NEXT_EDGE_ALLOW_DYNAMIC_USED', payload: { ...edgeFunctionConfig, @@ -773,7 +773,7 @@ function getExtractMetadata(params: { } } - telemetry.record({ + telemetry?.record({ eventName: EVENT_BUILD_FEATURE_USAGE, payload: { featureName: 'vercelImageGeneration', diff --git a/packages/next/src/telemetry/events/swc-load-failure.ts b/packages/next/src/telemetry/events/swc-load-failure.ts index 6414043b1ae4e..b6e617a58b246 100644 --- a/packages/next/src/telemetry/events/swc-load-failure.ts +++ b/packages/next/src/telemetry/events/swc-load-failure.ts @@ -20,7 +20,7 @@ export type EventSwcLoadFailure = { export async function eventSwcLoadFailure( event?: EventSwcLoadFailure['payload'] ): Promise { - const telemetry: Telemetry = traceGlobals.get('telemetry') + const telemetry: Telemetry | undefined = traceGlobals.get('telemetry') // can't continue if telemetry isn't set if (!telemetry) return diff --git a/packages/next/src/trace/report/to-telemetry.ts b/packages/next/src/trace/report/to-telemetry.ts index a308fc758badd..5eeb0d6e7b3a5 100644 --- a/packages/next/src/trace/report/to-telemetry.ts +++ b/packages/next/src/trace/report/to-telemetry.ts @@ -1,3 +1,4 @@ +import { Telemetry } from '../../telemetry/storage' import { traceGlobals } from '../shared' const TRACE_EVENT_ACCESSLIST = new Map( @@ -11,7 +12,7 @@ const reportToTelemetry = (spanName: string, duration: number) => { if (!eventName) { return } - const telemetry = traceGlobals.get('telemetry') + const telemetry: Telemetry | undefined = traceGlobals.get('telemetry') if (!telemetry) { return } diff --git a/packages/next/src/trace/shared.ts b/packages/next/src/trace/shared.ts index 1c04cc8ce442e..e4bbcbbe5c2ec 100644 --- a/packages/next/src/trace/shared.ts +++ b/packages/next/src/trace/shared.ts @@ -1,6 +1,13 @@ export type SpanId = number -export const traceGlobals: Map = new Map() +let _traceGlobals: Map = (global as any)._traceGlobals + +if (!_traceGlobals) { + _traceGlobals = new Map() +} +;(global as any)._traceGlobals = _traceGlobals + +export const traceGlobals: Map = _traceGlobals export const setGlobal = (key: any, val: any) => { traceGlobals.set(key, val) } diff --git a/test/e2e/yarn-pnp/test/utils.ts b/test/e2e/yarn-pnp/test/utils.ts index 022c72d2eeb06..94281898e2786 100644 --- a/test/e2e/yarn-pnp/test/utils.ts +++ b/test/e2e/yarn-pnp/test/utils.ts @@ -6,7 +6,11 @@ import { NextInstance } from 'test/lib/next-modes/base' jest.setTimeout(2 * 60 * 1000) -export function runTests(example = '') { +export function runTests( + example = '', + testPath = '/', + expectedContent = ['index page'] +) { const versionParts = process.versions.node.split('.').map((i) => Number(i)) if ((global as any).isNextDeploy) { @@ -42,7 +46,7 @@ export function runTests(example = '') { prev.push(`${cur}@${dependencies[cur]}`) return prev }, [] as string[]) - return `yarn set version 4.0.0-rc.13 && yarn config set enableGlobalCache true && yarn config set compressionLevel 0 && yarn add ${pkgs.join( + return `yarn set version berry && yarn config set enableGlobalCache true && yarn config set compressionLevel 0 && yarn add ${pkgs.join( ' ' )}` }, @@ -55,9 +59,14 @@ export function runTests(example = '') { afterAll(() => next?.destroy()) it(`should compile and serve the index page correctly ${example}`, async () => { - const res = await fetchViaHTTP(next.url, '/') + const res = await fetchViaHTTP(next.url, testPath) expect(res.status).toBe(200) - expect(await res.text()).toContain(' {}) diff --git a/test/e2e/yarn-pnp/test/with-eslint.test.ts b/test/e2e/yarn-pnp/test/with-eslint.test.ts index 271b4c7c97e36..b7362fe8aa4f4 100644 --- a/test/e2e/yarn-pnp/test/with-eslint.test.ts +++ b/test/e2e/yarn-pnp/test/with-eslint.test.ts @@ -1,5 +1,5 @@ import { runTests } from './utils' describe('yarn PnP', () => { - runTests('with-eslint') + runTests('with-eslint', '/', [' { - runTests('with-mdx') + runTests('with-mdx', '/', ['Look, a button', 'Hello']) }) diff --git a/test/e2e/yarn-pnp/test/with-next-sass.test.ts b/test/e2e/yarn-pnp/test/with-next-sass.test.ts index d1e5319da1579..e3d95892c46e0 100644 --- a/test/e2e/yarn-pnp/test/with-next-sass.test.ts +++ b/test/e2e/yarn-pnp/test/with-next-sass.test.ts @@ -1,5 +1,7 @@ import { runTests } from './utils' describe('yarn PnP', () => { - runTests('with-next-sass') + runTests('with-next-sass', '/', [ + 'Hello World, I am being styled using SCSS Modules', + ]) }) From c72ebf7722fd40f8342b8b9f94f4789c98245df8 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 16:09:26 -0800 Subject: [PATCH 30/56] v13.1.3-canary.4 --- 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 2c75ad2987d61..ad33b01cadd82 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.1.3-canary.3" + "version": "13.1.3-canary.4" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index b98406f64a1ed..19a863a58b6db 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.3-canary.3", + "version": "13.1.3-canary.4", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index cb5a6c65cebcb..c55e73664c281 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.3-canary.3", + "version": "13.1.3-canary.4", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.1.3-canary.3", + "@next/eslint-plugin-next": "13.1.3-canary.4", "@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 9837f5cd932db..2b2804d426bdb 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.3-canary.3", + "version": "13.1.3-canary.4", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 5af4185007e97..1acbb788fba7b 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.1.3-canary.3", + "version": "13.1.3-canary.4", "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 b62532e66f23a..041f81aff4de3 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.3-canary.3", + "version": "13.1.3-canary.4", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index d58207f3f0341..2cbc8ced16d03 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.1.3-canary.3", + "version": "13.1.3-canary.4", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index a7d976b9366e6..b09c8a8187d7a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.1.3-canary.3", + "version": "13.1.3-canary.4", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 5e341d2a257e2..f3614ee27caef 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.1.3-canary.3", + "version": "13.1.3-canary.4", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 00caadd05868b..f5fb8a627884c 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.3-canary.3", + "version": "13.1.3-canary.4", "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 262a10189418a..d1094dff0dc07 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.3-canary.3", + "version": "13.1.3-canary.4", "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 a4602ec0fb215..bb14be3821fb6 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.3-canary.3", + "version": "13.1.3-canary.4", "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 9f2e402ab8dfd..dd1668610a54d 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.1.3-canary.3", + "version": "13.1.3-canary.4", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin,rustls-tls --js false native", diff --git a/packages/next/package.json b/packages/next/package.json index fb1c85bbc6c72..bc8b2afd43c60 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.1.3-canary.3", + "version": "13.1.3-canary.4", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -76,7 +76,7 @@ ] }, "dependencies": { - "@next/env": "13.1.3-canary.3", + "@next/env": "13.1.3-canary.4", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -126,11 +126,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.13.3", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.1.3-canary.3", - "@next/polyfill-nomodule": "13.1.3-canary.3", - "@next/react-dev-overlay": "13.1.3-canary.3", - "@next/react-refresh-utils": "13.1.3-canary.3", - "@next/swc": "13.1.3-canary.3", + "@next/polyfill-module": "13.1.3-canary.4", + "@next/polyfill-nomodule": "13.1.3-canary.4", + "@next/react-dev-overlay": "13.1.3-canary.4", + "@next/react-refresh-utils": "13.1.3-canary.4", + "@next/swc": "13.1.3-canary.4", "@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 5a006d389f90a..5cb91f8a15cba 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.3-canary.3", + "version": "13.1.3-canary.4", "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 3d29199d3b7b6..aa086ab890f79 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.3-canary.3", + "version": "13.1.3-canary.4", "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 f582d2d9a93fb..dc05cf40f0586 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -440,7 +440,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.3-canary.3 + '@next/eslint-plugin-next': 13.1.3-canary.4 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -512,12 +512,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.3-canary.3 - '@next/polyfill-module': 13.1.3-canary.3 - '@next/polyfill-nomodule': 13.1.3-canary.3 - '@next/react-dev-overlay': 13.1.3-canary.3 - '@next/react-refresh-utils': 13.1.3-canary.3 - '@next/swc': 13.1.3-canary.3 + '@next/env': 13.1.3-canary.4 + '@next/polyfill-module': 13.1.3-canary.4 + '@next/polyfill-nomodule': 13.1.3-canary.4 + '@next/react-dev-overlay': 13.1.3-canary.4 + '@next/react-refresh-utils': 13.1.3-canary.4 + '@next/swc': 13.1.3-canary.4 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 From a54e43a02b56d2ae557343d66fe88c7d477a85bf Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 17 Jan 2023 16:56:04 -0800 Subject: [PATCH 31/56] Optimize to skip upload for non-publish swc builds (#44987) --- .github/workflows/build_test_deploy.yml | 5 +++++ scripts/run-for-change.js | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 4c6eca1ae7820..a803587b12246 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -827,6 +827,8 @@ jobs: needs: [publishRelease, build, build-native-test] env: NEXT_TELEMETRY_DISABLED: 1 + VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }} + VERCEL_TEST_TEAM: vtest314-next-e2e-tests steps: - uses: actions/cache@v3 id: restore-build @@ -1247,6 +1249,7 @@ jobs: shell: bash - name: Upload artifact + if: ${{ needs.build.outputs.isRelease == 'true' }} uses: actions/upload-artifact@v3 with: name: next-swc-binaries @@ -1337,6 +1340,7 @@ jobs: run: pnpm turbo run --force cache-build-native -- --platform --release --target x86_64-unknown-freebsd - name: Upload artifact + if: ${{ needs.build.outputs.isRelease == 'true' }} uses: actions/upload-artifact@v3 with: name: next-swc-binaries @@ -1386,6 +1390,7 @@ jobs: run: '[[ -d "packages/next-swc/crates/wasm/pkg" ]] && mv packages/next-swc/crates/wasm/pkg packages/next-swc/crates/wasm/pkg-${{ matrix.target }} || ls packages/next-swc/crates/wasm' - name: Upload artifact + if: ${{ needs.build.outputs.isRelease == 'true' }} uses: actions/upload-artifact@v3 with: name: wasm-binaries diff --git a/scripts/run-for-change.js b/scripts/run-for-change.js index c16e05452fcf6..b21a034ba608e 100644 --- a/scripts/run-for-change.js +++ b/scripts/run-for-change.js @@ -17,6 +17,22 @@ const CHANGE_ITEM_GROUPS = { '.github/ISSUE_TEMPLATE', '.github/labeler.json', '.github/pull_request_template.md', + 'packages/next-plugin-storybook/readme.md', + 'packages/next/license.md', + 'packages/next/README.md', + 'packages/eslint-plugin-next/README.md', + 'packages/next-codemod/license.md', + 'packages/next-codemod/README.md', + 'packages/next-swc/crates/wasm/README.md', + 'packages/next-swc/README.md', + 'packages/next-bundle-analyzer/readme.md', + 'packages/next-mdx/license.md', + 'packages/next-mdx/readme.md', + 'packages/react-dev-overlay/README.md', + 'packages/react-refresh-utils/README.md', + 'packages/create-next-app/README.md', + 'packages/font/README.md', + 'packages/next-env/README.md', ], cna: ['packages/create-next-app'], 'next-swc': ['packages/next-swc', 'scripts/normalize-version-bump.js'], From cefb30b32bc05fcb46058a6e8b1ff545fb9ddf1d Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 18 Jan 2023 12:21:10 +0100 Subject: [PATCH 32/56] Update codesandbox link in issue template (#44995) We want reproductions to use the new and better code sandbox. --- .github/ISSUE_TEMPLATE/1.bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index 6b4863b3ce125..40de55adfb8a9 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -49,7 +49,7 @@ body: - type: input attributes: label: Link to the code that reproduces this issue - description: A link to a GitHub repository, a [StackBlitz](https://stackblitz.com/fork/github/vercel/next.js/tree/canary/examples/reproduction-template), or a [CodeSandbox](https://codesandbox.io/s/github/vercel/next.js/tree/canary/examples/reproduction-template) minimal reproduction. Minimal reproductions should be created from our [bug report template with `npx create-next-app -e reproduction-template`](https://github.com/vercel/next.js/tree/canary/examples/reproduction-template) and should include only changes that contribute to the issue. + description: A link to a GitHub repository, a [StackBlitz](https://stackblitz.com/fork/github/vercel/next.js/tree/canary/examples/reproduction-template), or a [CodeSandbox](https://codesandbox.io/p/sandbox/github/vercel/next.js/tree/canary/examples/reproduction-template) minimal reproduction. Minimal reproductions should be created from our [bug report template with `npx create-next-app -e reproduction-template`](https://github.com/vercel/next.js/tree/canary/examples/reproduction-template) and should include only changes that contribute to the issue. validations: required: true - type: textarea From 6b7c69fa4d773d76730272158e2fe7fed12e03aa Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 10:24:35 -0800 Subject: [PATCH 33/56] Add proper error when app path switches static to dynamic (#44989) --- errors/app-static-to-dynamic-error.md | 18 ++++ errors/manifest.json | 4 + packages/next/src/server/base-server.ts | 6 ++ .../e2e/app-dir/app-static/app-static.test.ts | 85 +++++++++++++++++-- .../[id]/page.js | 17 ++++ .../app/static-to-dynamic-error/[id]/page.js | 15 ++++ 6 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 errors/app-static-to-dynamic-error.md create mode 100644 test/e2e/app-dir/app-static/app/static-to-dynamic-error-forced/[id]/page.js create mode 100644 test/e2e/app-dir/app-static/app/static-to-dynamic-error/[id]/page.js diff --git a/errors/app-static-to-dynamic-error.md b/errors/app-static-to-dynamic-error.md new file mode 100644 index 0000000000000..aba7d34f698bd --- /dev/null +++ b/errors/app-static-to-dynamic-error.md @@ -0,0 +1,18 @@ +# `app/` Static to Dynamic Error + +#### Why This Error Occurred + +Inside of one of your `app/` pages, the page was initially generated statically at build time and then during runtime either a fallback path or path being revalidated attempted to leverage dynamic server values e.g. `cookies()` or `headers()`. + +This is a hard error by default as a path generated statically can switch between types during runtime currently. + +#### Possible Ways to Fix It + +Prevent usage of these dynamic server values conditionally which can cause the static/dynamic mode of the page to differ between build time and runtime. + +Leverage `export const dynamic = 'force-static'` to ensure the page is handled statically regardless of the usage of dynamic server values. Alternatively if you prefer your page to allows be dynamic you can set `export const dynamic = 'force-dynamic'` and it won't attempt to have the page be statically generated. + +### Useful Links + +- [static/dynamic rendering](https://beta.nextjs.org/docs/rendering/static-and-dynamic-rendering) +- [dynamic server value methods](https://beta.nextjs.org/docs/data-fetching/fetching#server-component-functions) diff --git a/errors/manifest.json b/errors/manifest.json index 89178359a20f5..07d1d8191928c 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -785,6 +785,10 @@ { "title": "class-component-in-server-component", "path": "/errors/class-component-in-server-component.md" + }, + { + "title": "app-static-to-dynamic-error", + "path": "/errors/app-static-to-dynamic-error.md" } ] } diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index c31269c652af6..79710cbaecce5 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -1373,6 +1373,12 @@ export default abstract class Server { isNotFound = (renderOpts as any).isNotFound isRedirect = (renderOpts as any).isRedirect + if (isAppPath && isSSG && isrRevalidate === 0) { + throw new Error( + `Page changed from static to dynamic at runtime ${urlPathname}, see more here https://nextjs.org/docs/messages/app-static-to-dynamic-error` + ) + } + let value: ResponseCacheValue | null if (isNotFound) { value = null diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index 9a059306befb5..bb1721da11170 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -74,6 +74,12 @@ createNextDescribe( 'ssr-auto/cache-no-store/page.js', 'ssr-auto/fetch-revalidate-zero/page.js', 'ssr-forced/page.js', + 'static-to-dynamic-error-forced/[id].html', + 'static-to-dynamic-error-forced/[id].rsc', + 'static-to-dynamic-error-forced/[id]/page.js', + 'static-to-dynamic-error/[id].html', + 'static-to-dynamic-error/[id].rsc', + 'static-to-dynamic-error/[id]/page.js', 'variable-revalidate/no-store/page.js', 'variable-revalidate/revalidate-3.html', 'variable-revalidate/revalidate-3.rsc', @@ -215,26 +221,93 @@ createNextDescribe( }, '/hooks/use-pathname/[slug]': { dataRoute: '/hooks/use-pathname/[slug].rsc', - dataRouteRegex: '^\\/hooks\\/use\\-pathname\\/([^\\/]+?)\\.rsc$', + dataRouteRegex: normalizeRegEx( + '^\\/hooks\\/use\\-pathname\\/([^\\/]+?)\\.rsc$' + ), fallback: null, - routeRegex: '^\\/hooks\\/use\\-pathname\\/([^\\/]+?)(?:\\/)?$', + routeRegex: normalizeRegEx( + '^\\/hooks\\/use\\-pathname\\/([^\\/]+?)(?:\\/)?$' + ), }, '/force-static/[slug]': { dataRoute: '/force-static/[slug].rsc', - dataRouteRegex: '^\\/force\\-static\\/([^\\/]+?)\\.rsc$', + dataRouteRegex: normalizeRegEx( + '^\\/force\\-static\\/([^\\/]+?)\\.rsc$' + ), fallback: null, - routeRegex: '^\\/force\\-static\\/([^\\/]+?)(?:\\/)?$', + routeRegex: normalizeRegEx( + '^\\/force\\-static\\/([^\\/]+?)(?:\\/)?$' + ), }, '/ssg-preview/[[...route]]': { dataRoute: '/ssg-preview/[[...route]].rsc', - dataRouteRegex: '^\\/ssg\\-preview(?:\\/(.+?))?\\.rsc$', + dataRouteRegex: normalizeRegEx( + '^\\/ssg\\-preview(?:\\/(.+?))?\\.rsc$' + ), fallback: null, - routeRegex: '^\\/ssg\\-preview(?:\\/(.+?))?(?:\\/)?$', + routeRegex: normalizeRegEx( + '^\\/ssg\\-preview(?:\\/(.+?))?(?:\\/)?$' + ), + }, + '/static-to-dynamic-error-forced/[id]': { + dataRoute: '/static-to-dynamic-error-forced/[id].rsc', + dataRouteRegex: normalizeRegEx( + '^\\/static\\-to\\-dynamic\\-error\\-forced\\/([^\\/]+?)\\.rsc$' + ), + fallback: null, + routeRegex: normalizeRegEx( + '^\\/static\\-to\\-dynamic\\-error\\-forced\\/([^\\/]+?)(?:\\/)?$' + ), + }, + '/static-to-dynamic-error/[id]': { + dataRoute: '/static-to-dynamic-error/[id].rsc', + dataRouteRegex: normalizeRegEx( + '^\\/static\\-to\\-dynamic\\-error\\/([^\\/]+?)\\.rsc$' + ), + fallback: null, + routeRegex: normalizeRegEx( + '^\\/static\\-to\\-dynamic\\-error\\/([^\\/]+?)(?:\\/)?$' + ), }, }) }) } + if (!isDev) { + it('should properly error when static page switches to dynamic at runtime', async () => { + const res = await next.fetch( + '/static-to-dynamic-error/static-bailout-1' + ) + + expect(res.status).toBe(500) + + if (isNextStart) { + await check( + () => next.cliOutput, + /Page changed from static to dynamic at runtime \/static-to-dynamic-error\/static-bailout-1/ + ) + } + }) + + it('should not error with dynamic server usage with force-static', async () => { + const res = await next.fetch( + '/static-to-dynamic-error-forced/static-bailout-1' + ) + const outputIndex = next.cliOutput.length + const html = await res.text() + + expect(res.status).toBe(200) + expect(html).toContain('/static-to-dynamic-error-forced') + expect(html).toMatch(/id:.*?static-bailout-1/) + + if (isNextStart) { + expect(next.cliOutput.substring(outputIndex)).not.toMatch( + /Page changed from static to dynamic at runtime \/static-to-dynamic-error\/static-bailout-1/ + ) + } + }) + } + it('Should not throw Dynamic Server Usage error when using generateStaticParams with previewData', async () => { const browserOnIndexPage = await next.browser('/ssg-preview') diff --git a/test/e2e/app-dir/app-static/app/static-to-dynamic-error-forced/[id]/page.js b/test/e2e/app-dir/app-static/app/static-to-dynamic-error-forced/[id]/page.js new file mode 100644 index 0000000000000..5f6b80e43da43 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/static-to-dynamic-error-forced/[id]/page.js @@ -0,0 +1,17 @@ +import { cookies } from 'next/headers' + +export const dynamic = 'force-static' + +export default function Page({ params }) { + if (params.id.includes('static-bailout')) { + console.log('calling cookies', cookies()) + } + + return ( + <> +

/static-to-dynamic-error-forced

+

id: {params.id}

+

{Date.now()}

+ + ) +} diff --git a/test/e2e/app-dir/app-static/app/static-to-dynamic-error/[id]/page.js b/test/e2e/app-dir/app-static/app/static-to-dynamic-error/[id]/page.js new file mode 100644 index 0000000000000..076ac3c9ad584 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/static-to-dynamic-error/[id]/page.js @@ -0,0 +1,15 @@ +import { cookies } from 'next/headers' + +export default function Page({ params }) { + if (params.id.includes('static-bailout')) { + console.log('calling cookies', cookies()) + } + + return ( + <> +

/static-to-dynamic-error

+

id: {params.id}

+

{Date.now()}

+ + ) +} From 04f26460da908e31afc0a445bce927709259fba5 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 10:48:12 -0800 Subject: [PATCH 34/56] Update test timings token env (#45014) --- .github/workflows/build_test_deploy.yml | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index a803587b12246..aee48e71461b1 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -83,7 +83,7 @@ jobs: - run: pnpm install - - run: TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} node run-tests.js --timings --write-timings -g 1/1 + - run: TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} node run-tests.js --timings --write-timings -g 1/1 if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }} - run: pnpm run build @@ -258,7 +258,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -284,7 +284,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js --type development --timings -g ${{ matrix.group }}/4 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type development --timings -g ${{ matrix.group }}/4 >> /proc/1/fd/1" name: Run test/development if: ${{needs.build.outputs.docsChange == 'nope'}} # env: @@ -318,7 +318,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -344,7 +344,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" name: Run test/e2e (dev) if: ${{needs.build.outputs.docsChange == 'nope'}} # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testDevE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} @@ -377,7 +377,7 @@ jobs: timeout-minutes: 15 env: NEXT_TELEMETRY_DISABLED: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -403,7 +403,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js --type production --timings -g ${{ matrix.group }}/3 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type production --timings -g ${{ matrix.group }}/3 >> /proc/1/fd/1" name: Run test/production if: ${{needs.build.outputs.docsChange == 'nope'}} # env: @@ -427,7 +427,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -453,7 +453,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 NEXT_TEST_MODE=start TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange == 'nope'}} # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testProdE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} @@ -476,7 +476,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} steps: - run: echo "${{needs.build.outputs.docsChange}}" @@ -521,7 +521,7 @@ jobs: timeout-minutes: 35 env: NEXT_TELEMETRY_DISABLED: 1 - TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} strategy: fail-fast: false matrix: @@ -573,7 +573,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/25 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/25 >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} # env: # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testIntegration / Group ${{ matrix.group }} @@ -675,7 +675,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && BROWSERNAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && BROWSERNAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} testSafari: @@ -774,7 +774,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v18 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && BROWSER_NAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v18 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && BROWSER_NAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} publishRelease: @@ -845,7 +845,7 @@ jobs: - run: RESET_VC_PROJECT=true node scripts/reset-vercel-project.mjs name: Reset test project - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && VERCEL_TEST_TOKEN=${{ secrets.VERCEL_TEST_TOKEN }} VERCEL_TEST_TEAM=vtest314-next-e2e-tests NEXT_TEST_JOB=1 NEXT_TEST_MODE=deploy TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN || secrets.GITHUB_TOKEN }} xvfb-run node run-tests.js --type e2e >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && VERCEL_TEST_TOKEN=${{ secrets.VERCEL_TEST_TOKEN }} VERCEL_TEST_TEAM=vtest314-next-e2e-tests NEXT_TEST_JOB=1 NEXT_TEST_MODE=deploy TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e >> /proc/1/fd/1" name: Run test/e2e (deploy) - name: Upload test trace From ad4820243246306a1a2ab9344557b13f43f4f4cd Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 11:24:48 -0800 Subject: [PATCH 35/56] Ensure force-dynamic is honored during build (#45015) --- packages/next/src/build/utils.ts | 6 +++++- test/e2e/app-dir/app-static/app-static.test.ts | 1 + .../app-static/app/force-dynamic-no-prerender/[id]/page.js | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 test/e2e/app-dir/app-static/app/force-dynamic-no-prerender/[id]/page.js diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index e13a8ff1c0133..481a2e1c0a043 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -1048,7 +1048,7 @@ export async function buildStaticPaths({ export type AppConfig = { revalidate?: number | false dynamicParams?: true | false - dynamic?: 'auto' | 'error' | 'force-static' + dynamic?: 'auto' | 'error' | 'force-static' | 'force-dynamic' fetchCache?: 'force-cache' | 'only-cache' preferredRegion?: string } @@ -1354,6 +1354,10 @@ export async function isPageStatic({ {} ) + if (appConfig.dynamic === 'force-dynamic') { + appConfig.revalidate = 0 + } + if (isDynamicRoute(page)) { ;({ paths: prerenderRoutes, diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index bb1721da11170..b30c1da967c14 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -46,6 +46,7 @@ createNextDescribe( 'dynamic-no-gen-params/[slug].html', 'dynamic-no-gen-params/[slug].rsc', 'dynamic-no-gen-params/[slug]/page.js', + 'force-dynamic-no-prerender/[id]/page.js', 'force-static/[slug]/page.js', 'force-static/first.html', 'force-static/first.rsc', diff --git a/test/e2e/app-dir/app-static/app/force-dynamic-no-prerender/[id]/page.js b/test/e2e/app-dir/app-static/app/force-dynamic-no-prerender/[id]/page.js new file mode 100644 index 0000000000000..e42e7e48da2c7 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/force-dynamic-no-prerender/[id]/page.js @@ -0,0 +1,5 @@ +export const dynamic = 'force-dynamic' + +export default function Page({ params }) { + throw new Error('this should not attempt prerendering with force-dynamic') +} From d7307cffb753e7151a3ec02d27a6d3e100a5cad4 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 18 Jan 2023 20:35:28 +0100 Subject: [PATCH 36/56] Fix turbo usage in tests (#44715) --- .../actions/next-stats-action/package.json | 1 + .../next-stats-action/src/constants.js | 4 +- .../actions/next-stats-action/src/index.js | 6 +- .../src/prepare/repo-setup.js | 135 +++--------------- .gitignore | 3 + package.json | 2 + packages/create-next-app/package.json | 3 +- packages/eslint-config-next/package.json | 3 + packages/eslint-plugin-next/package.json | 3 +- packages/font/package.json | 3 +- packages/next-bundle-analyzer/package.json | 3 + packages/next-codemod/package.json | 3 +- packages/next-env/package.json | 3 +- packages/next-mdx/package.json | 3 + packages/next-plugin-storybook/package.json | 3 + packages/next-polyfill-module/package.json | 3 +- packages/next-polyfill-nomodule/package.json | 3 +- packages/next-swc/package.json | 3 +- packages/next/package.json | 3 +- packages/react-dev-overlay/package.json | 3 +- packages/react-refresh-utils/package.json | 3 +- pnpm-lock.yaml | 114 +++++++++++++-- scripts/test-pack-package.mts | 105 ++++++++++++++ scripts/trace-next-server.js | 2 +- test/lib/create-next-install.js | 127 +++------------- test/lib/next-modes/base.ts | 4 - turbo.json | 12 +- 27 files changed, 301 insertions(+), 259 deletions(-) create mode 100755 scripts/test-pack-package.mts diff --git a/.github/actions/next-stats-action/package.json b/.github/actions/next-stats-action/package.json index 4d2e88dae3e3e..c4f4c4c1d1023 100644 --- a/.github/actions/next-stats-action/package.json +++ b/.github/actions/next-stats-action/package.json @@ -3,6 +3,7 @@ "main": "src/index.js", "dependencies": { "async-sema": "^3.1.0", + "execa": "2.0.3", "fs-extra": "^8.1.0", "get-port": "^5.0.0", "glob": "^7.1.4", diff --git a/.github/actions/next-stats-action/src/constants.js b/.github/actions/next-stats-action/src/constants.js index 625624a18986d..2bac325eeea77 100644 --- a/.github/actions/next-stats-action/src/constants.js +++ b/.github/actions/next-stats-action/src/constants.js @@ -5,8 +5,8 @@ const benchTitle = 'Page Load Tests' const workDir = path.join(os.tmpdir(), 'next-stats') const mainRepoName = 'main-repo' const diffRepoName = 'diff-repo' -const mainRepoDir = path.join(workDir, mainRepoName) -const diffRepoDir = path.join(workDir, diffRepoName) +const mainRepoDir = path.join(os.tmpdir(), mainRepoName) +const diffRepoDir = path.join(os.tmpdir(), diffRepoName) const statsAppDir = path.join(workDir, 'stats-app') const diffingDir = path.join(workDir, 'diff') const yarnEnvValues = { diff --git a/.github/actions/next-stats-action/src/index.js b/.github/actions/next-stats-action/src/index.js index 8e2e9f4861087..23ea3882c5a46 100644 --- a/.github/actions/next-stats-action/src/index.js +++ b/.github/actions/next-stats-action/src/index.js @@ -78,9 +78,7 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { if (actionInfo.isRelease) { logger('Release detected, resetting mainRepo to last stable tag') const lastStableTag = await getLastStable(mainRepoDir, actionInfo.prRef) - mainNextSwcVersion = { - '@next/swc-linux-x64-gnu': lastStableTag, - } + mainNextSwcVersion = lastStableTag if (!lastStableTag) throw new Error('failed to get last stable tag') console.log('using latestStable', lastStableTag) await checkoutRef(lastStableTag, mainRepoDir) @@ -140,7 +138,7 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { const isMainRepo = dir === mainRepoDir const pkgPaths = await linkPackages({ repoDir: dir, - nextSwcPkg: isMainRepo ? mainNextSwcVersion : undefined, + nextSwcVersion: isMainRepo ? mainNextSwcVersion : undefined, }) if (isMainRepo) mainRepoPkgPaths = pkgPaths diff --git a/.github/actions/next-stats-action/src/prepare/repo-setup.js b/.github/actions/next-stats-action/src/prepare/repo-setup.js index f490a48ba3daa..15f1abf814725 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -4,11 +4,7 @@ const exec = require('../util/exec') const { remove } = require('fs-extra') const logger = require('../util/logger') const semver = require('semver') - -const mockTrace = () => ({ - traceAsyncFn: (fn) => fn(mockTrace()), - traceChild: () => mockTrace(), -}) +const execa = require('execa') module.exports = (actionInfo) => { return { @@ -58,117 +54,28 @@ module.exports = (actionInfo) => { } } }, - async linkPackages({ repoDir = '', nextSwcPkg, parentSpan }) { - const rootSpan = parentSpan - ? parentSpan.traceChild('linkPackages') - : mockTrace() - - return await rootSpan.traceAsyncFn(async () => { - const pkgPaths = new Map() - const pkgDatas = new Map() - let pkgs - - try { - pkgs = await fs.readdir(path.join(repoDir, 'packages')) - } catch (err) { - if (err.code === 'ENOENT') { - require('console').log('no packages to link') - return pkgPaths - } - throw err - } - - await rootSpan - .traceChild('prepare packages for packing') - .traceAsyncFn(async () => { - for (const pkg of pkgs) { - const pkgPath = path.join(repoDir, 'packages', pkg) - const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`) - - const pkgDataPath = path.join(pkgPath, 'package.json') - if (!fs.existsSync(pkgDataPath)) { - require('console').log(`Skipping ${pkgDataPath}`) - continue - } - const pkgData = require(pkgDataPath) - const { name } = pkgData - pkgDatas.set(name, { - pkgDataPath, - pkg, - pkgPath, - pkgData, - packedPkgPath, - }) - pkgPaths.set(name, packedPkgPath) - } - - for (const pkg of pkgDatas.keys()) { - const { pkgDataPath, pkgData } = pkgDatas.get(pkg) - - for (const pkg of pkgDatas.keys()) { - const { packedPkgPath } = pkgDatas.get(pkg) - if (!pkgData.dependencies || !pkgData.dependencies[pkg]) - continue - pkgData.dependencies[pkg] = packedPkgPath - } - - // make sure native binaries are included in local linking - if (pkg === '@next/swc') { - if (!pkgData.files) { - pkgData.files = [] - } - pkgData.files.push('native') - require('console').log( - 'using swc binaries: ', - await exec( - `ls ${path.join(path.dirname(pkgDataPath), 'native')}` - ) - ) - } - - if (pkg === 'next') { - if (nextSwcPkg) { - Object.assign(pkgData.dependencies, nextSwcPkg) - } else { - if (pkgDatas.get('@next/swc')) { - pkgData.dependencies['@next/swc'] = - pkgDatas.get('@next/swc').packedPkgPath - } else { - pkgData.files.push('native') - } - } - } - - await fs.writeFile( - pkgDataPath, - JSON.stringify(pkgData, null, 2), - 'utf8' - ) - } - }) - - // wait to pack packages until after dependency paths have been updated - // to the correct versions - await rootSpan - .traceChild('packing packages') - .traceAsyncFn(async (packingSpan) => { - await Promise.all( - Array.from(pkgDatas.keys()).map(async (pkgName) => { - await packingSpan - .traceChild(`pack ${pkgName}`) - .traceAsyncFn(async () => { - const { pkg, pkgPath } = pkgDatas.get(pkgName) - await exec( - `cd ${pkgPath} && yarn pack -f '${pkg}-packed.tgz'`, - true - ) - }) - }) - ) - }) + async linkPackages({ repoDir, nextSwcVersion }) { + execa.sync('pnpm', ['turbo', 'run', 'test-pack'], { + cwd: repoDir, + env: { NEXT_SWC_VERSION: nextSwcVersion }, + }) - return pkgPaths + const pkgPaths = new Map() + const pkgs = await fs.readdir(path.join(repoDir, 'packages')) + + pkgs.forEach((pkgDirname) => { + const { name } = require(path.join( + repoDir, + 'packages', + pkgDirname, + 'package.json' + )) + pkgPaths.set( + name, + path.join(repoDir, 'packages', pkgDirname, `packed-${pkgDirname}.tgz`) + ) }) + return pkgPaths }, } } diff --git a/.gitignore b/.gitignore index 6bbbba1173fd1..d5f0273c3fbc8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dist .next target packages/next/wasm/@next +packages/*/packed-*.tgz # dependencies node_modules @@ -29,6 +30,7 @@ test/**/tsconfig.json /e2e-tests test/tmp/** test/.trace +test/traces # Editors **/.idea @@ -49,3 +51,4 @@ test-timings.json # Cache *.tsbuildinfo .swc/ +.turbo diff --git a/package.json b/package.json index 1add35dd218c0..75d042c7c5de8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test": "pnpm testheadless", "testonly": "pnpm jest --runInBand", "testheadless": "cross-env HEADLESS=true pnpm testonly", + "test-pack": "TS_NODE_TRANSPILE_ONLY=1 node --loader ts-node/esm scripts/test-pack-package.mts", "genstats": "cross-env LOCAL_STATS=true node .github/actions/next-stats-action/src/index.js", "git-reset": "git reset --hard HEAD", "git-clean": "git clean -d -x -e node_modules -e packages -f", @@ -216,6 +217,7 @@ "tailwindcss": "1.1.3", "taskr": "1.1.0", "tree-kill": "1.2.2", + "ts-node": "10.9.1", "tsec": "0.2.1", "turbo": "1.6.3", "typescript": "4.8.2", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 19a863a58b6db..02a479e160e3a 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -25,7 +25,8 @@ "prerelease": "rimraf ./dist/", "release": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register", "prepublishOnly": "cd ../../ && turbo run build", - "build": "pnpm release" + "build": "pnpm release", + "test-pack": "cd ../../ && pnpm test-pack create-next-app" }, "devDependencies": { "@types/async-retry": "1.4.2", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index c55e73664c281..52a3802194830 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -8,6 +8,9 @@ "url": "vercel/next.js", "directory": "packages/eslint-config-next" }, + "scripts": { + "test-pack": "cd ../../ && pnpm test-pack eslint-config-next" + }, "dependencies": { "@next/eslint-plugin-next": "13.1.3-canary.4", "@rushstack/eslint-patch": "^1.1.3", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 2b2804d426bdb..4f006f4f37b02 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -20,6 +20,7 @@ }, "scripts": { "build": "swc -d dist src", - "prepublishOnly": "cd ../../ && turbo run build" + "prepublishOnly": "cd ../../ && turbo run build", + "test-pack": "cd ../../ && pnpm test-pack eslint-plugin-next" } } diff --git a/packages/font/package.json b/packages/font/package.json index 1acbb788fba7b..6d57e8c6edb2c 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -17,7 +17,8 @@ "prepublishOnly": "cd ../../ && turbo run build", "dev": "pnpm ncc-fontkit && tsc -d -w -p tsconfig.json", "typescript": "tsec --noEmit -p tsconfig.json", - "ncc-fontkit": "ncc build ./fontkit.js -o dist/fontkit" + "ncc-fontkit": "ncc build ./fontkit.js -o dist/fontkit", + "test-pack": "cd ../../ && pnpm test-pack font" }, "devDependencies": { "@types/fontkit": "2.0.0", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 041f81aff4de3..c207b513b298e 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -10,5 +10,8 @@ }, "dependencies": { "webpack-bundle-analyzer": "4.7.0" + }, + "scripts": { + "test-pack": "cd ../../ && pnpm test-pack next-bundle-analyzer" } } diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 2cbc8ced16d03..028ebeb256c4a 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -22,7 +22,8 @@ "build": "pnpm tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", "dev": "pnpm tsc -d -w -p tsconfig.json", - "test": "jest" + "test": "jest", + "test-pack": "cd ../../ && pnpm test-pack next-codemod" }, "bin": "./bin/next-codemod.js", "devDependencies": { diff --git a/packages/next-env/package.json b/packages/next-env/package.json index b09c8a8187d7a..e8dd37a133b2a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -27,7 +27,8 @@ "types": "tsc index.ts --declaration --emitDeclarationOnly --declarationDir types --esModuleInterop", "release": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register", "build": "pnpm release && pnpm types", - "prepublishOnly": "cd ../../ && turbo run build" + "prepublishOnly": "cd ../../ && turbo run build", + "test-pack": "cd ../../ && pnpm test-pack next-env" }, "devDependencies": { "@vercel/ncc": "0.34.0", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index f3614ee27caef..f8171120642a6 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -7,6 +7,9 @@ "url": "vercel/next.js", "directory": "packages/next-mdx" }, + "scripts": { + "test-pack": "cd ../../ && pnpm test-pack next-mdx" + }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": "*" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index f5fb8a627884c..c5a26ddeb2e6e 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -5,6 +5,9 @@ "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" }, + "scripts": { + "test-pack": "cd ../../ && pnpm test-pack next-plugin-storybook" + }, "peerDependencies": { "next": "*" } diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index d1094dff0dc07..d891aabfdca1c 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -11,7 +11,8 @@ "scripts": { "build": "microbundle -i src/index.js -o dist/polyfill-module.js -f iife --no-sourcemap --external none --no-pkg-main", "dev": "pnpm build", - "prepublishOnly": "cd ../../ && turbo run build" + "prepublishOnly": "cd ../../ && turbo run build", + "test-pack": "cd ../../ && pnpm test-pack next-polyfill-module" }, "devDependencies": { "microbundle": "0.15.0" diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index bb14be3821fb6..c5fffe7d53807 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -11,7 +11,8 @@ "scripts": { "build": "microbundle -i src/index.js -o dist/polyfill-nomodule.js -f iife --no-sourcemap --external none --no-pkg-main", "dev": "pnpm build", - "prepublishOnly": "cd ../../ && turbo run build" + "prepublishOnly": "cd ../../ && turbo run build", + "test-pack": "cd ../../ && pnpm test-pack next-polyfill-nomodule" }, "devDependencies": { "core-js": "3.6.5", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index dd1668610a54d..ff7ce92602eef 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -8,7 +8,8 @@ "build-native-no-plugin": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --js false native", "build-native-no-plugin-woa": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --cargo-flags=--no-default-features --features native-tls --js false native", "build-wasm": "wasm-pack build crates/wasm --scope=next", - "cache-build-native": "echo $(ls native)" + "cache-build-native": "echo $(ls native)", + "test-pack": "cd ../../ && pnpm test-pack next-swc" }, "napi": { "name": "next-swc", diff --git a/packages/next/package.json b/packages/next/package.json index bc8b2afd43c60..b3e0a08650b35 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -66,7 +66,8 @@ "prepublishOnly": "cd ../../ && turbo run build", "types": "tsc --declaration --emitDeclarationOnly --declarationDir dist", "typescript": "tsec --noEmit", - "ncc-compiled": "ncc cache clean && taskr ncc" + "ncc-compiled": "ncc cache clean && taskr ncc", + "test-pack": "cd ../../ && pnpm test-pack next" }, "taskr": { "requires": [ diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 5cb91f8a15cba..0e6db061fa0b5 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -15,7 +15,8 @@ "build": "rimraf dist && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", "dev": "tsc -d -w -p tsconfig.json", - "typescript": "tsec --noEmit -p tsconfig.json" + "typescript": "tsec --noEmit -p tsconfig.json", + "test-pack": "cd ../../ && pnpm test-pack react-dev-overlay" }, "dependencies": { "@babel/code-frame": "7.12.11", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index aa086ab890f79..146207451d65f 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -14,7 +14,8 @@ "scripts": { "build": "rimraf dist && tsc -d -p tsconfig.json", "prepublishOnly": "cd ../../ && turbo run build", - "dev": "tsc -d -w -p tsconfig.json" + "dev": "tsc -d -w -p tsconfig.json", + "test-pack": "cd ../../ && pnpm test-pack react-refresh-utils" }, "peerDependencies": { "react-refresh": "0.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc05cf40f0586..16f4881fb2534 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,7 @@ importers: tailwindcss: 1.1.3 taskr: 1.1.0 tree-kill: 1.2.2 + ts-node: 10.9.1 tsec: 0.2.1 turbo: 1.6.3 typescript: 4.8.2 @@ -299,7 +300,7 @@ importers: image-size: 0.9.3 is-animated: 2.0.2 isomorphic-unfetch: 3.0.0 - jest: 27.0.6 + jest: 27.0.6_ts-node@10.9.1 jest-extended: 1.2.1 json5: 2.2.3 ky: 0.19.1 @@ -354,6 +355,7 @@ importers: tailwindcss: 1.1.3 taskr: 1.1.0 tree-kill: 1.2.2 + ts-node: 10.9.1_cx2odcp7q42yre3tu7le55sjlu tsec: 0.2.1_sbe2uaqno6akssxfwbhgeg7v2q turbo: 1.6.3 typescript: 4.8.2 @@ -4350,6 +4352,13 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@cspotcode/source-map-support/0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@csstools/postcss-color-function/1.1.0_postcss@8.4.14: resolution: {integrity: sha512-5D5ND/mZWcQoSfYnSPsXtuiFxhzmhxt6pcjrFLJyldj+p0ZN2vvRpYNX+lahFTtMhAYOa2WmkdGINr0yP0CvGA==} engines: {node: ^12 || ^14 || >=16} @@ -4931,7 +4940,7 @@ packages: slash: 3.0.0 dev: true - /@jest/core/27.0.6: + /@jest/core/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-SsYBm3yhqOn5ZLJCtccaBcvD/ccTLCeuDv8U41WJH/V1MW5eKUkeMHT9U+Pw/v1m1AIWlnIW/eM2XzQr0rEmow==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: @@ -4952,7 +4961,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 27.0.6 - jest-config: 27.0.6 + jest-config: 27.0.6_ts-node@10.9.1 jest-haste-map: 27.0.6 jest-message-util: 27.5.1 jest-regex-util: 27.0.6 @@ -5226,6 +5235,13 @@ packages: '@jridgewell/resolve-uri': 3.0.5 '@jridgewell/sourcemap-codec': 1.4.11 + /@jridgewell/trace-mapping/0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.0.5 + '@jridgewell/sourcemap-codec': 1.4.11 + dev: true + /@lerna/add/4.0.0: resolution: {integrity: sha512-cpmAH1iS3k8JBxNvnMqrGTTjbY/ZAiKa1ChJzFevMYY3eeqbvhsBKnBcxjRXtdrJ6bd3dCQM+ZtK+0i682Fhng==} engines: {node: '>= 10.18.0'} @@ -6956,6 +6972,22 @@ packages: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} + /@tsconfig/node10/1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12/1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14/1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16/1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + /@types/amphtml-validator/1.0.0: resolution: {integrity: sha512-CJOi00fReT1JehItkgTZDI47v9WJxUH/OLX0XzkDgyEed7dGdeUQfXk5CTRM7N9FkHdv3klSjsZxo5sH1oTIGg==} dependencies: @@ -7983,6 +8015,11 @@ packages: resolution: {integrity: sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==} engines: {node: '>=0.4.0'} + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn/6.4.2: resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} engines: {node: '>=0.4.0'} @@ -10335,6 +10372,10 @@ packages: sha.js: 2.4.11 dev: true + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /critters/0.0.6: resolution: {integrity: sha512-NUB3Om7tkf+XWi9+2kJ2A3l4/tHORDI1UT+nHxUqay2B/tJvMpiXcklDDLBH3fPn9Pe23uu0we/08Ukjy4cLCQ==} dependencies: @@ -11147,6 +11188,11 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dev: true + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /diffie-hellman/5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} dependencies: @@ -13953,7 +13999,7 @@ packages: dependencies: '@babel/code-frame': 7.18.6 '@sidvind/better-ajv-errors': 0.6.10_ajv@6.12.6 - acorn-walk: 8.0.0 + acorn-walk: 8.2.0 ajv: 6.12.6 chalk: 4.1.2 deepmerge: 4.2.2 @@ -15107,7 +15153,7 @@ packages: - supports-color dev: true - /jest-cli/27.0.6: + /jest-cli/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-qUUVlGb9fdKir3RDE+B10ULI+LQrz+MCflEH2UJyoUjoHHCbxDrMxSzjQAPUMsic4SncI62ofYCcAvW6+6rhhg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -15117,14 +15163,14 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 27.0.6 + '@jest/core': 27.0.6_ts-node@10.9.1 '@jest/test-result': 27.0.6 '@jest/types': 27.5.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.0.2 - jest-config: 27.0.6 + jest-config: 27.0.6_ts-node@10.9.1 jest-util: 27.5.1 jest-validate: 27.0.6 prompts: 2.3.0 @@ -15137,7 +15183,7 @@ packages: - utf-8-validate dev: true - /jest-config/27.0.6: + /jest-config/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-JZRR3I1Plr2YxPBhgqRspDE2S5zprbga3swYNrvY3HfQGu7p/GjyLOqwrYad97tX3U3mzT53TPHVmozacfP/3w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} peerDependencies: @@ -15167,6 +15213,7 @@ packages: jest-validate: 27.5.1 micromatch: 4.0.4 pretty-format: 27.5.1 + ts-node: 10.9.1_cx2odcp7q42yre3tu7le55sjlu transitivePeerDependencies: - bufferutil - canvas @@ -15777,7 +15824,7 @@ packages: merge-stream: 2.0.0 supports-color: 8.1.1 - /jest/27.0.6: + /jest/27.0.6_ts-node@10.9.1: resolution: {integrity: sha512-EjV8aETrsD0wHl7CKMibKwQNQc3gIRBXlTikBmmHUeVMKaPFxdcUIBfoDqTSXDoGJIivAYGqCWVlzCSaVjPQsA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -15787,9 +15834,9 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 27.0.6 + '@jest/core': 27.0.6_ts-node@10.9.1 import-local: 3.0.2 - jest-cli: 27.0.6 + jest-cli: 27.0.6_ts-node@10.9.1 transitivePeerDependencies: - bufferutil - canvas @@ -16757,6 +16804,10 @@ packages: semver: 6.3.0 dev: true + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + /make-fetch-happen/8.0.13: resolution: {integrity: sha512-rQ5NijwwdU8tIaBrpTtSVrNCcAJfyDRcKBC76vOQlyJX588/88+TE+UpjWl4BgG7gCkp29wER7xcRqkeg+x64Q==} engines: {node: '>= 10'} @@ -22980,6 +23031,38 @@ packages: dependencies: glob: 7.2.0 + /ts-node/10.9.1_cx2odcp7q42yre3tu7le55sjlu: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@swc/core': 1.2.203 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 14.14.31 + acorn: 8.8.0 + acorn-walk: 8.2.0 + arg: 4.1.0 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.8.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} dependencies: @@ -23651,6 +23734,10 @@ packages: hasBin: true dev: true + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + /v8-compile-cache/2.1.0: resolution: {integrity: sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==} dev: true @@ -24319,6 +24406,11 @@ packages: buffer-crc32: 0.2.13 dev: true + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} diff --git a/scripts/test-pack-package.mts b/scripts/test-pack-package.mts new file mode 100755 index 0000000000000..36399fe001c25 --- /dev/null +++ b/scripts/test-pack-package.mts @@ -0,0 +1,105 @@ +import path from 'path' +import fs from 'fs-extra' +import os from 'os' +import execa from 'execa' +import { randomBytes } from 'crypto' +import { fileURLToPath } from 'url' + +const main = async () => { + const __dirname = fileURLToPath(new URL('.', import.meta.url)) + const repoRoot = path.dirname(__dirname) + const pkgsDir = path.join(repoRoot, 'packages') + const currentPkgDirname = process.argv[2] + + const getPackedPkgPath = (pkgDirname: string) => + path.join(pkgsDir, pkgDirname, `packed-${pkgDirname}.tgz`) + const getPackageJsonPath = (pkgDirname: string) => + path.join(pkgsDir, pkgDirname, `package.json`) + + const allPkgDirnames = await fs.readdir(pkgsDir) + if (!allPkgDirnames.includes(currentPkgDirname)) { + throw new Error(`Unknown package '${currentPkgDirname}'`) + } + + const currentPkgDir = path.join(pkgsDir, currentPkgDirname) + + const tmpPkgPath = path.join( + os.tmpdir(), + `${currentPkgDirname}-${randomBytes(32).toString('hex')}` + ) + console.log(`Packing '${currentPkgDirname}' in '${tmpPkgPath}'.`) + + const packageJsonPath = getPackageJsonPath(currentPkgDirname) + const packageJson = await fs.readJson(packageJsonPath) + const dependencies = packageJson.dependencies + + if (packageJson?.scripts?.prepublishOnly) { + // There's a bug in `pnpm pack` where it will run + // the prepublishOnly script and that will fail. + // See https://github.com/pnpm/pnpm/issues/2941 + delete packageJson.scripts.prepublishOnly + } + + // @next/swc is devDependency in next, but we want to include it anyway + if (currentPkgDirname === 'next') { + dependencies['@next/swc'] = '*' + } + + // Modify dependencies to point to packed packages + if (dependencies) { + await Promise.all( + allPkgDirnames.map(async (depPkgDirname) => { + const { name: depPkgName } = await fs.readJson( + getPackageJsonPath(depPkgDirname) + ) + if (depPkgName in dependencies) { + dependencies[depPkgName] = getPackedPkgPath(depPkgDirname) + } + }) + ) + } + + // Ensure that we bundle binaries with swc + if (currentPkgDirname === 'next-swc') { + packageJson.files = packageJson.files ?? [] + packageJson.files.push('native') + + console.log('using swc binaries:') + await execa('ls', [ + path.join(path.dirname(packageJsonPath), 'native'), + ]).stdout?.pipe(process.stdout) + } + + // Allow overriding nateve swc version in next + if (currentPkgDirname === 'next' && process.env.NEXT_SWC_VERSION) { + dependencies['@next/swc-linux-x64-gnu'] = process.env.NEXT_SWC_VERSION + } + + try { + await fs.copy(currentPkgDir, tmpPkgPath, { + filter: (item) => + !item.includes('node_modules') && + !item.includes('.DS_Store') && + // Exclude Rust compilation files + (currentPkgDirname !== 'next' || + !/build[\\/]swc[\\/]target/.test(item)) && + (currentPkgDirname !== 'next-swc' || !/target/.test(item)), + }) + await fs.writeJson(path.join(tmpPkgPath, 'package.json'), packageJson) + // Copied from pnpm source: https://github.com/pnpm/pnpm/blob/5a5512f14c47f4778b8d2b6d957fb12c7ef40127/releasing/plugin-commands-publishing/src/pack.ts#L96 + const tmpTarball = path.join( + tmpPkgPath, + `${packageJson.name.replace('@', '').replace('/', '-')}-${ + packageJson.version + }.tgz` + ) + await execa('yarn', ['pack', '-f', tmpTarball], { + cwd: tmpPkgPath, + }) + await fs.copyFile(tmpTarball, getPackedPkgPath(currentPkgDirname)) + } finally { + await fs.remove(tmpPkgPath).catch() + } +} + +main() diff --git a/scripts/trace-next-server.js b/scripts/trace-next-server.js index e2d8659e9a072..ca1db9cdd574f 100644 --- a/scripts/trace-next-server.js +++ b/scripts/trace-next-server.js @@ -38,7 +38,7 @@ async function main() { console.log('using repodir', repoDir) await fs.ensureDir(workDir) - const pkgPaths = await linkPackages({ repoDir }) + const pkgPaths = await linkPackages({ repoDir: origRepoDir }) await fs.writeFile( path.join(workDir, 'package.json'), diff --git a/test/lib/create-next-install.js b/test/lib/create-next-install.js index 7e8060310ecdb..0f22bf60e207f 100644 --- a/test/lib/create-next-install.js +++ b/test/lib/create-next-install.js @@ -7,30 +7,11 @@ const { randomBytes } = require('crypto') const { linkPackages } = require('../../.github/actions/next-stats-action/src/prepare/repo-setup')() -/** - * These are simple dependencies provided by default. We want to optimize this special case. - */ -const areGenericDependencies = (dependencies) => - Object.keys(dependencies).length === 6 && - Object.entries(dependencies).every(([dep, version]) => { - if (dep === 'next') return true - return ( - [ - 'react', - 'react-dom', - 'typescript', - '@types/react', - '@types/node', - ].includes(dep) && version === 'latest' - ) - }) - async function createNextInstall({ parentSpan, dependencies, installCommand, packageJson = {}, - packageLockPath = '', dirSuffix = '', }) { return await parentSpan @@ -42,14 +23,9 @@ async function createNextInstall({ tmpDir, `next-install-${randomBytes(32).toString('hex')}${dirSuffix}` ) - const tmpRepoDir = path.join( - tmpDir, - `next-repo-${randomBytes(32).toString('hex')}${dirSuffix}` - ) - require('console').log('Using following temporary directories:') + require('console').log('Creating next instance in:') require('console').log(installDir) - require('console').log(tmpRepoDir) await rootSpan.traceChild(' enruse swc binary').traceAsyncFn(async () => { // ensure swc binary is present in the native folder if @@ -82,35 +58,16 @@ async function createNextInstall({ } }) - for (const item of ['package.json', 'packages']) { - await rootSpan - .traceChild(`copy ${item} to temp dir`) - .traceAsyncFn(async () => { - await fs.copy( - path.join(origRepoDir, item), - path.join(tmpRepoDir, item), - { - filter: (item) => { - return ( - !item.includes('node_modules') && - !item.includes('.DS_Store') && - // Exclude Rust compilation files - !/next[\\/]build[\\/]swc[\\/]target/.test(item) && - !/next-swc[\\/]target/.test(item) - ) - }, - } - ) - }) - } - let combinedDependencies = dependencies if (!(packageJson && packageJson.nextPrivateSkipLocalDeps)) { - const pkgPaths = await linkPackages({ - repoDir: tmpRepoDir, - parentSpan: rootSpan, - }) + const pkgPaths = await rootSpan + .traceChild('linkPackages') + .traceAsyncFn(() => + linkPackages({ + repoDir: origRepoDir, + }) + ) combinedDependencies = { next: pkgPaths.get('next'), ...Object.keys(dependencies).reduce((prev, pkg) => { @@ -135,13 +92,6 @@ async function createNextInstall({ ) ) - if (packageLockPath) { - await fs.copy( - packageLockPath, - path.join(installDir, path.basename(packageLockPath)) - ) - } - if (installCommand) { const installString = typeof installCommand === 'function' @@ -159,65 +109,20 @@ async function createNextInstall({ await rootSpan .traceChild('run generic install command') .traceAsyncFn(async () => { - const runInstall = async () => { - const args = ['install', '--strict-peer-dependencies=false'] + const args = ['install', '--strict-peer-dependencies=false'] - if (process.env.NEXT_TEST_PREFER_OFFLINE === '1') { - args.push('--prefer-offline') - } - - return await execa('pnpm', args, { - cwd: installDir, - stdio: ['ignore', 'inherit', 'inherit'], - env: process.env, - }) + if (process.env.NEXT_TEST_PREFER_OFFLINE === '1') { + args.push('--prefer-offline') } - if (!areGenericDependencies(combinedDependencies)) { - await runInstall() - } else { - const cacheDir = path.join( - origRepoDir, - 'test', - 'tmp', - 'genericInstallCache' - ) - - const cachedFiles = [ - // We can't cache node-modules because .pnpm store must be on the same mount - we can't move it between mountpoints - // 'node_modules', - // FIXME: caching lock file caused itssues and It's not possible when we don't use turbo which we had to disable temporarily - // 'pnpm-lock.yaml', - ] - - if ( - await fs - .access(cacheDir) - .then(() => true) - .catch(() => false) - ) { - require('console').log( - 'We are able to prepopulate pnpm install from cache' - ) - cachedFiles.forEach((file) => { - fs.copy( - path.join(cacheDir, file), - path.join(installDir, file) - ) - }) - } - - await runInstall() - - await fs.ensureDir(cacheDir) - cachedFiles.forEach((file) => { - fs.copy(path.join(installDir, file), path.join(cacheDir, file)) - }) - } + await execa('pnpm', args, { + cwd: installDir, + stdio: ['ignore', 'inherit', 'inherit'], + env: process.env, + }) }) } - await fs.remove(tmpRepoDir) return installDir }) } diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts index 12433a429c652..714969ff46311 100644 --- a/test/lib/next-modes/base.ts +++ b/test/lib/next-modes/base.ts @@ -24,7 +24,6 @@ export interface NextInstanceOpts { files: FileRef | string | { [filename: string]: string | FileRef } dependencies?: { [name: string]: string } packageJson?: PackageJson - packageLockPath?: string nextConfig?: NextConfig installCommand?: InstallCommand buildCommand?: string @@ -59,7 +58,6 @@ export class NextInstance { protected _url: string protected _parsedUrl: URL protected packageJson?: PackageJson = {} - protected packageLockPath?: string protected basePath?: string protected env?: Record public forcedPort?: string @@ -179,7 +177,6 @@ export class NextInstance { dependencies: finalDependencies, installCommand: this.installCommand, packageJson: this.packageJson, - packageLockPath: this.packageLockPath, dirSuffix: this.dirSuffix, }) } @@ -266,7 +263,6 @@ export class NextInstance { ` ) } - require('console').log(`Test directory created at ${this.testDir}`) }) } diff --git a/turbo.json b/turbo.json index 21c50734074ee..e5ddd1a816a54 100644 --- a/turbo.json +++ b/turbo.json @@ -29,6 +29,16 @@ "dependsOn": ["^dev"], "outputs": ["dist/**"] }, - "typescript": {} + "typescript": {}, + "test-pack": { + "dependsOn": ["^test-pack"], + "inputs": [ + "*", + "../../scripts/test-pack-package.mts", + "../../package.json" + ], + "outputs": ["packed-*.tgz"], + "env": ["NEXT_SWC_VERSION"] + } } } From 4dfe831bbb04ca601d14ab4c1f76be128c942a59 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 14:37:40 -0800 Subject: [PATCH 37/56] Add project directory rename/remove handling (#44911) --- packages/next/src/cli/next-dev.ts | 222 ++++++++++++++++-- packages/next/src/server/base-server.ts | 33 ++- .../next/src/server/dev/next-dev-server.ts | 7 +- .../ReactRefreshLogBox-scss.test.ts.snap | 2 +- .../basic/project-directory-rename.test.ts | 68 ++++++ .../create-root-layout.test.ts | 19 +- .../edge-configurable-runtime/index.test.ts | 9 +- .../test/index.test.js | 4 +- .../invalid-image-import/test/index.test.ts | 5 +- .../project-dir-delete/index.test.ts | 49 ++++ test/lib/next-modes/next-dev.ts | 2 +- test/lib/next-modes/next-start.ts | 2 +- 12 files changed, 378 insertions(+), 44 deletions(-) create mode 100644 test/development/basic/project-directory-rename.test.ts create mode 100644 test/integration/project-dir-delete/index.test.ts diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 190d12d338872..022e3b018aaab 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -1,6 +1,5 @@ #!/usr/bin/env node import arg from 'next/dist/compiled/arg/index.js' -import { existsSync, watchFile } from 'fs' import { startServer } from '../server/lib/start-server' import { getPort, printAndExit } from '../server/lib/utils' import * as Log from '../build/output/log' @@ -18,11 +17,15 @@ import cluster from 'cluster' import { Telemetry } from '../telemetry/storage' import loadConfig from '../server/config' import { findPagesDir } from '../lib/find-pages-dir' +import { fileExists } from '../lib/file-exists' +import Watchpack from 'next/dist/compiled/watchpack' +import stripAnsi from 'next/dist/compiled/strip-ansi' let isTurboSession = false let sessionStopHandled = false let sessionStarted = Date.now() let dir: string +let unwatchConfigFiles: () => void const handleSessionStop = async () => { if (sessionStopHandled) return @@ -65,14 +68,37 @@ const handleSessionStop = async () => { true ) telemetry.flushDetached('dev', dir) - } catch (err) { - console.error(err) + } catch (_) { + // errors here aren't actionable so don't add + // noise to the output } process.exit(0) } -process.on('SIGINT', handleSessionStop) -process.on('SIGTERM', handleSessionStop) +if (cluster.isMaster) { + process.on('SIGINT', handleSessionStop) + process.on('SIGTERM', handleSessionStop) +} else { + process.on('SIGINT', () => process.exit(0)) + process.on('SIGTERM', () => process.exit(0)) +} + +function watchConfigFiles(dirToWatch: string) { + if (unwatchConfigFiles) { + unwatchConfigFiles() + } + + const wp = new Watchpack() + wp.watch({ files: CONFIG_FILES.map((file) => path.join(dirToWatch, file)) }) + wp.on('change', (filename) => { + console.log( + `\n> Found a change in ${path.basename( + filename + )}. Restart the server to see the changes in effect.` + ) + }) + return () => wp.close() +} const nextDev: CliCommand = async (argv) => { const validArgs: arg.Spec = { @@ -121,10 +147,11 @@ const nextDev: CliCommand = async (argv) => { process.exit(0) } - dir = getProjectDir(args._[0]) + dir = getProjectDir(process.env.NEXT_PRIVATE_DEV_DIR || args._[0]) + unwatchConfigFiles = watchConfigFiles(dir) // Check if pages dir exists and warn if not - if (!existsSync(dir)) { + if (!(await fileExists(dir, 'directory'))) { printAndExit(`> No such directory exists as the project root: ${dir}`) } @@ -392,12 +419,173 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit // this is a temporary solution until we can fix the memory leaks. // the logic for the worker killing itself is in `packages/next/server/lib/start-server.ts` if (!process.env.__NEXT_DISABLE_MEMORY_WATCHER && cluster.isMaster) { - cluster.fork() - cluster.on('exit', (worker) => { - if (worker.exitedAfterDisconnect) { - cluster.fork() + let config: NextConfig + + const setupFork = (env?: Parameters[0]) => { + const startDir = dir + let shouldFilter = false + cluster.fork({ + ...env, + FORCE_COLOR: '1', + }) + + // since errors can start being logged from the fork + // before we detect the project directory rename + // attempt suppressing them long enough to check + const filterForkErrors = (chunk: Buffer, fd: 'stdout' | 'stderr') => { + const cleanChunk = stripAnsi(chunk + '') + if ( + cleanChunk.match( + /(ENOENT|Module build failed|Module not found|Cannot find module)/ + ) + ) { + if (startDir === dir) { + try { + // check if start directory is still valid + const result = findPagesDir( + startDir, + !!config.experimental?.appDir + ) + shouldFilter = !Boolean(result.pagesDir || result.appDir) + } catch (_) { + shouldFilter = true + } + } + if (shouldFilter || startDir !== dir) { + shouldFilter = true + return + } + } + process[fd].write(chunk) + } + + for (const workerId in cluster.workers) { + cluster.workers[workerId]?.process.stdout?.on('data', (chunk) => { + filterForkErrors(chunk, 'stdout') + }) + cluster.workers[workerId]?.process.stderr?.on('data', (chunk) => { + filterForkErrors(chunk, 'stderr') + }) + } + } + + const handleClusterExit = () => { + const callback = async (worker: cluster.Worker) => { + // ignore if we killed the worker + if ((worker as any).killed) return + + // TODO: we should track how many restarts are + // occurring and how long in-between them + if (worker.exitedAfterDisconnect) { + setupFork() + } else if (!sessionStopHandled) { + await handleSessionStop() + process.exit(1) + } + } + cluster.addListener('exit', callback) + return () => cluster.removeListener('exit', callback) + } + let clusterExitUnsub = handleClusterExit() + cluster.settings.stdio = ['ipc', 'pipe', 'pipe'] + + setupFork() + config = await loadConfig(PHASE_DEVELOPMENT_SERVER, dir) + + const handleProjectDirRename = (newDir: string) => { + clusterExitUnsub() + + for (const workerId in cluster.workers) { + try { + // @ts-expect-error custom field + cluster.workers[workerId].killed = true + cluster.workers[workerId]!.process.kill('SIGKILL') + } catch (_) {} + } + process.chdir(newDir) + // @ts-expect-error type is incorrect + cluster.settings.cwd = newDir + cluster.settings.exec = cluster.settings.exec?.replace(dir, newDir) + setupFork({ + ...Object.keys(process.env).reduce((newEnv, key) => { + newEnv[key] = process.env[key]?.replace(dir, newDir) + return newEnv + }, {} as typeof process.env), + NEXT_PRIVATE_DEV_DIR: newDir, + }) + clusterExitUnsub = handleClusterExit() + } + const parentDir = path.join('/', dir, '..') + const watchedEntryLength = parentDir.split('/').length + 1 + const previousItems = new Set() + + const wp = new Watchpack({ + ignored: (entry: string) => { + // watch only one level + return !(entry.split('/').length <= watchedEntryLength) + }, + }) + + wp.watch({ directories: [parentDir], startTime: 0 }) + + wp.on('aggregated', () => { + const knownFiles = wp.getTimeInfoEntries() + const newFiles: string[] = [] + let hasPagesApp = false + + // if the dir still exists nothing to check + try { + const result = findPagesDir(dir, !!config.experimental?.appDir) + hasPagesApp = Boolean(result.pagesDir || result.appDir) + } catch (err) { + // if findPagesDir throws validation error let this be + // handled in the dev-server itself in the fork + if ((err as any).message?.includes('experimental')) { + return + } + } + + // try to find new dir introduced + if (previousItems.size) { + for (const key of knownFiles.keys()) { + if (!previousItems.has(key)) { + newFiles.push(key) + } + } + previousItems.clear() + } + + for (const key of knownFiles.keys()) { + previousItems.add(key) + } + + if (hasPagesApp) { + return + } + + // if we failed to find the new dir it may have been moved + // to a new parent directory which we can't track as easily + // so exit gracefully + try { + const result = findPagesDir( + newFiles[0], + !!config.experimental?.appDir + ) + hasPagesApp = Boolean(result.pagesDir || result.appDir) + } catch (_) {} + + if (hasPagesApp && newFiles.length === 1) { + Log.info( + `Detected project directory rename, restarting in new location` + ) + handleProjectDirRename(newFiles[0]) + watchConfigFiles(newFiles[0]) + dir = newFiles[0] } else { - process.exit(1) + Log.error( + `Project directory could not be found, restart Next.js in your new directory` + ) + process.exit(0) } }) } else { @@ -440,16 +628,6 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit }) } } - - for (const CONFIG_FILE of CONFIG_FILES) { - watchFile(path.join(dir, CONFIG_FILE), (cur: any, prev: any) => { - if (cur.size > 0 || prev.size > 0) { - console.log( - `\n> Found a change in ${CONFIG_FILE}. Restart the server to see the changes in effect.` - ) - } - }) - } } export { nextDev } diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 79710cbaecce5..c029e2b27bb18 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -1924,6 +1924,37 @@ export default abstract class Server { this.customErrorNo404Warn() } + if (!result) { + // this can occur when a project directory has been moved/deleted + // which is handled in the parent process in development + if (this.renderOpts.dev) { + return { + type: 'html', + // wait for dev-server to restart before refreshing + body: RenderResult.fromStatic( + ` +
missing required error components, refreshing...
+ ` + ), + } + } + + throw new WrappedBuildError( + new Error('missing required error components') + ) + } + try { return await this.renderToResponseWithComponents( { @@ -1934,7 +1965,7 @@ export default abstract class Server { err, }, }, - result! + result ) } catch (maybeFallbackError) { if (maybeFallbackError instanceof NoFallbackError) { diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 5bb59120df523..fee3933ff991d 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -1443,7 +1443,12 @@ export default class DevServer extends Server { // patched global in memory, creating a memory leak. this.restorePatchedGlobals() - return super.findPageComponents({ pathname, query, params, isAppPath }) + return await super.findPageComponents({ + pathname, + query, + params, + isAppPath, + }) } catch (err) { if ((err as any).code !== 'ENOENT') { throw err diff --git a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap index 847360206a9cd..d8ecc49fc84cd 100644 --- a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap +++ b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox-scss.test.ts.snap @@ -20,5 +20,5 @@ exports[`ReactRefreshLogBox app scss syntax errors 2`] = ` Syntax error: Selector \\"button\\" is not pure (pure selectors must contain at least one local class or id) > 1 | button { font-size: 5px; } - | ^" + | ^" `; diff --git a/test/development/basic/project-directory-rename.test.ts b/test/development/basic/project-directory-rename.test.ts new file mode 100644 index 0000000000000..3c4bd21ad5055 --- /dev/null +++ b/test/development/basic/project-directory-rename.test.ts @@ -0,0 +1,68 @@ +import fs from 'fs-extra' +import webdriver from 'next-webdriver' +import { check, findPort, hasRedbox } from 'next-test-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { createNext } from 'e2e-utils' +import stripAnsi from 'strip-ansi' + +describe('Project Directory Renaming', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` + export default function Page() { + return

hello world

+ } + `, + }, + skipStart: true, + }) + next.forcedPort = (await findPort()) + '' + await next.start() + }) + afterAll(() => next.destroy()) + + it('should detect project dir rename and restart', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval('window.beforeNav = 1') + + let newTestDir = `${next.testDir}-renamed` + await fs.move(next.testDir, newTestDir) + + next.testDir = newTestDir + + await check( + () => stripAnsi(next.cliOutput), + /Detected project directory rename, restarting in new location/ + ) + await check(async () => { + return (await browser.eval('window.beforeNav')) === 1 ? 'pending' : 'done' + }, 'done') + expect(await hasRedbox(browser, false)).toBe(false) + + try { + // should still HMR correctly + await next.patchFile( + 'pages/index.js', + ( + await next.readFile('pages/index.js') + ).replace('hello world', 'hello again') + ) + await check(async () => { + if (!(await browser.eval('!!window.next'))) { + await browser.refresh() + } + return browser.eval('document.documentElement.innerHTML') + }, /hello again/) + } finally { + await next.patchFile( + 'pages/index.js', + ( + await next.readFile('pages/index.js') + ).replace('hello again', 'hello world') + ) + } + }) +}) diff --git a/test/e2e/app-dir/create-root-layout/create-root-layout.test.ts b/test/e2e/app-dir/create-root-layout/create-root-layout.test.ts index fb8766c5379ac..fdb763a3a4502 100644 --- a/test/e2e/app-dir/create-root-layout/create-root-layout.test.ts +++ b/test/e2e/app-dir/create-root-layout/create-root-layout.test.ts @@ -2,6 +2,7 @@ import path from 'path' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { check } from 'next-test-utils' +import stripAnsi from 'strip-ansi' describe('app-dir create root layout', () => { const isDev = (global as any).isNextDev @@ -41,10 +42,10 @@ describe('app-dir create root layout', () => { ) await check( - () => next.cliOutput.slice(outputIndex), + () => stripAnsi(next.cliOutput.slice(outputIndex)), /did not have a root layout/ ) - expect(next.cliOutput.slice(outputIndex)).toMatch( + expect(stripAnsi(next.cliOutput.slice(outputIndex))).toMatch( 'Your page app/route/page.js did not have a root layout. We created app/layout.js and app/head.js for you.' ) @@ -101,10 +102,10 @@ describe('app-dir create root layout', () => { ) await check( - () => next.cliOutput.slice(outputIndex), + () => stripAnsi(next.cliOutput.slice(outputIndex)), /did not have a root layout/ ) - expect(next.cliOutput.slice(outputIndex)).toInclude( + expect(stripAnsi(next.cliOutput.slice(outputIndex))).toInclude( 'Your page app/(group)/page.js did not have a root layout. We created app/(group)/layout.js and app/(group)/head.js for you.' ) @@ -163,10 +164,10 @@ describe('app-dir create root layout', () => { ) await check( - () => next.cliOutput.slice(outputIndex), + () => stripAnsi(next.cliOutput.slice(outputIndex)), /did not have a root layout/ ) - expect(next.cliOutput.slice(outputIndex)).toInclude( + expect(stripAnsi(next.cliOutput.slice(outputIndex))).toInclude( 'Your page app/(group)/route/second/inner/page.js did not have a root layout. We created app/(group)/route/second/layout.js and app/(group)/route/second/head.js for you.' ) @@ -224,10 +225,10 @@ describe('app-dir create root layout', () => { ) await check( - () => next.cliOutput.slice(outputIndex), + () => stripAnsi(next.cliOutput.slice(outputIndex)), /did not have a root layout/ ) - expect(next.cliOutput.slice(outputIndex)).toInclude( + expect(stripAnsi(next.cliOutput.slice(outputIndex))).toInclude( 'Your page app/page.tsx did not have a root layout. We created app/layout.tsx and app/head.tsx for you.' ) @@ -281,7 +282,7 @@ describe('app-dir create root layout', () => { }) await expect(next.start()).rejects.toThrow('next build failed') - expect(next.cliOutput).toInclude( + expect(stripAnsi(next.cliOutput)).toInclude( "page.js doesn't have a root layout. To fix this error, make sure every page has a root layout." ) await next.destroy() diff --git a/test/e2e/edge-configurable-runtime/index.test.ts b/test/e2e/edge-configurable-runtime/index.test.ts index 572eb731a27fa..9d93f70c60dd0 100644 --- a/test/e2e/edge-configurable-runtime/index.test.ts +++ b/test/e2e/edge-configurable-runtime/index.test.ts @@ -2,6 +2,7 @@ import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { fetchViaHTTP, File, nextBuild } from 'next-test-utils' import { join } from 'path' +import stripAnsi from 'strip-ansi' const appDir = join(__dirname, './app') const pagePath = 'pages/index.jsx' @@ -50,7 +51,7 @@ describe('Configurable runtime for pages and API routes', () => { const res = await fetchViaHTTP(next.url, `/api/edge`) expect(res.status).toEqual(200) expect(next.cliOutput).not.toInclude('error') - expect(next.cliOutput).toInclude( + expect(stripAnsi(next.cliOutput)).toInclude( `warn - /pages/api/edge provided runtime 'experimental-edge'. It can be updated to 'edge' instead.` ) }) @@ -66,7 +67,7 @@ describe('Configurable runtime for pages and API routes', () => { const res = await fetchViaHTTP(next.url, `/`) expect(res.status).toEqual(200) expect(next.cliOutput).not.toInclude('error') - expect(next.cliOutput).toInclude( + expect(stripAnsi(next.cliOutput)).toInclude( `warn - You are using an experimental edge runtime, the API might change.` ) }) @@ -82,7 +83,7 @@ describe('Configurable runtime for pages and API routes', () => { await next.start() const res = await fetchViaHTTP(next.url, `/`) expect(res.status).toEqual(200) - expect(next.cliOutput).toInclude( + expect(stripAnsi(next.cliOutput)).toInclude( `error - Page /pages provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` ) expect(next.cliOutput).not.toInclude('warn') @@ -121,7 +122,7 @@ describe('Configurable runtime for pages and API routes', () => { }) expect(output.code).toBe(1) expect(output.stderr).not.toContain(`Build failed`) - expect(output.stderr).toContain( + expect(stripAnsi(output.stderr)).toContain( `Error: Page / provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` ) }) diff --git a/test/integration/edge-runtime-module-errors/test/index.test.js b/test/integration/edge-runtime-module-errors/test/index.test.js index 242e9dc934fa7..1fa51b2d1dba6 100644 --- a/test/integration/edge-runtime-module-errors/test/index.test.js +++ b/test/integration/edge-runtime-module-errors/test/index.test.js @@ -656,9 +656,9 @@ function expectModuleNotFoundProdError( output = context.logs.output ) { const moduleNotSupportedMessage = getUnsupportedModule(moduleName) - expect(output).not.toContain(moduleNotSupportedMessage) + expect(stripAnsi(output)).not.toContain(moduleNotSupportedMessage) const moduleNotFoundMessage = getModuleNotFound(moduleName) - expect(output).toContain(moduleNotFoundMessage) + expect(stripAnsi(output)).toContain(moduleNotFoundMessage) } function expectModuleNotFoundDevError( diff --git a/test/integration/next-image-new/invalid-image-import/test/index.test.ts b/test/integration/next-image-new/invalid-image-import/test/index.test.ts index a8790c7ceffd8..978aecfa09226 100644 --- a/test/integration/next-image-new/invalid-image-import/test/index.test.ts +++ b/test/integration/next-image-new/invalid-image-import/test/index.test.ts @@ -11,6 +11,7 @@ import { nextBuild, } from 'next-test-utils' import webdriver from 'next-webdriver' +import stripAnsi from 'strip-ansi' const appDir = join(__dirname, '../') let appPort: number @@ -26,9 +27,9 @@ function runTests({ isDev }) { expect(await hasRedbox(browser, true)).toBe(true) expect(await getRedboxHeader(browser)).toBe('Failed to compile') expect(await getRedboxSource(browser)).toBe(`./pages/index.js:3\n${msg}`) - expect(stderr).toContain(msg) + expect(stripAnsi(stderr)).toContain(msg) } else { - expect(stderr).toContain(msg) + expect(stripAnsi(stderr)).toContain(msg) } }) } diff --git a/test/integration/project-dir-delete/index.test.ts b/test/integration/project-dir-delete/index.test.ts new file mode 100644 index 0000000000000..f00a02cefc7c7 --- /dev/null +++ b/test/integration/project-dir-delete/index.test.ts @@ -0,0 +1,49 @@ +import { + check, + findPort, + killApp, + launchApp, + renderViaHTTP, +} from 'next-test-utils' +import { join } from 'path' +import fs from 'fs-extra' +import stripAnsi from 'strip-ansi' + +describe('Project Directory Delete Handling', () => { + it('should gracefully exit on project dir delete', async () => { + const appDir = join(__dirname, 'app') + const appPort = await findPort() + + await fs.ensureDir(join(appDir, 'pages')) + await fs.writeFile( + join(appDir, 'pages', 'index.js'), + ` + export default function Page() { + return

hello world

+ } + ` + ) + let output = '' + + const app = await launchApp(appDir, appPort, { + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + + expect(await renderViaHTTP(appPort, '/')).toContain('hello world') + await fs.remove(appDir) + + await check( + () => stripAnsi(output), + /Project directory could not be found, restart Next\.js in your new directory/ + ) + + try { + await killApp(app) + } catch (_) {} + }) +}) diff --git a/test/lib/next-modes/next-dev.ts b/test/lib/next-modes/next-dev.ts index c294a20c5e112..45d4842897044 100644 --- a/test/lib/next-modes/next-dev.ts +++ b/test/lib/next-modes/next-dev.ts @@ -70,7 +70,7 @@ export class NextDevInstance extends NextInstance { this.childProcess.on('close', (code, signal) => { if (this.isStopping) return if (code || signal) { - throw new Error( + require('console').error( `next dev exited unexpectedly with code/signal ${code || signal}` ) } diff --git a/test/lib/next-modes/next-start.ts b/test/lib/next-modes/next-start.ts index 3d712f1b01a19..cef159e367251 100644 --- a/test/lib/next-modes/next-start.ts +++ b/test/lib/next-modes/next-start.ts @@ -110,7 +110,7 @@ export class NextStartInstance extends NextInstance { this.childProcess.on('close', (code, signal) => { if (this.isStopping) return if (code || signal) { - throw new Error( + require('console').error( `next start exited unexpectedly with code/signal ${ code || signal }` From 27b95ffb1f8700de52c9560043441b6fba317101 Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 18 Jan 2023 23:45:17 +0100 Subject: [PATCH 38/56] chore: open dev overlay error links in new tab (#45018) --- packages/react-dev-overlay/src/internal/container/Errors.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-overlay/src/internal/container/Errors.tsx b/packages/react-dev-overlay/src/internal/container/Errors.tsx index 887d4f45f16b9..329436693bc83 100644 --- a/packages/react-dev-overlay/src/internal/container/Errors.tsx +++ b/packages/react-dev-overlay/src/internal/container/Errors.tsx @@ -57,7 +57,9 @@ const HotlinkedText: React.FC<{ if (linkRegex.test(word)) { return ( - {word} + + {word} + {index === array.length - 1 ? '' : ' '} ) From c50641f70987925984d96123de4ceceb3604a2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Wed, 18 Jan 2023 23:52:39 +0100 Subject: [PATCH 39/56] Remove next-app-loader from import trace (#44996) --- .../parseNotFoundError.ts | 2 +- .../acceptance-app/ReactRefreshLogBox.test.ts | 30 +++++++++++++++++++ .../ReactRefreshLogBox.test.ts.snap | 11 +++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/next/src/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/src/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index f7c7cf7e48bd2..b4c18fdb1b175 100644 --- a/packages/next/src/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/src/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -86,7 +86,7 @@ export async function getNotFoundError( .filter( (name) => name && - !/next-(middleware|client-pages|flight-(client|server|client-entry))-loader\.js/.test( + !/next-(app|middleware|client-pages|flight-(client|server|client-entry))-loader\.js/.test( name ) ) diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts index 70ecdaa79ed54..786af5d163eae 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts @@ -1365,5 +1365,35 @@ for (const variant of ['default', 'turbo']) { await cleanup() }) + + test('Import trace when module not found in layout', async () => { + const { session, cleanup } = await sandbox( + next, + + new Map([['app/module.js', `import "non-existing-module"`]]) + ) + + await session.patch( + 'app/layout.js', + ` + import "./module" + + export default function RootLayout({ children }) { + return ( + + + {children} + + ) + } + + ` + ) + + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() + + await cleanup() + }) }) } diff --git a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap index 83a71a3365965..036394b5d7fbe 100644 --- a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap +++ b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap @@ -6,6 +6,17 @@ exports[`ReactRefreshLogBox app default Hydration errors should get error link 1 See more info here: https://nextjs.org/docs/messages/react-hydration-error" `; +exports[`ReactRefreshLogBox app default Import trace when module not found in layout 1`] = ` +"./app/module.js:1:0 +Module not found: Can't resolve 'non-existing-module' +> 1 | import \\"non-existing-module\\" + +Import trace for requested module: +./app/layout.js + +https://nextjs.org/docs/messages/module-not-found" +`; + exports[`ReactRefreshLogBox app default Should not show __webpack_exports__ when exporting anonymous arrow function 1`] = ` "index.js (4:16) @ default From 57fb41b880c86204e10bec2067d4e637580ce070 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 16:59:02 -0800 Subject: [PATCH 40/56] Ensure additional windows are not created on Windows OS (#45022) --- packages/next/src/cli/next-dev.ts | 3 +++ packages/next/src/telemetry/project-id.ts | 1 + packages/next/src/telemetry/storage.ts | 8 ++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 022e3b018aaab..7b08565af0381 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -487,6 +487,9 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit return () => cluster.removeListener('exit', callback) } let clusterExitUnsub = handleClusterExit() + // x-ref: https://nodejs.org/api/cluster.html#clustersettings + // @ts-expect-error type is incorrect + cluster.settings.windowsHide = true cluster.settings.stdio = ['ipc', 'pipe', 'pipe'] setupFork() diff --git a/packages/next/src/telemetry/project-id.ts b/packages/next/src/telemetry/project-id.ts index 5c307ef02b576..ac2b2713f336a 100644 --- a/packages/next/src/telemetry/project-id.ts +++ b/packages/next/src/telemetry/project-id.ts @@ -18,6 +18,7 @@ function _getProjectIdByGit() { { timeout: 1000, stdio: `pipe`, + windowsHide: true, } ) diff --git a/packages/next/src/telemetry/storage.ts b/packages/next/src/telemetry/storage.ts index 3911178315e77..e5281f1969b4d 100644 --- a/packages/next/src/telemetry/storage.ts +++ b/packages/next/src/telemetry/storage.ts @@ -10,7 +10,9 @@ import { _postPayload } from './post-payload' import { getRawProjectId } from './project-id' import { AbortController } from 'next/dist/compiled/@edge-runtime/primitives/abort-controller' import fs from 'fs' -import spawn from 'next/dist/compiled/cross-spawn' +// Note: cross-spawn is not used here as it causes +// a new command window to appear when we don't want it to +import { spawn } from 'child_process' // This is the key that stores whether or not telemetry is enabled or disabled. const TELEMETRY_KEY_ENABLED = 'telemetry.enabled' @@ -245,8 +247,10 @@ export class Telemetry { JSON.stringify(allEvents) ) - spawn('node', [require.resolve('./deteched-flush'), mode, dir], { + spawn(process.execPath, [require.resolve('./deteched-flush'), mode, dir], { detached: !this.NEXT_TELEMETRY_DEBUG, + windowsHide: true, + shell: false, ...(this.NEXT_TELEMETRY_DEBUG ? { stdio: 'inherit', From 9b97461ba8ec998e73d83efd9e2dff510e9145b0 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 17:03:39 -0800 Subject: [PATCH 41/56] v13.1.3-canary.5 --- 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 ad33b01cadd82..84d9cd8a4b2cc 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.1.3-canary.4" + "version": "13.1.3-canary.5" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 02a479e160e3a..6c04c5c05aa38 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.3-canary.4", + "version": "13.1.3-canary.5", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 52a3802194830..102856dbe9586 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.3-canary.4", + "version": "13.1.3-canary.5", "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.3-canary.4", + "@next/eslint-plugin-next": "13.1.3-canary.5", "@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 4f006f4f37b02..55e03aa663a31 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.3-canary.4", + "version": "13.1.3-canary.5", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 6d57e8c6edb2c..689c2c4b6bda6 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.1.3-canary.4", + "version": "13.1.3-canary.5", "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 c207b513b298e..3b9d4d47fc517 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.3-canary.4", + "version": "13.1.3-canary.5", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 028ebeb256c4a..1e83dc85bed26 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.1.3-canary.4", + "version": "13.1.3-canary.5", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index e8dd37a133b2a..17ef803c7ba10 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.1.3-canary.4", + "version": "13.1.3-canary.5", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index f8171120642a6..f2bae2d557089 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.1.3-canary.4", + "version": "13.1.3-canary.5", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index c5a26ddeb2e6e..8ae7db44c7a74 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.3-canary.4", + "version": "13.1.3-canary.5", "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 d891aabfdca1c..d3e6d070ad1b3 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.3-canary.4", + "version": "13.1.3-canary.5", "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 c5fffe7d53807..1870cca079da7 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.3-canary.4", + "version": "13.1.3-canary.5", "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 ff7ce92602eef..39d5268512948 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.1.3-canary.4", + "version": "13.1.3-canary.5", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin,rustls-tls --js false native", diff --git a/packages/next/package.json b/packages/next/package.json index b3e0a08650b35..7eefd9f820d14 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.1.3-canary.4", + "version": "13.1.3-canary.5", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -77,7 +77,7 @@ ] }, "dependencies": { - "@next/env": "13.1.3-canary.4", + "@next/env": "13.1.3-canary.5", "@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.3-canary.4", - "@next/polyfill-nomodule": "13.1.3-canary.4", - "@next/react-dev-overlay": "13.1.3-canary.4", - "@next/react-refresh-utils": "13.1.3-canary.4", - "@next/swc": "13.1.3-canary.4", + "@next/polyfill-module": "13.1.3-canary.5", + "@next/polyfill-nomodule": "13.1.3-canary.5", + "@next/react-dev-overlay": "13.1.3-canary.5", + "@next/react-refresh-utils": "13.1.3-canary.5", + "@next/swc": "13.1.3-canary.5", "@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 0e6db061fa0b5..c3f137550f146 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.3-canary.4", + "version": "13.1.3-canary.5", "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 146207451d65f..57b9f1d94cbd6 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.3-canary.4", + "version": "13.1.3-canary.5", "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 16f4881fb2534..16cbf755a173d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -442,7 +442,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.1.3-canary.4 + '@next/eslint-plugin-next': 13.1.3-canary.5 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -514,12 +514,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.13.3 '@napi-rs/triples': 1.1.0 - '@next/env': 13.1.3-canary.4 - '@next/polyfill-module': 13.1.3-canary.4 - '@next/polyfill-nomodule': 13.1.3-canary.4 - '@next/react-dev-overlay': 13.1.3-canary.4 - '@next/react-refresh-utils': 13.1.3-canary.4 - '@next/swc': 13.1.3-canary.4 + '@next/env': 13.1.3-canary.5 + '@next/polyfill-module': 13.1.3-canary.5 + '@next/polyfill-nomodule': 13.1.3-canary.5 + '@next/react-dev-overlay': 13.1.3-canary.5 + '@next/react-refresh-utils': 13.1.3-canary.5 + '@next/swc': 13.1.3-canary.5 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 From a4d9373bd0e6942c5228bab53a892811b9121687 Mon Sep 17 00:00:00 2001 From: Rinku Chaudhari <76877078+therealrinku@users.noreply.github.com> Date: Thu, 19 Jan 2023 09:22:44 +0545 Subject: [PATCH 42/56] Remove useless ts-ignores (#45025) --- test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.js | 1 - .../fixtures/font-override-size-adjust/pages/_document.js | 1 - .../font-optimization/fixtures/font-override/pages/_document.js | 1 - .../font-optimization/fixtures/with-google/pages/_document.js | 1 - .../font-optimization/fixtures/with-typekit/pages/_document.js | 1 - test/integration/script-loader/base/pages/_document.js | 1 - 6 files changed, 6 deletions(-) diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.js b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.js index ade6718153a2a..714616632e967 100644 --- a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.js +++ b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.js @@ -1,4 +1,3 @@ -// @ts-ignore import { use } from 'react' const fetchCategory = async (categorySlug) => { diff --git a/test/integration/font-optimization/fixtures/font-override-size-adjust/pages/_document.js b/test/integration/font-optimization/fixtures/font-override-size-adjust/pages/_document.js index 27416216d6245..09f77550903af 100644 --- a/test/integration/font-optimization/fixtures/font-override-size-adjust/pages/_document.js +++ b/test/integration/font-optimization/fixtures/font-override-size-adjust/pages/_document.js @@ -1,5 +1,4 @@ import * as React from 'react' -/// @ts-ignore import Document, { Main, NextScript, Head, Html } from 'next/document' export default class MyDocument extends Document { render() { diff --git a/test/integration/font-optimization/fixtures/font-override/pages/_document.js b/test/integration/font-optimization/fixtures/font-override/pages/_document.js index 27416216d6245..09f77550903af 100644 --- a/test/integration/font-optimization/fixtures/font-override/pages/_document.js +++ b/test/integration/font-optimization/fixtures/font-override/pages/_document.js @@ -1,5 +1,4 @@ import * as React from 'react' -/// @ts-ignore import Document, { Main, NextScript, Head, Html } from 'next/document' export default class MyDocument extends Document { render() { diff --git a/test/integration/font-optimization/fixtures/with-google/pages/_document.js b/test/integration/font-optimization/fixtures/with-google/pages/_document.js index c17ca02e11f39..f8faa6f866a19 100644 --- a/test/integration/font-optimization/fixtures/with-google/pages/_document.js +++ b/test/integration/font-optimization/fixtures/with-google/pages/_document.js @@ -1,5 +1,4 @@ import * as React from 'react' -/// @ts-ignore import Document, { Main, NextScript, Head, Html } from 'next/document' export default class MyDocument extends Document { constructor(props) { diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js b/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js index 1462161f612ed..1aa2f2c2f64cc 100644 --- a/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js @@ -1,5 +1,4 @@ import * as React from 'react' -/// @ts-ignore import Document, { Main, NextScript, Head, Html } from 'next/document' export default class MyDocument extends Document { diff --git a/test/integration/script-loader/base/pages/_document.js b/test/integration/script-loader/base/pages/_document.js index fcd56e2a7d6f8..8f62e1f190aee 100644 --- a/test/integration/script-loader/base/pages/_document.js +++ b/test/integration/script-loader/base/pages/_document.js @@ -1,5 +1,4 @@ import * as React from 'react' -/// @ts-ignore import { Main, NextScript, Head, Html } from 'next/document' import Script from 'next/script' From 98a2448e5e71351f5cdc85af69107b44dd3bbc7d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 18 Jan 2023 20:17:11 -0800 Subject: [PATCH 43/56] Fix release stats (#45027) --- .../next-stats-action/src/constants.js | 4 +- .../src/prepare/repo-setup.js | 151 +++++++++++++++--- 2 files changed, 133 insertions(+), 22 deletions(-) diff --git a/.github/actions/next-stats-action/src/constants.js b/.github/actions/next-stats-action/src/constants.js index 2bac325eeea77..625624a18986d 100644 --- a/.github/actions/next-stats-action/src/constants.js +++ b/.github/actions/next-stats-action/src/constants.js @@ -5,8 +5,8 @@ const benchTitle = 'Page Load Tests' const workDir = path.join(os.tmpdir(), 'next-stats') const mainRepoName = 'main-repo' const diffRepoName = 'diff-repo' -const mainRepoDir = path.join(os.tmpdir(), mainRepoName) -const diffRepoDir = path.join(os.tmpdir(), diffRepoName) +const mainRepoDir = path.join(workDir, mainRepoName) +const diffRepoDir = path.join(workDir, diffRepoName) const statsAppDir = path.join(workDir, 'stats-app') const diffingDir = path.join(workDir, 'diff') const yarnEnvValues = { diff --git a/.github/actions/next-stats-action/src/prepare/repo-setup.js b/.github/actions/next-stats-action/src/prepare/repo-setup.js index 15f1abf814725..0703dbdee13a0 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -55,27 +55,138 @@ module.exports = (actionInfo) => { } }, async linkPackages({ repoDir, nextSwcVersion }) { - execa.sync('pnpm', ['turbo', 'run', 'test-pack'], { - cwd: repoDir, - env: { NEXT_SWC_VERSION: nextSwcVersion }, - }) - - const pkgPaths = new Map() - const pkgs = await fs.readdir(path.join(repoDir, 'packages')) - - pkgs.forEach((pkgDirname) => { - const { name } = require(path.join( - repoDir, - 'packages', - pkgDirname, - 'package.json' - )) - pkgPaths.set( - name, - path.join(repoDir, 'packages', pkgDirname, `packed-${pkgDirname}.tgz`) + let hasTestPack = false + + try { + hasTestPack = Boolean( + ((await fs.readJSON(path.join(repoDir, 'package.json'))).scripts || + {})['test-pack'] ) - }) - return pkgPaths + } catch (err) { + console.error(err) + } + + if (hasTestPack) { + execa.sync('pnpm', ['turbo', 'run', 'test-pack'], { + cwd: repoDir, + env: { NEXT_SWC_VERSION: nextSwcVersion }, + }) + + const pkgPaths = new Map() + const pkgs = await fs.readdir(path.join(repoDir, 'packages')) + + pkgs.forEach((pkgDirname) => { + const { name } = require(path.join( + repoDir, + 'packages', + pkgDirname, + 'package.json' + )) + pkgPaths.set( + name, + path.join( + repoDir, + 'packages', + pkgDirname, + `packed-${pkgDirname}.tgz` + ) + ) + }) + return pkgPaths + } else { + // TODO: remove after next stable release (current v13.1.2) + const pkgPaths = new Map() + const pkgDatas = new Map() + let pkgs + + try { + pkgs = await fs.readdir(path.join(repoDir, 'packages')) + } catch (err) { + if (err.code === 'ENOENT') { + require('console').log('no packages to link') + return pkgPaths + } + throw err + } + + for (const pkg of pkgs) { + const pkgPath = path.join(repoDir, 'packages', pkg) + const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`) + + const pkgDataPath = path.join(pkgPath, 'package.json') + if (!fs.existsSync(pkgDataPath)) { + require('console').log(`Skipping ${pkgDataPath}`) + continue + } + const pkgData = require(pkgDataPath) + const { name } = pkgData + pkgDatas.set(name, { + pkgDataPath, + pkg, + pkgPath, + pkgData, + packedPkgPath, + }) + pkgPaths.set(name, packedPkgPath) + } + + for (const pkg of pkgDatas.keys()) { + const { pkgDataPath, pkgData } = pkgDatas.get(pkg) + + for (const pkg of pkgDatas.keys()) { + const { packedPkgPath } = pkgDatas.get(pkg) + if (!pkgData.dependencies || !pkgData.dependencies[pkg]) continue + pkgData.dependencies[pkg] = packedPkgPath + } + + // make sure native binaries are included in local linking + if (pkg === '@next/swc') { + if (!pkgData.files) { + pkgData.files = [] + } + pkgData.files.push('native') + require('console').log( + 'using swc binaries: ', + await exec(`ls ${path.join(path.dirname(pkgDataPath), 'native')}`) + ) + } + + if (pkg === 'next') { + if (nextSwcVersion) { + Object.assign(pkgData.dependencies, { + '@next/swc-linux-x64-gnu': nextSwcVersion, + }) + } else { + if (pkgDatas.get('@next/swc')) { + pkgData.dependencies['@next/swc'] = + pkgDatas.get('@next/swc').packedPkgPath + } else { + pkgData.files.push('native') + } + } + } + + await fs.writeFile( + pkgDataPath, + JSON.stringify(pkgData, null, 2), + 'utf8' + ) + } + + // wait to pack packages until after dependency paths have been updated + // to the correct versions + await Promise.all( + Array.from(pkgDatas.keys()).map(async (pkgName) => { + const { pkg, pkgPath } = pkgDatas.get(pkgName) + await exec( + `cd ${pkgPath} && yarn pack -f '${pkg}-packed.tgz'`, + true + ) + }) + ) + + return pkgPaths + } }, } } From 06ed494480f5ef55f15407dc819f97b45ba93cbb Mon Sep 17 00:00:00 2001 From: Adil Ansari Date: Wed, 18 Jan 2023 20:44:18 -0800 Subject: [PATCH 44/56] refactor: Latest Tigris sdk and example using decorators (#44256) --- .eslintignore | 3 ++- examples/with-tigris/.env.local.example | 7 +++++ examples/with-tigris/README.md | 19 +++++-------- examples/with-tigris/components/EachToDo.tsx | 2 +- examples/with-tigris/db/models/todoItems.ts | 19 +++++++++++++ examples/with-tigris/lib/tigris.ts | 27 +++---------------- .../models/tigris/todoStarterApp/todoItems.ts | 22 --------------- examples/with-tigris/package.json | 7 ++--- examples/with-tigris/pages/api/item/[id].ts | 16 +++++------ examples/with-tigris/pages/api/items/index.ts | 9 +++---- .../with-tigris/pages/api/items/search.ts | 15 ++++------- examples/with-tigris/pages/index.tsx | 2 +- examples/with-tigris/scripts/setup.ts | 18 +++++-------- examples/with-tigris/tsconfig.json | 2 ++ 14 files changed, 67 insertions(+), 101 deletions(-) create mode 100644 examples/with-tigris/db/models/todoItems.ts delete mode 100644 examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts diff --git a/.eslintignore b/.eslintignore index e06abff35d2df..d3507e8b96a9d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,6 +11,7 @@ examples/with-flow/** examples/with-jest/** examples/with-mobx-state-tree/** examples/with-mobx/** +examples/with-tigris/db/models/todoItems.ts packages/next/src/bundles/webpack/packages/*.runtime.js packages/next/src/bundles/webpack/packages/lazy-compilation-*.js packages/next/src/compiled/**/* @@ -36,4 +37,4 @@ bench/nested-deps/pages/** bench/nested-deps/components/** packages/next-bundle-analyzer/index.d.ts examples/with-typescript-graphql/lib/gql/ -test/development/basic/hmr/components/parse-error.js \ No newline at end of file +test/development/basic/hmr/components/parse-error.js diff --git a/examples/with-tigris/.env.local.example b/examples/with-tigris/.env.local.example index d020d0b248581..cfbf4f48d4043 100644 --- a/examples/with-tigris/.env.local.example +++ b/examples/with-tigris/.env.local.example @@ -1,7 +1,14 @@ # Enter your tigris uri, ex :- localhost:8081, api.preview.tigrisdata.cloud etc. TIGRIS_URI= +# Name of your project +TIGRIS_PROJECT= + # Client credentials, if using auth, can be generated from Tigris cloud console. # See: https://docs.tigrisdata.com/auth TIGRIS_CLIENT_ID= TIGRIS_CLIENT_SECRET= + +# Optionally specify a database branch, ex :- "staging" or "${VERCEL_GIT_COMMIT_REF}" to dynamically +# create branches for preview deployments (database branch name will match the branch name on github) +TIGRIS_DB_BRANCH= diff --git a/examples/with-tigris/README.md b/examples/with-tigris/README.md index 58d9abdd15f4f..1289152474de0 100644 --- a/examples/with-tigris/README.md +++ b/examples/with-tigris/README.md @@ -60,7 +60,7 @@ tigris dev start npm run dev ``` -> Note: This step uses a custom dev & build script to initialize Tigris database and collection for +> Note: This step uses a custom dev & build script to initialize Tigris collections for > the app and requires [ts-node](https://www.npmjs.com/package/ts-node#installation) to be installed. :tada: All done. You should be able to use app on `localhost:3000` in browser. Feel free to play @@ -95,10 +95,9 @@ Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&ut ├── package.json ├── lib │ ├── tigris.ts -├── models -│ └── tigris -│ └── todoStarterApp -│ └── todoItems.ts +├── db +│ └── models +│ └── todoItems.ts └── pages ├── index.tsx └── api @@ -114,13 +113,9 @@ Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&ut
🪢 Tigris schema definition -[models/tigris/todoStarterApp/todoItems.ts](models/tigris/todoStarterApp/todoItems.ts) - The to-do list app -has a single collection `todoItems` that stores the to-do items in `todoStarterApp` database. The -Database and Collection get automatically provisioned by the [setup script](scripts/setup.ts). - -This is an inspiration from Next.js based file system router. Create a folder or drop a schema file -inside database folder under `models/tigris/`, and you're able to instantly create Databases and -Collections in Tigris for your application. +[db/models/todoItems.ts](db/models/todoItems.ts) - The to-do list app has a single collection +`todoItems` that stores the to-do items. The Collection gets automatically provisioned by the +[setup script](scripts/setup.ts).
diff --git a/examples/with-tigris/components/EachToDo.tsx b/examples/with-tigris/components/EachToDo.tsx index 2409a68f4a5f6..d2ac863cb6e65 100644 --- a/examples/with-tigris/components/EachToDo.tsx +++ b/examples/with-tigris/components/EachToDo.tsx @@ -1,6 +1,6 @@ import Image from 'next/image' import React from 'react' -import { TodoItem } from '../models/tigris/todoStarterApp/todoItems' +import { TodoItem } from '../db/models/todoItems' import styles from '../styles/EachToDo.module.css' type Props = { diff --git a/examples/with-tigris/db/models/todoItems.ts b/examples/with-tigris/db/models/todoItems.ts new file mode 100644 index 0000000000000..5d75bbe98b87a --- /dev/null +++ b/examples/with-tigris/db/models/todoItems.ts @@ -0,0 +1,19 @@ +import { + Field, + PrimaryKey, + TigrisCollection, + TigrisCollectionType, + TigrisDataTypes, +} from '@tigrisdata/core' + +@TigrisCollection('todoItems') +export class TodoItem implements TigrisCollectionType { + @PrimaryKey(TigrisDataTypes.INT32, { order: 1, autoGenerate: true }) + id!: number + + @Field() + text!: string + + @Field() + completed!: boolean +} diff --git a/examples/with-tigris/lib/tigris.ts b/examples/with-tigris/lib/tigris.ts index 365739f87642b..9627335d71ee9 100644 --- a/examples/with-tigris/lib/tigris.ts +++ b/examples/with-tigris/lib/tigris.ts @@ -1,28 +1,7 @@ -import { DB, Tigris } from '@tigrisdata/core' +import { Tigris } from '@tigrisdata/core' -const DB_NAME = 'todoStarterApp' - -declare global { - // eslint-disable-next-line no-var - var tigrisDb: DB -} - -let tigrisDb: DB - -// Caching the client because `next dev` would otherwise create a -// new connection on every file save while previous connection is active due to -// hot reloading. However, in production, Next.js would completely tear down before -// restarting, thus, disconnecting and reconnecting to Tigris. -if (process.env.NODE_ENV !== 'production') { - if (!global.tigrisDb) { - const tigrisClient = new Tigris() - global.tigrisDb = tigrisClient.getDatabase(DB_NAME) - } - tigrisDb = global.tigrisDb -} else { - const tigrisClient = new Tigris() - tigrisDb = tigrisClient.getDatabase(DB_NAME) -} +const tigrisClient = new Tigris() +const tigrisDb = tigrisClient.getDatabase() // export to share DB across modules export default tigrisDb diff --git a/examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts b/examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts deleted file mode 100644 index 9953fe98f6ec0..0000000000000 --- a/examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - TigrisCollectionType, - TigrisDataTypes, - TigrisSchema, -} from '@tigrisdata/core/dist/types' - -export const COLLECTION_NAME = 'todoItems' - -export interface TodoItem extends TigrisCollectionType { - id?: number - text: string - completed: boolean -} - -export const TodoItemSchema: TigrisSchema = { - id: { - type: TigrisDataTypes.INT32, - primary_key: { order: 1, autoGenerate: true }, - }, - text: { type: TigrisDataTypes.STRING }, - completed: { type: TigrisDataTypes.BOOLEAN }, -} diff --git a/examples/with-tigris/package.json b/examples/with-tigris/package.json index 5d06c9bc0a90b..acf04ea81f10f 100644 --- a/examples/with-tigris/package.json +++ b/examples/with-tigris/package.json @@ -1,10 +1,10 @@ { "private": true, "scripts": { - "predev": "APP_ENV=development npm run setup", + "predev": "npm run setup", "dev": "next dev", "build": "next build", - "postbuild": "APP_ENV=production npm run setup", + "postbuild": "npm run setup", "start": "next start", "setup": "npx ts-node scripts/setup.ts" }, @@ -12,7 +12,8 @@ "@tigrisdata/core": "latest", "next": "latest", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "reflect-metadata": "0.1.13" }, "devDependencies": { "@types/node": "18.11.2", diff --git a/examples/with-tigris/pages/api/item/[id].ts b/examples/with-tigris/pages/api/item/[id].ts index 6e5901053cc36..2c7558ecd3e48 100644 --- a/examples/with-tigris/pages/api/item/[id].ts +++ b/examples/with-tigris/pages/api/item/[id].ts @@ -1,8 +1,5 @@ import { NextApiRequest, NextApiResponse } from 'next' -import { - COLLECTION_NAME, - TodoItem, -} from '../../../models/tigris/todoStarterApp/todoItems' +import { TodoItem } from '../../../db/models/todoItems' import tigrisDb from '../../../lib/tigris' type Data = { @@ -16,8 +13,8 @@ async function handleGet( itemId: number ) { try { - const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME) - const item = await itemsCollection.findOne({ id: itemId }) + const itemsCollection = tigrisDb.getCollection(TodoItem) + const item = await itemsCollection.findOne({ filter: { id: itemId } }) if (!item) { res.status(404).json({ error: 'No item found' }) } else { @@ -32,7 +29,7 @@ async function handleGet( async function handlePut(req: NextApiRequest, res: NextApiResponse) { try { const item = JSON.parse(req.body) as TodoItem - const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME) + const itemsCollection = tigrisDb.getCollection(TodoItem) const updated = await itemsCollection.insertOrReplaceOne(item) res.status(200).json({ result: updated }) } catch (err) { @@ -47,8 +44,9 @@ async function handleDelete( itemId: number ) { try { - const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME) - const status = (await itemsCollection.deleteOne({ id: itemId })).status + const itemsCollection = tigrisDb.getCollection(TodoItem) + const status = (await itemsCollection.deleteOne({ filter: { id: itemId } })) + .status if (status === 'deleted') { res.status(200).json({}) } else { diff --git a/examples/with-tigris/pages/api/items/index.ts b/examples/with-tigris/pages/api/items/index.ts index 7f65b578246dc..2fb7678b0f26c 100644 --- a/examples/with-tigris/pages/api/items/index.ts +++ b/examples/with-tigris/pages/api/items/index.ts @@ -1,8 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next' -import { - COLLECTION_NAME, - TodoItem, -} from '../../../models/tigris/todoStarterApp/todoItems' +import { TodoItem } from '../../../db/models/todoItems' import tigrisDb from '../../../lib/tigris' type Response = { @@ -12,7 +9,7 @@ type Response = { async function handleGet(req: NextApiRequest, res: NextApiResponse) { try { - const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME) + const itemsCollection = tigrisDb.getCollection(TodoItem) const cursor = itemsCollection.findMany() const items = await cursor.toArray() res.status(200).json({ result: items }) @@ -25,7 +22,7 @@ async function handleGet(req: NextApiRequest, res: NextApiResponse) { async function handlePost(req: NextApiRequest, res: NextApiResponse) { try { const item = JSON.parse(req.body) as TodoItem - const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME) + const itemsCollection = tigrisDb.getCollection(TodoItem) const inserted = await itemsCollection.insertOne(item) res.status(200).json({ result: [inserted] }) } catch (err) { diff --git a/examples/with-tigris/pages/api/items/search.ts b/examples/with-tigris/pages/api/items/search.ts index e4e2dd0115df0..fe661a7eda56f 100644 --- a/examples/with-tigris/pages/api/items/search.ts +++ b/examples/with-tigris/pages/api/items/search.ts @@ -1,9 +1,5 @@ import { NextApiRequest, NextApiResponse } from 'next' -import { - COLLECTION_NAME, - TodoItem, -} from '../../../models/tigris/todoStarterApp/todoItems' -import { SearchRequest } from '@tigrisdata/core/dist/search/types' +import { TodoItem } from '../../../db/models/todoItems' import tigrisDb from '../../../lib/tigris' type Data = { @@ -22,12 +18,11 @@ export default async function handler( return } try { - const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME) - const searchRequest: SearchRequest = { q: query as string } - const searchResult = await itemsCollection.search(searchRequest) + const itemsCollection = tigrisDb.getCollection(TodoItem) + const searchResult = await itemsCollection.search({ q: query as string }) const items = new Array() - for (const hit of searchResult.hits) { - items.push(hit.document) + for await (const res of searchResult) { + res.hits.forEach((hit) => items.push(hit.document)) } res.status(200).json({ result: items }) } catch (err) { diff --git a/examples/with-tigris/pages/index.tsx b/examples/with-tigris/pages/index.tsx index 689fbc9bcb45c..47950a38984b1 100644 --- a/examples/with-tigris/pages/index.tsx +++ b/examples/with-tigris/pages/index.tsx @@ -4,7 +4,7 @@ import Head from 'next/head' import React, { useEffect, useState } from 'react' import EachTodo from '../components/EachToDo' import LoaderWave from '../components/LoaderWave' -import { TodoItem } from '../models/tigris/todoStarterApp/todoItems' +import { TodoItem } from '../db/models/todoItems' import styles from '../styles/Home.module.css' const Home: NextPage = () => { diff --git a/examples/with-tigris/scripts/setup.ts b/examples/with-tigris/scripts/setup.ts index 42ad6b69d308a..0b159a9bd9680 100644 --- a/examples/with-tigris/scripts/setup.ts +++ b/examples/with-tigris/scripts/setup.ts @@ -1,19 +1,13 @@ import { Tigris } from '@tigrisdata/core' -import { loadEnvConfig } from '@next/env' - -// Run the config loader only when not executing within next runtime -if (process.env.NODE_ENV === undefined) { - if (process.env.APP_ENV === 'development') { - loadEnvConfig(process.cwd(), true) - } else if (process.env.APP_ENV === 'production') { - loadEnvConfig(process.cwd()) - } -} +import { TodoItem } from '../db/models/todoItems' async function main() { - // setup client and register schemas + // setup client const tigrisClient = new Tigris() - await tigrisClient.registerSchemas('models/tigris') + // ensure branch exists, create it if it needs to be created dynamically + await tigrisClient.getDatabase().initializeBranch() + // register schemas + await tigrisClient.registerSchemas([TodoItem]) } main() diff --git a/examples/with-tigris/tsconfig.json b/examples/with-tigris/tsconfig.json index 7b7e1fbbfe224..39a6d4ac96ff2 100644 --- a/examples/with-tigris/tsconfig.json +++ b/examples/with-tigris/tsconfig.json @@ -10,6 +10,8 @@ "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", From d363e6f2b3b8daa616233d3df71354f9c76c8ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 19 Jan 2023 13:12:36 +0100 Subject: [PATCH 45/56] Don't dispaly "Error: " twice when router isn't mounted (#45038) Before ![image](https://user-images.githubusercontent.com/25056922/213422675-d2865012-ea23-4ee7-b984-4021e698bce0.png) After ![image](https://user-images.githubusercontent.com/25056922/213422561-5730efea-82da-4e13-aac6-a1e84b098605.png) Fixes NEXT-381 ## 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/client/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/client/router.ts b/packages/next/src/client/router.ts index 7f4acbd51a60c..930535fb1de3b 100644 --- a/packages/next/src/client/router.ts +++ b/packages/next/src/client/router.ts @@ -133,7 +133,7 @@ export function useRouter(): NextRouter { const router = React.useContext(RouterContext) if (!router) { throw new Error( - 'Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted' + 'NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted' ) } From 9b39c79d2c41fa3cbd3e4f6f22815389f553fd5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Thu, 19 Jan 2023 13:45:34 +0100 Subject: [PATCH 46/56] Display the reason of hydration error in error overlay (#44857) --- packages/next/src/client/app-index.tsx | 11 + .../internal/helpers/hydration-error-info.ts | 30 + .../internal/helpers/use-error-handler.ts | 7 +- .../acceptance-app/ReactRefreshLogBox.test.ts | 20 - .../ReactRefreshLogBox.test.ts.snap | 6 - .../acceptance-app/hydration-error.test.ts | 1269 +++++++++++++++++ 6 files changed, 1316 insertions(+), 27 deletions(-) create mode 100644 packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts create mode 100644 test/development/acceptance-app/hydration-error.test.ts diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index 09d024418ac13..9a8a45e8dea5c 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -254,6 +254,17 @@ export function hydrate() { onRecoverableError, } const isError = document.documentElement.id === '__next_error__' + + if (process.env.NODE_ENV !== 'production') { + // Patch console.error to collect information about hydration errors + const patchConsoleError = + require('./components/react-dev-overlay/internal/helpers/hydration-error-info') + .patchConsoleError as typeof import('./components/react-dev-overlay/internal/helpers/hydration-error-info').patchConsoleError + if (!isError) { + patchConsoleError() + } + } + const reactRoot = isError ? (ReactDOMClient as any).createRoot(appElement, options) : (React as any).startTransition(() => diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts new file mode 100644 index 0000000000000..af2e1c9b66ce6 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts @@ -0,0 +1,30 @@ +export let hydrationErrorInfo: string | undefined + +const knownHydrationWarnings = new Set([ + 'Warning: Text content did not match. Server: "%s" Client: "%s"%s', + 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s', + 'Warning: Expected server HTML to contain a matching text node for "%s" in <%s>.%s', + 'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s', + 'Warning: Did not expect server HTML to contain the text node "%s" in <%s>.%s', +]) + +export function patchConsoleError() { + const prev = console.error + console.error = function ( + msg, + serverContent, + clientContent, + // TODO-APP: Display the component stack in the overlay + _componentStack + ) { + if (knownHydrationWarnings.has(msg)) { + hydrationErrorInfo = msg + .replace('%s', serverContent) + .replace('%s', clientContent) + .replace('%s', '') + } + + // @ts-expect-error argument is defined + prev.apply(console, arguments) + } +} diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts index 8e97f46bc8c7f..97c6087eb2bfd 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts @@ -1,4 +1,5 @@ import { useEffect } from 'react' +import { hydrationErrorInfo } from './hydration-error-info' export type ErrorHandler = (error: Error) => void @@ -60,7 +61,11 @@ if (typeof window !== 'undefined') { 'https://nextjs.org/docs/messages/react-hydration-error' ) ) { - error.message += `\n\nSee more info here: https://nextjs.org/docs/messages/react-hydration-error` + if (hydrationErrorInfo) { + error.message += '\n\n' + hydrationErrorInfo + } + error.message += + '\n\nSee more info here: https://nextjs.org/docs/messages/react-hydration-error' } const e = error diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts index 786af5d163eae..b4d8159bad91d 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts @@ -1346,26 +1346,6 @@ for (const variant of ['default', 'turbo']) { await cleanup() }) - test('Hydration errors should get error link', async () => { - const { session, browser, cleanup } = await sandbox(next) - - await session.patch( - 'app/page.js', - ` - "use client" - export default function Page() { - return

{typeof window === 'undefined' ? "hello" : "world"}

- } - ` - ) - - await browser.refresh() - await session.waitForAndOpenRuntimeError() - expect(await session.getRedboxDescription()).toMatchSnapshot() - - await cleanup() - }) - test('Import trace when module not found in layout', async () => { const { session, cleanup } = await sandbox( next, diff --git a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap index 036394b5d7fbe..bd932ab8394e9 100644 --- a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap +++ b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap @@ -1,11 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ReactRefreshLogBox app default Hydration errors should get error link 1`] = ` -"Error: Text content does not match server-rendered HTML. - -See more info here: https://nextjs.org/docs/messages/react-hydration-error" -`; - exports[`ReactRefreshLogBox app default Import trace when module not found in layout 1`] = ` "./app/module.js:1:0 Module not found: Can't resolve 'non-existing-module' diff --git a/test/development/acceptance-app/hydration-error.test.ts b/test/development/acceptance-app/hydration-error.test.ts new file mode 100644 index 0000000000000..b475255a12625 --- /dev/null +++ b/test/development/acceptance-app/hydration-error.test.ts @@ -0,0 +1,1269 @@ +/* eslint-env jest */ +import { sandbox } from './helpers' +import { createNextDescribe, FileRef } from 'e2e-utils' +import path from 'path' + +// https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js +createNextDescribe( + 'Error Overlay for server components', + { + files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')), + dependencies: { + react: 'latest', + 'react-dom': 'latest', + }, + skipStart: true, + }, + ({ next }) => { + it('should show correct hydration errror when client and server render different text', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
{isClient ? "client" : "server"}
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Text content does not match server-rendered HTML. + + Warning: Text content did not match. Server: \\"server\\" Client: \\"client\\" + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client and server render different html', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
client" + : "server", + }} + /> +
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client and server render different attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client renders extra attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when server renders extra attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when both client and server render extra attributes', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + // TODO: does not throw error, throw to show warning in overlay or remove this test + it.skip('should show correct hydration errror when client and server render different styles', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot() + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element as only child', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+ {isClient &&
} +
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching
in
. + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element in the beginning', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+ {isClient &&
} +
+
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching
in
. + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element in the middle', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+ {isClient &&
} +
+
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching
in
. + + See more info here: https://nextjs.org/docs/messages/react-hydration-error" + `) + + await cleanup() + }) + it('should show correct hydration errror when client renders an extra element in the end', async () => { + const { cleanup, session } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` + 'use client' + const isClient = typeof window !== 'undefined' + export default function Mismatch() { + return ( +
+
+
+ {isClient &&
} +
+ ); + } +`, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + expect(await session.getRedboxDescription()).toMatchInlineSnapshot(` + "Error: Hydration failed because the initial UI does not match what was rendered on the server. + + Warning: Expected server HTML to contain a matching