From df76f6633fe192e04b88ace62a00ccc9bb833d86 Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Mon, 11 Sep 2023 14:23:02 +0200 Subject: [PATCH 01/16] server: remove turbopack-specific code when compiling with webpack (#55226) This PR tweaks the webpack config so that when we compile user code with webpack, we remove all branches of code that are guarded with `process.env.TURBOPACK`. I noticed that the bundle runtime was not tree-shaken correctly, this fixes that. --- packages/next/src/build/webpack-config.ts | 1 + packages/next/webpack.config.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index d1b7487618fe2..dbc805edaf1fd 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -371,6 +371,7 @@ export function getDefineEnv({ 'global.GENTLY': JSON.stringify(false), } : undefined), + 'process.env.TURBOPACK': JSON.stringify(false), } } diff --git a/packages/next/webpack.config.js b/packages/next/webpack.config.js index 8e7c3063fa5ed..83fe9d3009877 100644 --- a/packages/next/webpack.config.js +++ b/packages/next/webpack.config.js @@ -131,6 +131,7 @@ module.exports = ({ dev, turbo }) => { dev ? 'development' : 'production' ), 'process.env.NEXT_RUNTIME': JSON.stringify('nodejs'), + ...(!dev ? { 'process.env.TURBOPACK': JSON.stringify(turbo) } : {}), }), !!process.env.ANALYZE && new BundleAnalyzerPlugin({ From 31f28287bf1cb420878786f90fab915b9348d75f Mon Sep 17 00:00:00 2001 From: Mayank Date: Mon, 11 Sep 2023 19:26:43 +0530 Subject: [PATCH 02/16] fix: upgrade recoil from 0.7.6 to 0.7.7 (#55222) upgrade recoil from 0.7.6 to 0.7.7 Co-authored-by: snyk-bot Co-authored-by: Steven --- examples/with-recoil/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-recoil/package.json b/examples/with-recoil/package.json index 1f6a55ac55acd..17f3a92cfd5b1 100644 --- a/examples/with-recoil/package.json +++ b/examples/with-recoil/package.json @@ -9,6 +9,6 @@ "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0", - "recoil": "0.7.6" + "recoil": "0.7.7" } } From 9d529e525fa4beffa4152fccdd8f5dae20b9135e Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Mon, 11 Sep 2023 08:59:27 -0500 Subject: [PATCH 03/16] docs: satisfies follow up (#55234) Follow up from https://github.com/vercel/next.js/pull/55205/files#r1321306801. --- .../03-data-fetching/02-get-static-paths.mdx | 2 +- .../03-pages/02-api-reference/02-functions/get-static-paths.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/03-pages/01-building-your-application/03-data-fetching/02-get-static-paths.mdx b/docs/03-pages/01-building-your-application/03-data-fetching/02-get-static-paths.mdx index 785682a4da9ea..4d00624c3f98c 100644 --- a/docs/03-pages/01-building-your-application/03-data-fetching/02-get-static-paths.mdx +++ b/docs/03-pages/01-building-your-application/03-data-fetching/02-get-static-paths.mdx @@ -30,7 +30,7 @@ export const getStaticPaths = (async () => { ], fallback: true, // false or "blocking" } -}) satisfies getStaticPaths +}) satisfies GetStaticPaths export const getStaticProps = (async (context) => { const res = await fetch('https://api.github.com/repos/vercel/next.js') diff --git a/docs/03-pages/02-api-reference/02-functions/get-static-paths.mdx b/docs/03-pages/02-api-reference/02-functions/get-static-paths.mdx index ae60b5cab4b49..0552c0b91b3b0 100644 --- a/docs/03-pages/02-api-reference/02-functions/get-static-paths.mdx +++ b/docs/03-pages/02-api-reference/02-functions/get-static-paths.mdx @@ -28,7 +28,7 @@ export const getStaticPaths = (async () => { ], fallback: true, // false or "blocking" } -}) satisfies getStaticPaths +}) satisfies GetStaticPaths export const getStaticProps = (async (context) => { const res = await fetch('https://api.github.com/repos/vercel/next.js') From 80654c1e30725c6b3c9385893cc662a11bf29c14 Mon Sep 17 00:00:00 2001 From: Mayank Date: Mon, 11 Sep 2023 19:32:20 +0530 Subject: [PATCH 04/16] chore(examples): upgrade `@keystone-next/keystone` (#55210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …ilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-PROMPTS-1729737 - https://snyk.io/vuln/SNYK-JS-SHELLQUOTE-1766506 - https://snyk.io/vuln/SNYK-JS-TAR-1579147 - https://snyk.io/vuln/SNYK-JS-TAR-1579152 - https://snyk.io/vuln/SNYK-JS-TAR-1579155 Co-authored-by: Snyk bot <19733683+snyk-bot@users.noreply.github.com> --- examples/cms-keystonejs-embedded/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cms-keystonejs-embedded/package.json b/examples/cms-keystonejs-embedded/package.json index a3d8d7933fe34..c8e06ef5bb60e 100644 --- a/examples/cms-keystonejs-embedded/package.json +++ b/examples/cms-keystonejs-embedded/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@keystone-next/fields": "^15.0.0", - "@keystone-next/keystone": "^18.0.0", + "@keystone-next/keystone": "^25.0.0", "next": "10.2.2", "react": "18.2.0", "react-dom": "18.2.0" From e1bc270830f2fc2df3542d4ef4c61b916c802df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20V=C4=83n=20D=C5=A9ng?= Date: Mon, 11 Sep 2023 21:14:39 +0700 Subject: [PATCH 05/16] Ensure `ImageResponse` extends `Response` (#55187) ### Why? The `ImageResponse` class constructor returns a `Response` instance, but doesn't extend `Response`, so it doesn't pass the type check introduced in #51394. ### How? Make the `ImageResponse` class a subclass of `Response`. ### Notes I can't find any tests to check for the types so didn't add any tests here; but shouldn't there be one? --- packages/next/src/server/web/spec-extension/image-response.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/web/spec-extension/image-response.ts b/packages/next/src/server/web/spec-extension/image-response.ts index d4d7c22a10846..bedf93ad0400d 100644 --- a/packages/next/src/server/web/spec-extension/image-response.ts +++ b/packages/next/src/server/web/spec-extension/image-response.ts @@ -1,4 +1,4 @@ -export class ImageResponse { +export class ImageResponse extends Response { public static displayName = 'NextImageResponse' constructor( ...args: ConstructorParameters< @@ -36,7 +36,7 @@ export class ImageResponse { const options = args[1] || {} - return new Response(readable, { + super(readable, { headers: { 'content-type': 'image/png', 'cache-control': From 40da1aeb9253402cf24ddfd7ca680de0e0a5c96b Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Mon, 11 Sep 2023 08:29:49 -0700 Subject: [PATCH 06/16] test(nexttestsetup): teardown nextinstance gracefully (#55144) ### What? https://vercel.slack.com/archives/C04KC8A53T7/p1694190462958199 I realized we throws an error attempt to destory against undefined instance when `beforeAll` fails in any reason. This is quite redundant error since if next instance doesn't exist, likely there's an upstream test failures already and destory against undefined error is not useful to debug anyway. PR replaces `nextTestSetup`'s teardown first, for the remaining places calling `next.destory` explicitly will be amended later. --- test/lib/e2e-utils.ts | 7 ++++++- test/lib/next-test-utils.ts | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/test/lib/e2e-utils.ts b/test/lib/e2e-utils.ts index 1d739f6de28c4..5de183b8b29c5 100644 --- a/test/lib/e2e-utils.ts +++ b/test/lib/e2e-utils.ts @@ -233,7 +233,12 @@ export function nextTestSetup( next = await createNext(options) }) afterAll(async () => { - await next.destroy() + // Gracefully destroy the instance if `createNext` success. + // If next instnace is not available, it's likely beforeAll hook failed and unnecessariliy throws another error + // by attempting to destroy on undefined. + if (next) { + await next.destroy() + } }) } diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts index 09c9736d8c552..5e28fd7c602a1 100644 --- a/test/lib/next-test-utils.ts +++ b/test/lib/next-test-utils.ts @@ -536,7 +536,9 @@ export async function killProcess(pid: number): Promise { // Kill a launched app export async function killApp(instance: ChildProcess) { - await killProcess(instance.pid) + if (instance && instance.pid) { + await killProcess(instance.pid) + } } export async function startApp(app: NextServer) { From 360142d4097dd525c57a1ad0f2c8879ba06c3816 Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Mon, 11 Sep 2023 08:44:39 -0700 Subject: [PATCH 07/16] tests(next-dev): migrate basic tailwind tests for turbopack (#55118) Closes WEB-1518 ### What? This PR refactors test cases from next-dev, tests basic tailwind rendering with turbopack. It still runs against plain next.js too. While checking, noticed there's a stub fixture (css-fixture/with-tailwindcss) but those fixture seems not being used in the test. Moved those and utilized. Since this test runs against next.js / turbopack both anyway, moving fixture should not be a huge regression I believe. --- .../next/tailwind/basic/input/next.config.js | 4 - .../next/tailwind/basic/input/pages/_app.tsx | 8 -- .../tailwind/basic/input/pages/api/hello.ts | 13 --- .../next/tailwind/basic/input/pages/index.jsx | 95 ------------------ .../tailwind/basic/input/postcss.config.js | 6 -- .../tailwind/basic/input/public/favicon.ico | Bin 15086 -> 0 bytes .../tailwind/basic/input/public/vercel.svg | 4 - .../tailwind/basic/input/styles/globals.css | 3 - .../tailwind/basic/input/tailwind.config.js | 12 --- .../next/tailwind/basic/input/tsconfig.json | 23 ----- ...tsSync(__q____q____q____star__0-55e40b.txt | 15 --- ...tsSync(__q____q____q____star__0-bbe67b.txt | 15 --- ...FileSync(__q____q____q____star_-50ea93.txt | 15 --- ...FileSync(__q____q____q____star_-a3cd71.txt | 15 --- ...solve(__q____q____q____star__0_-820338.txt | 17 ---- ...solve(__q____q____q____star__0_-c23a56.txt | 17 ---- test/development/basic/tailwind-jit.test.ts | 44 ++++---- .../with-tailwindcss/package.json | 7 -- .../with-tailwindcss/pages/_app.js | 5 - .../with-tailwindcss/pages/index.js | 3 - .../with-tailwindcss/styles/global.css | 3 - test/turbopack-tests-manifest.js | 2 +- 22 files changed, 23 insertions(+), 303 deletions(-) delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/next.config.js delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/_app.tsx delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/api/hello.ts delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/postcss.config.js delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/public/favicon.ico delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/public/vercel.svg delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tailwind.config.js delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tsconfig.json delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-55e40b.txt delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-bbe67b.txt delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-50ea93.txt delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-a3cd71.txt delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-820338.txt delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-c23a56.txt delete mode 100644 test/integration/css-fixtures/with-tailwindcss/package.json delete mode 100644 test/integration/css-fixtures/with-tailwindcss/pages/_app.js delete mode 100644 test/integration/css-fixtures/with-tailwindcss/pages/index.js delete mode 100644 test/integration/css-fixtures/with-tailwindcss/styles/global.css diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/next.config.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/next.config.js deleted file mode 100644 index 8b61df4e50f8a..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/next.config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -module.exports = { - reactStrictMode: true, -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/_app.tsx b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/_app.tsx deleted file mode 100644 index 3f5c9d5485860..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/_app.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import '../styles/globals.css' -import type { AppProps } from 'next/app' - -function MyApp({ Component, pageProps }: AppProps) { - return -} - -export default MyApp diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/api/hello.ts b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/api/hello.ts deleted file mode 100644 index f8bcc7e5caed1..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/api/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next' - -type Data = { - name: string -} - -export default function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx deleted file mode 100644 index 217eb8e63055e..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import Head from 'next/head' -import Image from 'next/image' -import { useTestHarness } from '@turbo/pack-test-harness' - -const Home = () => { - useTestHarness(runTests) - return ( - - ) -} - -export default Home - -function runTests() { - console.log(document.querySelectorAll('footer')) - it('it should apply tailwind styles', function () { - const footer = document.querySelector('footer') - expect(getComputedStyle(footer).alignItems).toBe('center') - }) -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/postcss.config.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/postcss.config.js deleted file mode 100644 index 33ad091d26d8a..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/public/favicon.ico b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/public/favicon.ico deleted file mode 100644 index 4965832f2c9b0605eaa189b7c7fb11124d24e48a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHOOH5Q(7(R0cc?bh2AT>N@1PWL!LLfZKyG5c!MTHoP7_p!sBz0k$?pjS;^lmgJ zU6^i~bWuZYHL)9$wuvEKm~qo~(5=Lvx5&Hv;?X#m}i|`yaGY4gX+&b>tew;gcnRQA1kp zBbm04SRuuE{Hn+&1wk%&g;?wja_Is#1gKoFlI7f`Gt}X*-nsMO30b_J@)EFNhzd1QM zdH&qFb9PVqQOx@clvc#KAu}^GrN`q5oP(8>m4UOcp`k&xwzkTio*p?kI4BPtIwX%B zJN69cGsm=x90<;Wmh-bs>43F}ro$}Of@8)4KHndLiR$nW?*{Rl72JPUqRr3ta6e#A z%DTEbi9N}+xPtd1juj8;(CJt3r9NOgb>KTuK|z7!JB_KsFW3(pBN4oh&M&}Nb$Ee2 z$-arA6a)CdsPj`M#1DS>fqj#KF%0q?w50GN4YbmMZIoF{e1yTR=4ablqXHBB2!`wM z1M1ke9+<);|AI;f=2^F1;G6Wfpql?1d5D4rMr?#f(=hkoH)U`6Gb)#xDLjoKjp)1;Js@2Iy5yk zMXUqj+gyk1i0yLjWS|3sM2-1ECc;MAz<4t0P53%7se$$+5Ex`L5TQO_MMXXi04UDIU+3*7Ez&X|mj9cFYBXqM{M;mw_ zpw>azP*qjMyNSD4hh)XZt$gqf8f?eRSFX8VQ4Y+H3jAtvyTrXr`qHAD6`m;aYmH2zOhJC~_*AuT} zvUxC38|JYN94i(05R)dVKgUQF$}#cxV7xZ4FULqFCNX*Forhgp*yr6;DsIk=ub0Hv zpk2L{9Q&|uI^b<6@i(Y+iSxeO_n**4nRLc`P!3ld5jL=nZRw6;DEJ*1z6Pvg+eW|$lnnjO zjd|8>6l{i~UxI244CGn2kK@cJ|#ecwgSyt&HKA2)z zrOO{op^o*- - - \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css deleted file mode 100644 index b5c61c956711f..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tailwind.config.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tailwind.config.js deleted file mode 100644 index 95b7c46ff8ab9..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tailwind.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - './pages/**/*.{js,ts,jsx,tsx}', - './components/**/*.{js,ts,jsx,tsx}', - './app/**/*.{js,ts,jsx,tsx}', - ], - theme: { - extend: {}, - }, - plugins: [], -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tsconfig.json b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tsconfig.json deleted file mode 100644 index 61365f3fa7c03..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "paths": { - "@turbo/pack-test-harness": ["../../../../../../test-harness"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/index.jsx"], - "exclude": ["node_modules"] -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-55e40b.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-55e40b.txt deleted file mode 100644 index 004c2a51e57ae..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-55e40b.txt +++ /dev/null @@ -1,15 +0,0 @@ -warning - [parse] [project]/node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js /node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js:88:8 lint TP1004 fs.existsSync(???*0*) is very dynamic - 84 | } - 85 | - 86 | loadFile(path) { - 87 | this.root = dirname(path) - + v - 88 + if (existsSync(path)) { - + ^ - 89 | this.mapFile = path - 90 | return readFileSync(path, 'utf-8').toString().trim() - 91 | } - 92 | } - - - *0* arguments[0] - ⚠️ function calls are not analysed yet \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-bbe67b.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-bbe67b.txt deleted file mode 100644 index 004c2a51e57ae..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0-bbe67b.txt +++ /dev/null @@ -1,15 +0,0 @@ -warning - [parse] [project]/node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js /node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js:88:8 lint TP1004 fs.existsSync(???*0*) is very dynamic - 84 | } - 85 | - 86 | loadFile(path) { - 87 | this.root = dirname(path) - + v - 88 + if (existsSync(path)) { - + ^ - 89 | this.mapFile = path - 90 | return readFileSync(path, 'utf-8').toString().trim() - 91 | } - 92 | } - - - *0* arguments[0] - ⚠️ function calls are not analysed yet \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-50ea93.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-50ea93.txt deleted file mode 100644 index fb6ae7c8a4cbe..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-50ea93.txt +++ /dev/null @@ -1,15 +0,0 @@ -warning - [parse] [project]/node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js /node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js:90:13 lint TP1004 fs.readFileSync(???*0*, "utf-8") is very dynamic - 86 | loadFile(path) { - 87 | this.root = dirname(path) - 88 | if (existsSync(path)) { - 89 | this.mapFile = path - + v - 90 + return readFileSync(path, 'utf-8').toString().trim() - + ^ - 91 | } - 92 | } - 93 | - 94 | loadMap(file, prev) { - - - *0* arguments[0] - ⚠️ function calls are not analysed yet \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-a3cd71.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-a3cd71.txt deleted file mode 100644 index fb6ae7c8a4cbe..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star_-a3cd71.txt +++ /dev/null @@ -1,15 +0,0 @@ -warning - [parse] [project]/node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js /node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/previous-map.js:90:13 lint TP1004 fs.readFileSync(???*0*, "utf-8") is very dynamic - 86 | loadFile(path) { - 87 | this.root = dirname(path) - 88 | if (existsSync(path)) { - 89 | this.mapFile = path - + v - 90 + return readFileSync(path, 'utf-8').toString().trim() - + ^ - 91 | } - 92 | } - 93 | - 94 | loadMap(file, prev) { - - - *0* arguments[0] - ⚠️ function calls are not analysed yet \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-820338.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-820338.txt deleted file mode 100644 index 745e0fe32d777..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-820338.txt +++ /dev/null @@ -1,17 +0,0 @@ -warning - [parse] [project]/node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/input.js /node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/input.js:44:20 lint TP1006 path.resolve(???*0*) is very dynamic - 40 | isAbsolute(opts.from) - 41 | ) { - 42 | this.file = opts.from - 43 | } else { - + v - 44 + this.file = resolve(opts.from) - + ^ - 45 | } - 46 | } - 47 | - 48 | if (pathAvailable && sourceMapAvailable) { - - - *0* ???*1*["from"] - ⚠️ unknown object - - *1* opts - ⚠️ pattern without value \ No newline at end of file diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-c23a56.txt b/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-c23a56.txt deleted file mode 100644 index 745e0fe32d777..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0_-c23a56.txt +++ /dev/null @@ -1,17 +0,0 @@ -warning - [parse] [project]/node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/input.js /node_modules/.pnpm/postcss@8.4.14/node_modules/postcss/lib/input.js:44:20 lint TP1006 path.resolve(???*0*) is very dynamic - 40 | isAbsolute(opts.from) - 41 | ) { - 42 | this.file = opts.from - 43 | } else { - + v - 44 + this.file = resolve(opts.from) - + ^ - 45 | } - 46 | } - 47 | - 48 | if (pathAvailable && sourceMapAvailable) { - - - *0* ???*1*["from"] - ⚠️ unknown object - - *1* opts - ⚠️ pattern without value \ No newline at end of file diff --git a/test/development/basic/tailwind-jit.test.ts b/test/development/basic/tailwind-jit.test.ts index 1852ba9d73628..533cd10ebc94d 100644 --- a/test/development/basic/tailwind-jit.test.ts +++ b/test/development/basic/tailwind-jit.test.ts @@ -1,30 +1,30 @@ import { join } from 'path' import webdriver from 'next-webdriver' -import { createNext, FileRef } from 'e2e-utils' -import { NextInstance } from 'test/lib/next-modes/base' -import { check } from 'next-test-utils' +import { FileRef, nextTestSetup } from 'e2e-utils' +import { check, shouldRunTurboDevTest } from 'next-test-utils' -describe('TailwindCSS JIT', () => { - let next: NextInstance +// [TODO]: It is unclear why turbopack takes longer to run this test +// remove once it's fixed +if (shouldRunTurboDevTest()) { + jest.setTimeout(1000 * 60 * 5) +} - beforeAll(async () => { - next = await createNext({ - files: { - 'postcss.config.js': new FileRef( - join(__dirname, 'tailwind-jit/postcss.config.js') - ), - 'tailwind.config.js': new FileRef( - join(__dirname, 'tailwind-jit/tailwind.config.js') - ), - pages: new FileRef(join(__dirname, 'tailwind-jit/pages')), - }, - dependencies: { - tailwindcss: '2.2.19', - postcss: '8.3.5', - }, - }) +describe('TailwindCSS JIT', () => { + const { next } = nextTestSetup({ + files: { + 'postcss.config.js': new FileRef( + join(__dirname, 'tailwind-jit/postcss.config.js') + ), + 'tailwind.config.js': new FileRef( + join(__dirname, 'tailwind-jit/tailwind.config.js') + ), + pages: new FileRef(join(__dirname, 'tailwind-jit/pages')), + }, + dependencies: { + tailwindcss: '2.2.19', + postcss: '8.3.5', + }, }) - afterAll(() => next.destroy()) it('works with JIT enabled', async () => { let browser diff --git a/test/integration/css-fixtures/with-tailwindcss/package.json b/test/integration/css-fixtures/with-tailwindcss/package.json deleted file mode 100644 index 50375a78eb20b..0000000000000 --- a/test/integration/css-fixtures/with-tailwindcss/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "postcss": { - "plugins": { - "tailwindcss": {} - } - } -} diff --git a/test/integration/css-fixtures/with-tailwindcss/pages/_app.js b/test/integration/css-fixtures/with-tailwindcss/pages/_app.js deleted file mode 100644 index 0bd950249faeb..0000000000000 --- a/test/integration/css-fixtures/with-tailwindcss/pages/_app.js +++ /dev/null @@ -1,5 +0,0 @@ -import '../styles/global.css' - -export default function MyApp({ Component, pageProps }) { - return -} diff --git a/test/integration/css-fixtures/with-tailwindcss/pages/index.js b/test/integration/css-fixtures/with-tailwindcss/pages/index.js deleted file mode 100644 index b3ba78da2d5e1..0000000000000 --- a/test/integration/css-fixtures/with-tailwindcss/pages/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function Home() { - return
-} diff --git a/test/integration/css-fixtures/with-tailwindcss/styles/global.css b/test/integration/css-fixtures/with-tailwindcss/styles/global.css deleted file mode 100644 index b5c61c956711f..0000000000000 --- a/test/integration/css-fixtures/with-tailwindcss/styles/global.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/test/turbopack-tests-manifest.js b/test/turbopack-tests-manifest.js index fdae633aef0a4..19a43fb62de76 100644 --- a/test/turbopack-tests-manifest.js +++ b/test/turbopack-tests-manifest.js @@ -12,6 +12,7 @@ const enabledTests = [ 'test/development/basic/legacy-decorators.test.ts', 'test/development/basic/misc.test.ts', 'test/development/basic/next-rs-api.test.ts', + 'test/development/basic/tailwind-jit.test.ts', 'test/development/basic/theme-ui.test.ts', 'test/development/client-dev-overlay/index.test.ts', 'test/development/correct-tsconfig-defaults/index.test.ts', @@ -158,7 +159,6 @@ module.exports = { enabledTests } 'test/development/basic/emotion-swc.test.ts', 'test/development/basic/legacy-decorators.test.ts', 'test/development/basic/project-directory-rename.test.ts', - 'test/development/basic/tailwind-jit.test.ts', 'test/development/basic/theme-ui.test.ts', 'test/development/dotenv-default-expansion/index.test.ts', 'test/development/jsconfig-path-reloading/index.test.ts', From e486d74464078888426f9ad976f90996a0e0708a Mon Sep 17 00:00:00 2001 From: Daniel Salvadori Date: Mon, 11 Sep 2023 16:09:06 -0300 Subject: [PATCH 08/16] Fix typo (#55245) This PR fixes a single character typo. --- .../03-rendering/01-server-components.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx b/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx index 4284a137212e9..f526187d809cc 100644 --- a/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx +++ b/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx @@ -80,7 +80,7 @@ Dynamic rendering is useful when a route has data that is personalized to the us > > In most websites, routes are not fully static or fully dynamic - it's a spectrum. For example, you can have e-commerce page that uses cached product data that's revalidated at an interval, but also has uncached, personalized customer data. > -> In Next.js, you can have dynamically rendered routes that have both cached and uncached data. This is because the RSC Payload and data are cached separetely. This allows you to opt into dynamic rendering without worrying about the performance impact of fetching all the data at request time. +> In Next.js, you can have dynamically rendered routes that have both cached and uncached data. This is because the RSC Payload and data are cached separately. This allows you to opt into dynamic rendering without worrying about the performance impact of fetching all the data at request time. > > Learn more about the [full-route cache](/docs/app/building-your-application/caching#full-route-cache) and [Data Cache](/docs/app/building-your-application/caching#data-cache). From 7d93808c435d6d74f93e045711b1639dc1fc05b3 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 11 Sep 2023 13:17:52 -0700 Subject: [PATCH 09/16] Remove render workers in favor of esm loader (#54813) Currently we create separate workers to isolate `pages` and `app` routers due to differing react versions being used between the two. This adds overhead and complexity in the rendering process which we can avoid by leveraging an `esm-loader` similar to our `require-hook` to properly alias `pages` router to the bundled react version to match `app` router when both are leveraged together. This aims to seamlessly inject the `esm-loader` by restarting the process with the loader arg added whenever `next` is imported so that this also works with custom-servers and fixes the issue with custom req/res fields not working after upgrading. x-ref: https://github.com/vercel/next.js/issues/53883 x-ref: https://github.com/vercel/next.js/issues/54288 x-ref: https://github.com/vercel/next.js/issues/54129 x-ref: https://github.com/vercel/next.js/issues/54435 closes: https://github.com/vercel/next.js/issues/54440 closes: https://github.com/vercel/next.js/issues/52702 x-ref: [slack thread](https://vercel.slack.com/archives/C03KAR5DCKC/p1693348443932499?thread_ts=1693275196.347509&cid=C03KAR5DCKC) --------- Co-authored-by: Tim Neutkens Co-authored-by: Zack Tanner Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- packages/next/src/bin/next.ts | 92 ++++- packages/next/src/build/index.ts | 70 ++-- packages/next/src/build/utils.ts | 21 +- packages/next/src/build/webpack-config.ts | 31 +- .../nextjs-require-cache-hot-reloader.ts | 29 +- packages/next/src/cli/next-build-args.ts | 17 + packages/next/src/cli/next-build.ts | 22 +- packages/next/src/cli/next-dev-args.ts | 25 ++ packages/next/src/cli/next-dev.ts | 173 +-------- packages/next/src/cli/next-export-args.ts | 14 + packages/next/src/cli/next-export.ts | 17 +- packages/next/src/cli/next-info-args.ts | 13 + packages/next/src/cli/next-info.ts | 18 +- packages/next/src/cli/next-lint-args.ts | 44 +++ packages/next/src/cli/next-lint.ts | 49 +-- packages/next/src/cli/next-start-args.ts | 15 + packages/next/src/cli/next-start.ts | 18 +- packages/next/src/cli/next-telemetry-args.ts | 10 + packages/next/src/cli/next-telemetry.ts | 14 +- packages/next/src/lib/command-args.ts | 17 + packages/next/src/lib/commands.ts | 4 +- packages/next/src/server/api-utils/node.ts | 25 -- packages/next/src/server/base-http/node.ts | 2 +- packages/next/src/server/base-server.ts | 26 +- packages/next/src/server/config-utils.ts | 2 +- packages/next/src/server/config.ts | 21 +- .../src/server/dev/hot-reloader-webpack.ts | 4 +- .../next/src/server/dev/next-dev-server.ts | 36 +- packages/next/src/server/esm-loader.mts | 27 ++ .../{require-hook.ts => import-overrides.ts} | 86 ++--- .../src/server/lib/get-esm-loader-path.ts | 30 ++ packages/next/src/server/lib/mock-request.ts | 62 +++- packages/next/src/server/lib/render-server.ts | 60 ++-- .../next/src/server/lib/route-resolver.ts | 121 ++----- packages/next/src/server/lib/router-server.ts | 333 +++++++----------- .../server/lib/router-utils/resolve-routes.ts | 92 +++-- .../src/server/lib/router-utils/setup-dev.ts | 37 +- .../src/server/lib/setup-server-worker.ts | 4 +- packages/next/src/server/lib/start-server.ts | 45 ++- packages/next/src/server/next-server.ts | 44 ++- packages/next/src/server/next.ts | 54 ++- packages/next/src/server/require-hook.js | 52 +++ packages/next/src/server/require.ts | 16 +- packages/next/src/shared/lib/constants.ts | 8 + packages/next/taskfile-swc.js | 5 +- packages/next/taskfile.js | 2 +- test/development/basic/node-builtins.test.ts | 4 +- .../watch-config-file/index.test.ts | 42 +-- test/e2e/app-dir/app/index.test.ts | 4 +- test/e2e/app-dir/rsc-basic/rsc-basic.test.ts | 10 +- .../amphtml-fragment-style/pages/index.js | 5 +- .../api-body-parser/pages/api/index.js | 13 +- test/integration/api-body-parser/server.js | 1 + .../api-body-parser/test/index.test.js | 10 +- test/integration/cli/test/index.test.js | 34 +- .../middleware.js | 2 +- test/lib/next-test-utils.ts | 7 +- .../custom-server/custom-server.test.ts | 4 +- test/production/custom-server/server.js | 2 +- 59 files changed, 1073 insertions(+), 972 deletions(-) create mode 100755 packages/next/src/cli/next-build-args.ts create mode 100644 packages/next/src/cli/next-dev-args.ts create mode 100755 packages/next/src/cli/next-export-args.ts create mode 100755 packages/next/src/cli/next-info-args.ts create mode 100755 packages/next/src/cli/next-lint-args.ts create mode 100755 packages/next/src/cli/next-start-args.ts create mode 100755 packages/next/src/cli/next-telemetry-args.ts create mode 100644 packages/next/src/lib/command-args.ts create mode 100644 packages/next/src/server/esm-loader.mts rename packages/next/src/server/{require-hook.ts => import-overrides.ts} (56%) create mode 100644 packages/next/src/server/lib/get-esm-loader-path.ts create mode 100644 packages/next/src/server/require-hook.js diff --git a/packages/next/src/bin/next.ts b/packages/next/src/bin/next.ts index 91b15669d179a..b38e42f1e8617 100755 --- a/packages/next/src/bin/next.ts +++ b/packages/next/src/bin/next.ts @@ -1,18 +1,18 @@ #!/usr/bin/env node +import '../server/require-hook' import * as log from '../build/output/log' import arg from 'next/dist/compiled/arg/index.js' import { NON_STANDARD_NODE_ENV } from '../lib/constants' import { commands } from '../lib/commands' -;['react', 'react-dom'].forEach((dependency) => { - try { - // When 'npm link' is used it checks the clone location. Not the project. - require.resolve(dependency) - } catch (err) { - console.warn( - `The module '${dependency}' was not found. Next.js requires that you include it in 'dependencies' of your 'package.json'. To add it, run 'npm install ${dependency}'` - ) - } -}) +import { commandArgs } from '../lib/command-args' +import loadConfig from '../server/config' +import { + PHASE_PRODUCTION_SERVER, + PHASE_DEVELOPMENT_SERVER, +} from '../shared/lib/constants' +import { getProjectDir } from '../lib/get-project-dir' +import { getValidatedArgs } from '../lib/get-validated-args' +import { findPagesDir } from '../lib/find-pages-dir' const defaultCommand = 'dev' const args = arg( @@ -122,13 +122,69 @@ if (!process.env.NEXT_MANUAL_SIG_HANDLE && command !== 'dev') { process.on('SIGTERM', () => process.exit(0)) process.on('SIGINT', () => process.exit(0)) } +async function main() { + const currentArgsSpec = commandArgs[command]() + const validatedArgs = getValidatedArgs(currentArgsSpec, forwardedArgs) + + if ( + (command === 'start' || command === 'dev') && + !process.env.NEXT_PRIVATE_WORKER + ) { + const dir = getProjectDir( + process.env.NEXT_PRIVATE_DEV_DIR || validatedArgs._[0] + ) + process.env.NEXT_PRIVATE_DIR = dir + const origEnv = Object.assign({}, process.env) + + // TODO: set config to env variable to be re-used so we don't reload + // un-necessarily + const config = await loadConfig( + command === 'dev' ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER, + dir + ) + let dirsResult: ReturnType | undefined = undefined + + try { + dirsResult = findPagesDir(dir) + } catch (_) { + // handle this error further down + } -commands[command]() - .then((exec) => exec(forwardedArgs)) - .then(() => { - if (command === 'build' || command === 'experimental-compile') { - // ensure process exits after build completes so open handles/connections - // don't cause process to hang - process.exit(0) + if (dirsResult?.appDir || process.env.NODE_ENV === 'development') { + process.env = origEnv } - }) + + if (dirsResult?.appDir) { + // we need to reset env if we are going to create + // the worker process with the esm loader so that the + // initial env state is correct + process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = config.experimental + .serverActions + ? 'experimental' + : 'next' + } + } + + for (const dependency of ['react', 'react-dom']) { + try { + // When 'npm link' is used it checks the clone location. Not the project. + require.resolve(dependency) + } catch (err) { + console.warn( + `The module '${dependency}' was not found. Next.js requires that you include it in 'dependencies' of your 'package.json'. To add it, run 'npm install ${dependency}'` + ) + } + } + + await commands[command]() + .then((exec) => exec(validatedArgs)) + .then(() => { + if (command === 'build' || command === 'experimental-compile') { + // ensure process exits after build completes so open handles/connections + // don't cause process to hang + process.exit(0) + } + }) +} + +main() diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index e7d525373c3f5..8d92c8ae5587d 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -149,9 +149,10 @@ import { baseOverrides, defaultOverrides, experimentalOverrides, -} from '../server/require-hook' -import { initialize } from '../server/lib/incremental-cache-server' +} from '../server/import-overrides' +import { initialize as initializeIncrementalCache } from '../server/lib/incremental-cache-server' import { nodeFs } from '../server/lib/node-fs-methods' +import { getEsmLoaderPath } from '../server/lib/get-esm-loader-path' export type SsgRoute = { initialRevalidateSeconds: number | false @@ -1207,9 +1208,8 @@ export default async function build( : config.experimental.cpus || 4 function createStaticWorker( - type: 'app' | 'pages', - ipcPort: number, - ipcValidationKey: string + incrementalCacheIpcPort: number, + incrementalCacheIpcValidationKey: string ) { let infoPrinted = false @@ -1246,16 +1246,21 @@ export default async function build( }, numWorkers, forkOptions: { + execArgv: [ + '--experimental-loader', + getEsmLoaderPath(), + '--no-warnings', + ], env: { ...process.env, - __NEXT_INCREMENTAL_CACHE_IPC_PORT: ipcPort + '', - __NEXT_INCREMENTAL_CACHE_IPC_KEY: ipcValidationKey, - __NEXT_PRIVATE_PREBUNDLED_REACT: - type === 'app' - ? config.experimental.serverActions - ? 'experimental' - : 'next' - : undefined, + __NEXT_INCREMENTAL_CACHE_IPC_PORT: incrementalCacheIpcPort + '', + __NEXT_INCREMENTAL_CACHE_IPC_KEY: + incrementalCacheIpcValidationKey, + __NEXT_PRIVATE_PREBUNDLED_REACT: hasAppDir + ? config.experimental.serverActions + ? 'experimental' + : 'next' + : '', }, }, enableWorkerThreads: config.experimental.workerThreads, @@ -1290,7 +1295,10 @@ export default async function build( CacheHandler = CacheHandler.default || CacheHandler } - const { ipcPort, ipcValidationKey } = await initialize({ + const { + ipcPort: incrementalCacheIpcPort, + ipcValidationKey: incrementalCacheIpcValidationKey, + } = await initializeIncrementalCache({ fs: nodeFs, dev: false, appDir: isAppDirEnabled, @@ -1315,12 +1323,14 @@ export default async function build( }) const pagesStaticWorkers = createStaticWorker( - 'pages', - ipcPort, - ipcValidationKey + incrementalCacheIpcPort, + incrementalCacheIpcValidationKey ) const appStaticWorkers = isAppDirEnabled - ? createStaticWorker('app', ipcPort, ipcValidationKey) + ? createStaticWorker( + incrementalCacheIpcPort, + incrementalCacheIpcValidationKey + ) : undefined const analysisBegin = process.hrtime() @@ -2124,9 +2134,14 @@ export default async function build( const vanillaServerEntries = [ ...sharedEntriesSet, - isStandalone - ? require.resolve('next/dist/server/lib/start-server') - : null, + ...(isStandalone + ? [ + require.resolve('next/dist/server/lib/start-server'), + require.resolve('next/dist/server/next'), + require.resolve('next/dist/esm/server/esm-loader.mjs'), + require.resolve('next/dist/server/import-overrides'), + ] + : []), require.resolve('next/dist/server/next-server'), ].filter(Boolean) as string[] @@ -2402,7 +2417,8 @@ export default async function build( outputFileTracingRoot, requiredServerFiles.config, middlewareManifest, - hasInstrumentationHook + hasInstrumentationHook, + hasAppDir ) }) } @@ -3315,11 +3331,13 @@ export default async function build( require('../export').default const pagesWorker = createStaticWorker( - 'pages', - ipcPort, - ipcValidationKey + incrementalCacheIpcPort, + incrementalCacheIpcValidationKey + ) + const appWorker = createStaticWorker( + incrementalCacheIpcPort, + incrementalCacheIpcValidationKey ) - const appWorker = createStaticWorker('app', ipcPort, ipcValidationKey) const options: ExportOptions = { isInvokedFromCli: false, diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 70f8ac8b27423..6f47380af82e9 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -1830,7 +1830,8 @@ export async function copyTracedFiles( tracingRoot: string, serverConfig: { [key: string]: any }, middlewareManifest: MiddlewareManifest, - hasInstrumentationHook: boolean + hasInstrumentationHook: boolean, + hasAppDir: boolean ) { const outputPath = path.join(distDir, 'standalone') let moduleType = false @@ -1963,12 +1964,11 @@ export async function copyTracedFiles( moduleType ? `import path from 'path' import { fileURLToPath } from 'url' +import module from 'module' +const require = module.createRequire(import.meta.url) const __dirname = fileURLToPath(new URL('.', import.meta.url)) -import { startServer } from 'next/dist/server/lib/start-server.js' ` - : ` -const path = require('path') -const { startServer } = require('next/dist/server/lib/start-server')` + : `const path = require('path')` } const dir = path.join(__dirname) @@ -1993,9 +1993,14 @@ const nextConfig = ${JSON.stringify({ })} process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig) -process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = nextConfig.experimental && nextConfig.experimental.serverActions - ? 'experimental' - : 'next' +process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = ${hasAppDir} + ? nextConfig.experimental && nextConfig.experimental.serverActions + ? 'experimental' + : 'next' + : ''; + +require('next') +const { startServer } = require('next/dist/server/lib/start-server') if ( Number.isNaN(keepAliveTimeout) || diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index dbc805edaf1fd..00f85439e00cd 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -68,6 +68,7 @@ import { NextFontManifestPlugin } from './webpack/plugins/next-font-manifest-plu import { getSupportedBrowsers } from './utils' import { MemoryWithGcCachePlugin } from './webpack/plugins/memory-with-gc-cache-plugin' import { getBabelConfigFile } from './get-babel-config-file' +import { defaultOverrides } from '../server/import-overrides' type ExcludesFalse = (x: T | false) => x is T type ClientEntries = { @@ -1127,6 +1128,14 @@ export default async function getBaseWebpackConfig( '@opentelemetry/api': 'next/dist/compiled/@opentelemetry/api', }), + ...(hasAppDir + ? createRSCAliases(bundledReactChannel, { + reactSharedSubset: false, + reactDomServerRenderingStub: false, + reactProductionProfiling, + }) + : {}), + ...(config.images.loaderFile ? { 'next/dist/shared/lib/image-loader': config.images.loaderFile, @@ -1138,8 +1147,8 @@ export default async function getBaseWebpackConfig( next: NEXT_PROJECT_ROOT, - 'styled-jsx/style$': require.resolve(`styled-jsx/style`), - 'styled-jsx$': require.resolve(`styled-jsx`), + 'styled-jsx/style$': defaultOverrides['styled-jsx/style'], + 'styled-jsx$': defaultOverrides['styled-jsx'], ...customAppAliases, ...customErrorAlias, @@ -1273,7 +1282,16 @@ export default async function getBaseWebpackConfig( } } - for (const packageName of ['react', 'react-dom']) { + for (const packageName of [ + 'react', + 'react-dom', + ...(hasAppDir + ? [ + `next/dist/compiled/react${bundledReactChannel}`, + `next/dist/compiled/react-dom${bundledReactChannel}`, + ] + : []), + ]) { addPackagePath(packageName, dir) } @@ -1541,7 +1559,7 @@ export default async function getBaseWebpackConfig( // Forcedly resolve the styled-jsx installed by next.js, // since `resolveExternal` cannot find the styled-jsx dep with pnpm if (request === 'styled-jsx/style') { - resolveResult.res = require.resolve(request) + resolveResult.res = defaultOverrides['styled-jsx/style'] } const { res, isEsm } = resolveResult @@ -2116,11 +2134,6 @@ export default async function getBaseWebpackConfig( [require.resolve('next/dynamic')]: require.resolve( 'next/dist/shared/lib/app-dynamic' ), - ...createRSCAliases(bundledReactChannel, { - reactSharedSubset: false, - reactDomServerRenderingStub: false, - reactProductionProfiling, - }), }, }, }, diff --git a/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts b/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts index 107e2e03f07bb..f14071a5df009 100644 --- a/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts +++ b/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts @@ -20,16 +20,7 @@ const originModules = [ const RUNTIME_NAMES = ['webpack-runtime', 'webpack-api-runtime'] -const nextDeleteCacheRpc = async (filePaths: string[]) => { - if ((global as any)._nextDeleteCache) { - return (global as any)._nextDeleteCache(filePaths) - } -} - export function deleteAppClientCache() { - if ((global as any)._nextDeleteAppClientCache) { - return (global as any)._nextDeleteAppClientCache() - } // ensure we reset the cache for rsc components // loaded via react-server-dom-webpack const reactServerDomModId = require.resolve( @@ -88,43 +79,41 @@ export class NextJsRequireCacheHotReloader implements WebpackPluginInstance { apply(compiler: Compiler) { compiler.hooks.assetEmitted.tap(PLUGIN_NAME, (_file, { targetPath }) => { - nextDeleteCacheRpc([targetPath]) - - // Clear module context in other processes - if ((global as any)._nextClearModuleContext) { - ;(global as any)._nextClearModuleContext(targetPath) - } // Clear module context in this process clearModuleContext(targetPath) + deleteCache(targetPath) }) compiler.hooks.afterEmit.tapPromise(PLUGIN_NAME, async (compilation) => { - const cacheEntriesToDelete = [] - for (const name of RUNTIME_NAMES) { const runtimeChunkPath = path.join( compilation.outputOptions.path!, `${name}.js` ) - cacheEntriesToDelete.push(runtimeChunkPath) + deleteCache(runtimeChunkPath) } // we need to make sure to clear all server entries from cache // since they can have a stale webpack-runtime cache // which needs to always be in-sync + let hasAppEntry = false const entries = [...compilation.entries.keys()].filter((entry) => { const isAppPath = entry.toString().startsWith('app/') + if (isAppPath) hasAppEntry = true return entry.toString().startsWith('pages/') || isAppPath }) + if (hasAppEntry) { + deleteAppClientCache() + } + for (const page of entries) { const outputPath = path.join( compilation.outputOptions.path!, page + '.js' ) - cacheEntriesToDelete.push(outputPath) + deleteCache(outputPath) } - await nextDeleteCacheRpc(cacheEntriesToDelete) }) } } diff --git a/packages/next/src/cli/next-build-args.ts b/packages/next/src/cli/next-build-args.ts new file mode 100755 index 0000000000000..814ac16df8396 --- /dev/null +++ b/packages/next/src/cli/next-build-args.ts @@ -0,0 +1,17 @@ +import arg from 'next/dist/compiled/arg/index.js' + +export const validArgs: arg.Spec = { + // Types + '--help': Boolean, + '--profile': Boolean, + '--debug': Boolean, + '--no-lint': Boolean, + '--no-mangling': Boolean, + '--experimental-app-only': Boolean, + '--experimental-turbo': Boolean, + '--experimental-turbo-root': String, + '--build-mode': String, + // Aliases + '-h': '--help', + '-d': '--debug', +} diff --git a/packages/next/src/cli/next-build.ts b/packages/next/src/cli/next-build.ts index fc7decbe032f3..223b772ff6578 100755 --- a/packages/next/src/cli/next-build.ts +++ b/packages/next/src/cli/next-build.ts @@ -1,33 +1,13 @@ #!/usr/bin/env node import { existsSync } from 'fs' -import arg from 'next/dist/compiled/arg/index.js' import * as Log from '../build/output/log' import { CliCommand } from '../lib/commands' import build from '../build' import { printAndExit } from '../server/lib/utils' import isError from '../lib/is-error' import { getProjectDir } from '../lib/get-project-dir' -import { getValidatedArgs } from '../lib/get-validated-args' - -const nextBuild: CliCommand = (argv) => { - const validArgs: arg.Spec = { - // Types - '--help': Boolean, - '--profile': Boolean, - '--debug': Boolean, - '--no-lint': Boolean, - '--no-mangling': Boolean, - '--experimental-app-only': Boolean, - '--experimental-turbo': Boolean, - '--experimental-turbo-root': String, - '--build-mode': String, - // Aliases - '-h': '--help', - '-d': '--debug', - } - - const args = getValidatedArgs(validArgs, argv) +const nextBuild: CliCommand = (args) => { if (args['--help']) { printAndExit( ` diff --git a/packages/next/src/cli/next-dev-args.ts b/packages/next/src/cli/next-dev-args.ts new file mode 100644 index 0000000000000..3ab378660c386 --- /dev/null +++ b/packages/next/src/cli/next-dev-args.ts @@ -0,0 +1,25 @@ +import arg from 'next/dist/compiled/arg/index.js' + +export const validArgs: arg.Spec = { + // Types + '--help': Boolean, + '--port': Number, + '--hostname': String, + '--turbo': Boolean, + '--experimental-turbo': Boolean, + '--experimental-https': Boolean, + '--experimental-https-key': String, + '--experimental-https-cert': String, + '--experimental-test-proxy': Boolean, + '--experimental-upload-trace': String, + + // To align current messages with native binary. + // Will need to adjust subcommand later. + '--show-all': Boolean, + '--root': String, + + // Aliases + '-h': '--help', + '-p': '--port', + '-H': '--hostname', +} diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 17792914bbc8c..e6b03751a9342 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -1,15 +1,10 @@ #!/usr/bin/env node -import arg from 'next/dist/compiled/arg/index.js' import type { StartServerOptions } from '../server/lib/start-server' -import { - genRouterWorkerExecArgv, - getNodeOptionsWithoutInspect, -} from '../server/lib/utils' import { getPort, printAndExit } from '../server/lib/utils' import * as Log from '../build/output/log' import { CliCommand } from '../lib/commands' import { getProjectDir } from '../lib/get-project-dir' -import { CONFIG_FILES, PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' +import { PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' import path from 'path' import { NextConfigComplete } from '../server/config-shared' import { setGlobal, traceGlobals } from '../trace/shared' @@ -18,14 +13,9 @@ import loadConfig, { getEnabledExperimentalFeatures } from '../server/config' import { findPagesDir } from '../lib/find-pages-dir' import { fileExists, FileType } from '../lib/file-exists' import { getNpxCommand } from '../lib/helpers/get-npx-command' -import Watchpack from 'watchpack' -import { initialEnv } from '@next/env' -import { getValidatedArgs } from '../lib/get-validated-args' -import { Worker } from 'next/dist/compiled/jest-worker' -import type { ChildProcess } from 'child_process' -import { checkIsNodeDebugging } from '../server/lib/is-node-debugging' import { createSelfSignedCertificate } from '../lib/mkcert' import uploadTrace from '../trace/upload-trace' +import { startServer } from '../server/lib/start-server' import { loadEnvConfig } from '@next/env' import { trace } from '../trace' @@ -110,125 +100,7 @@ const handleSessionStop = async () => { process.on('SIGINT', handleSessionStop) process.on('SIGTERM', handleSessionStop) -function watchConfigFiles( - dirToWatch: string, - onChange: (filename: string) => void -) { - const wp = new Watchpack() - wp.watch({ files: CONFIG_FILES.map((file) => path.join(dirToWatch, file)) }) - wp.on('change', onChange) -} - -type StartServerWorker = Worker & - Pick - -async function createRouterWorker(fullConfig: NextConfigComplete): Promise<{ - worker: StartServerWorker - cleanup: () => Promise -}> { - const isNodeDebugging = checkIsNodeDebugging() - const worker = new Worker(require.resolve('../server/lib/start-server'), { - numWorkers: 1, - // TODO: do we want to allow more than 8 OOM restarts? - maxRetries: 8, - forkOptions: { - execArgv: await genRouterWorkerExecArgv( - isNodeDebugging === undefined ? false : isNodeDebugging - ), - env: { - FORCE_COLOR: '1', - ...(initialEnv as any), - NODE_OPTIONS: getNodeOptionsWithoutInspect(), - ...(process.env.NEXT_CPU_PROF - ? { __NEXT_PRIVATE_CPU_PROFILE: `CPU.router` } - : {}), - WATCHPACK_WATCHER_LIMIT: '20', - TURBOPACK: process.env.TURBOPACK, - __NEXT_PRIVATE_PREBUNDLED_REACT: !!fullConfig.experimental.serverActions - ? 'experimental' - : 'next', - }, - }, - exposedMethods: ['startServer'], - }) as Worker & - Pick - - const cleanup = () => { - for (const curWorker of ((worker as any)._workerPool?._workers || []) as { - _child?: ChildProcess - }[]) { - curWorker._child?.kill('SIGINT') - } - process.exit(0) - } - - // If the child routing worker exits we need to exit the entire process - for (const curWorker of ((worker as any)._workerPool?._workers || []) as { - _child?: ChildProcess - }[]) { - curWorker._child?.on('exit', cleanup) - } - - process.on('exit', cleanup) - process.on('SIGINT', cleanup) - process.on('SIGTERM', cleanup) - process.on('uncaughtException', cleanup) - process.on('unhandledRejection', cleanup) - - const workerStdout = worker.getStdout() - const workerStderr = worker.getStderr() - - workerStdout.on('data', (data) => { - process.stdout.write(data) - }) - workerStderr.on('data', (data) => { - process.stderr.write(data) - }) - - return { - worker, - cleanup: async () => { - // Remove process listeners for childprocess too. - for (const curWorker of ((worker as any)._workerPool?._workers || []) as { - _child?: ChildProcess - }[]) { - curWorker._child?.off('exit', cleanup) - } - process.off('exit', cleanup) - process.off('SIGINT', cleanup) - process.off('SIGTERM', cleanup) - process.off('uncaughtException', cleanup) - process.off('unhandledRejection', cleanup) - await worker.end() - }, - } -} - -const nextDev: CliCommand = async (argv) => { - const validArgs: arg.Spec = { - // Types - '--help': Boolean, - '--port': Number, - '--hostname': String, - '--turbo': Boolean, - '--experimental-turbo': Boolean, - '--experimental-https': Boolean, - '--experimental-https-key': String, - '--experimental-https-cert': String, - '--experimental-test-proxy': Boolean, - '--experimental-upload-trace': String, - - // To align current messages with native binary. - // Will need to adjust subcommand later. - '--show-all': Boolean, - '--root': String, - - // Aliases - '-h': '--help', - '-p': '--port', - '-H': '--hostname', - } - const args = getValidatedArgs(validArgs, argv) +const nextDev: CliCommand = async (args) => { if (args['--help']) { console.log(` Description @@ -354,8 +226,6 @@ const nextDev: CliCommand = async (argv) => { const runDevServer = async (reboot: boolean) => { try { - const workerInit = await createRouterWorker(config) - if (!!args['--experimental-https']) { Log.warn( 'Self-signed certificates are currently an experimental feature, use at your own risk.' @@ -375,54 +245,23 @@ const nextDev: CliCommand = async (argv) => { certificate = await createSelfSignedCertificate(host) } - await workerInit.worker.startServer({ + await startServer({ ...devServerOptions, selfSignedCertificate: certificate, }) } else { - await workerInit.worker.startServer(devServerOptions) + await startServer(devServerOptions) } await preflight(reboot) - return { - cleanup: workerInit.cleanup, - } } catch (err) { console.error(err) process.exit(1) } } - let runningServer: Awaited> | undefined - - watchConfigFiles(devServerOptions.dir, async (filename) => { - if (process.env.__NEXT_DISABLE_MEMORY_WATCHER) { - Log.info( - `Detected change, manual restart required due to '__NEXT_DISABLE_MEMORY_WATCHER' usage` - ) - return - } - // Adding a new line to avoid the logs going directly after the spinner in `next build` - Log.warn('') - Log.warn( - `Found a change in ${path.basename( - filename - )}. Restarting the server to apply the changes...` - ) - - try { - if (runningServer) { - await runningServer.cleanup() - } - runningServer = await runDevServer(true) - } catch (err) { - console.error(err) - process.exit(1) - } - }) - await trace('start-dev-server').traceAsyncFn(async (_) => { - runningServer = await runDevServer(false) + await runDevServer(false) }) } diff --git a/packages/next/src/cli/next-export-args.ts b/packages/next/src/cli/next-export-args.ts new file mode 100755 index 0000000000000..e6c66da77dd30 --- /dev/null +++ b/packages/next/src/cli/next-export-args.ts @@ -0,0 +1,14 @@ +import arg from 'next/dist/compiled/arg/index.js' + +export const validArgs: arg.Spec = { + // Types + '--help': Boolean, + '--silent': Boolean, + '--outdir': String, + '--threads': Number, + + // Aliases + '-h': '--help', + '-o': '--outdir', + '-s': '--silent', +} diff --git a/packages/next/src/cli/next-export.ts b/packages/next/src/cli/next-export.ts index b3fe1d21b1dc7..bf94fdf3f2e2b 100755 --- a/packages/next/src/cli/next-export.ts +++ b/packages/next/src/cli/next-export.ts @@ -1,7 +1,6 @@ #!/usr/bin/env node import { resolve, join } from 'path' import { existsSync } from 'fs' -import arg from 'next/dist/compiled/arg/index.js' import chalk from 'next/dist/compiled/chalk' import exportApp, { ExportError, ExportOptions } from '../export' import * as Log from '../build/output/log' @@ -9,23 +8,9 @@ import { printAndExit } from '../server/lib/utils' import { CliCommand } from '../lib/commands' import { trace } from '../trace' import { getProjectDir } from '../lib/get-project-dir' -import { getValidatedArgs } from '../lib/get-validated-args' -const nextExport: CliCommand = (argv) => { +const nextExport: CliCommand = (args) => { const nextExportCliSpan = trace('next-export-cli') - const validArgs: arg.Spec = { - // Types - '--help': Boolean, - '--silent': Boolean, - '--outdir': String, - '--threads': Number, - - // Aliases - '-h': '--help', - '-o': '--outdir', - '-s': '--silent', - } - const args = getValidatedArgs(validArgs, argv) if (args['--help']) { console.log(` Description diff --git a/packages/next/src/cli/next-info-args.ts b/packages/next/src/cli/next-info-args.ts new file mode 100755 index 0000000000000..dafea92660d84 --- /dev/null +++ b/packages/next/src/cli/next-info-args.ts @@ -0,0 +1,13 @@ +import arg from 'next/dist/compiled/arg/index.js' + +/** + * Supported CLI arguments. + */ +export const validArgs: arg.Spec = { + // Types + '--help': Boolean, + // Aliases + '-h': '--help', + // Detailed diagnostics + '--verbose': Boolean, +} diff --git a/packages/next/src/cli/next-info.ts b/packages/next/src/cli/next-info.ts index 54bf0b58fd52a..b8095852c9484 100755 --- a/packages/next/src/cli/next-info.ts +++ b/packages/next/src/cli/next-info.ts @@ -4,14 +4,12 @@ import os from 'os' import childProcess from 'child_process' import chalk from 'next/dist/compiled/chalk' -import arg from 'next/dist/compiled/arg/index.js' const { fetch } = require('next/dist/compiled/undici') as { fetch: typeof global.fetch } import { CliCommand } from '../lib/commands' import { PHASE_INFO } from '../shared/lib/constants' import loadConfig from '../server/config' -import { getValidatedArgs } from '../lib/get-validated-args' const dir = process.cwd() @@ -51,18 +49,6 @@ type PlatformTaskScript = darwin?: TaskScript } -/** - * Supported CLI arguments. - */ -const validArgs: arg.Spec = { - // Types - '--help': Boolean, - // Aliases - '-h': '--help', - // Detailed diagnostics - '--verbose': Boolean, -} - function getPackageVersion(packageName: string) { try { return require(`${packageName}/package.json`).version @@ -590,9 +576,7 @@ async function printVerbose() { * There are 2 modes, by default it collects basic next.js installation with runtime information. If * `--verbose` mode is enabled it'll try to collect, verify more data for next-swc installation and others. */ -const nextInfo: CliCommand = async (argv) => { - const args = getValidatedArgs(validArgs, argv) - +const nextInfo: CliCommand = async (args) => { if (args['--help']) { printHelp() return diff --git a/packages/next/src/cli/next-lint-args.ts b/packages/next/src/cli/next-lint-args.ts new file mode 100755 index 0000000000000..854af64be303f --- /dev/null +++ b/packages/next/src/cli/next-lint-args.ts @@ -0,0 +1,44 @@ +import arg from 'next/dist/compiled/arg/index.js' + +const validEslintArgs: arg.Spec = { + // Types + '--config': String, + '--ext': [String], + '--resolve-plugins-relative-to': String, + '--rulesdir': [String], + '--fix': Boolean, + '--fix-type': [String], + '--ignore-path': String, + '--no-ignore': Boolean, + '--quiet': Boolean, + '--max-warnings': Number, + '--no-inline-config': Boolean, + '--report-unused-disable-directives': String, + '--cache': Boolean, // Although cache is enabled by default, this dummy flag still exists to not cause any breaking changes + '--no-cache': Boolean, + '--cache-location': String, + '--cache-strategy': String, + '--error-on-unmatched-pattern': Boolean, + '--format': String, + '--output-file': String, + + // Aliases + '-c': '--config', + '-f': '--format', + '-o': '--output-file', +} + +export const validArgs: arg.Spec = { + // Types + '--help': Boolean, + '--base-dir': String, + '--dir': [String], + '--file': [String], + '--strict': Boolean, + + // Aliases + '-h': '--help', + '-b': '--base-dir', + '-d': '--dir', + ...validEslintArgs, +} diff --git a/packages/next/src/cli/next-lint.ts b/packages/next/src/cli/next-lint.ts index 1bb782c7f23d5..0a9acfbd1c25a 100755 --- a/packages/next/src/cli/next-lint.ts +++ b/packages/next/src/cli/next-lint.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node +import type arg from 'next/dist/compiled/arg/index.js' import { existsSync } from 'fs' -import arg from 'next/dist/compiled/arg/index.js' import { join } from 'path' import chalk from 'next/dist/compiled/chalk' @@ -16,7 +16,6 @@ import { CompileError } from '../lib/compile-error' import { getProjectDir } from '../lib/get-project-dir' import { findPagesDir } from '../lib/find-pages-dir' import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup' -import { getValidatedArgs } from '../lib/get-validated-args' const eslintOptions = (args: arg.Spec, defaultCacheLocation: string) => ({ overrideConfigFile: args['--config'] || null, @@ -47,51 +46,7 @@ const eslintOptions = (args: arg.Spec, defaultCacheLocation: string) => ({ : false, }) -const nextLint: CliCommand = async (argv) => { - const validArgs: arg.Spec = { - // Types - '--help': Boolean, - '--base-dir': String, - '--dir': [String], - '--file': [String], - '--strict': Boolean, - - // Aliases - '-h': '--help', - '-b': '--base-dir', - '-d': '--dir', - } - - const validEslintArgs: arg.Spec = { - // Types - '--config': String, - '--ext': [String], - '--resolve-plugins-relative-to': String, - '--rulesdir': [String], - '--fix': Boolean, - '--fix-type': [String], - '--ignore-path': String, - '--no-ignore': Boolean, - '--quiet': Boolean, - '--max-warnings': Number, - '--no-inline-config': Boolean, - '--report-unused-disable-directives': String, - '--cache': Boolean, // Although cache is enabled by default, this dummy flag still exists to not cause any breaking changes - '--no-cache': Boolean, - '--cache-location': String, - '--cache-strategy': String, - '--error-on-unmatched-pattern': Boolean, - '--format': String, - '--output-file': String, - - // Aliases - '-c': '--config', - '-f': '--format', - '-o': '--output-file', - } - - const args = getValidatedArgs({ ...validArgs, ...validEslintArgs }, argv) - +const nextLint: CliCommand = async (args) => { if (args['--help']) { printAndExit( ` diff --git a/packages/next/src/cli/next-start-args.ts b/packages/next/src/cli/next-start-args.ts new file mode 100755 index 0000000000000..1c95bafd0bc71 --- /dev/null +++ b/packages/next/src/cli/next-start-args.ts @@ -0,0 +1,15 @@ +import arg from 'next/dist/compiled/arg/index.js' + +export const validArgs: arg.Spec = { + // Types + '--help': Boolean, + '--port': Number, + '--hostname': String, + '--keepAliveTimeout': Number, + '--experimental-test-proxy': Boolean, + + // Aliases + '-h': '--help', + '-p': '--port', + '-H': '--hostname', +} diff --git a/packages/next/src/cli/next-start.ts b/packages/next/src/cli/next-start.ts index ef54c2723de47..6af517729492d 100755 --- a/packages/next/src/cli/next-start.ts +++ b/packages/next/src/cli/next-start.ts @@ -1,27 +1,11 @@ #!/usr/bin/env node -import arg from 'next/dist/compiled/arg/index.js' import { startServer } from '../server/lib/start-server' import { getPort, printAndExit } from '../server/lib/utils' import { getProjectDir } from '../lib/get-project-dir' import { CliCommand } from '../lib/commands' -import { getValidatedArgs } from '../lib/get-validated-args' -const nextStart: CliCommand = async (argv) => { - const validArgs: arg.Spec = { - // Types - '--help': Boolean, - '--port': Number, - '--hostname': String, - '--keepAliveTimeout': Number, - '--experimental-test-proxy': Boolean, - - // Aliases - '-h': '--help', - '-p': '--port', - '-H': '--hostname', - } - const args = getValidatedArgs(validArgs, argv) +const nextStart: CliCommand = async (args) => { if (args['--help']) { console.log(` Description diff --git a/packages/next/src/cli/next-telemetry-args.ts b/packages/next/src/cli/next-telemetry-args.ts new file mode 100755 index 0000000000000..91b07af36d4fe --- /dev/null +++ b/packages/next/src/cli/next-telemetry-args.ts @@ -0,0 +1,10 @@ +import arg from 'next/dist/compiled/arg/index.js' + +export const validArgs: arg.Spec = { + // Types + '--enable': Boolean, + '--disable': Boolean, + '--help': Boolean, + // Aliases + '-h': '--help', +} diff --git a/packages/next/src/cli/next-telemetry.ts b/packages/next/src/cli/next-telemetry.ts index 7b6f458b17b55..e8fae460cf1ea 100755 --- a/packages/next/src/cli/next-telemetry.ts +++ b/packages/next/src/cli/next-telemetry.ts @@ -1,21 +1,9 @@ #!/usr/bin/env node import chalk from 'next/dist/compiled/chalk' -import arg from 'next/dist/compiled/arg/index.js' import { CliCommand } from '../lib/commands' import { Telemetry } from '../telemetry/storage' -import { getValidatedArgs } from '../lib/get-validated-args' - -const nextTelemetry: CliCommand = (argv) => { - const validArgs: arg.Spec = { - // Types - '--enable': Boolean, - '--disable': Boolean, - '--help': Boolean, - // Aliases - '-h': '--help', - } - const args = getValidatedArgs(validArgs, argv) +const nextTelemetry: CliCommand = (args) => { if (args['--help']) { console.log( ` diff --git a/packages/next/src/lib/command-args.ts b/packages/next/src/lib/command-args.ts new file mode 100644 index 0000000000000..00d1e4164e020 --- /dev/null +++ b/packages/next/src/lib/command-args.ts @@ -0,0 +1,17 @@ +import { getValidatedArgs } from './get-validated-args' + +export type CliCommand = (args: ReturnType) => void + +export const commandArgs: { + [command: string]: () => Parameters[0] +} = { + build: () => require('../cli/next-build-args').validArgs, + start: () => require('../cli/next-start-args').validArgs, + export: () => require('../cli/next-export-args').validArgs, + dev: () => require('../cli/next-dev-args').validArgs, + lint: () => require('../cli/next-lint-args').validArgs, + telemetry: () => require('../cli/next-telemetry-args').validArgs, + info: () => require('../cli/next-info-args').validArgs, + 'experimental-compile': () => require('../cli/next-build-args').validArgs, + 'experimental-generate': () => require('../cli/next-build-args').validArgs, +} diff --git a/packages/next/src/lib/commands.ts b/packages/next/src/lib/commands.ts index fdc6c0ff51976..d921a8583cb56 100644 --- a/packages/next/src/lib/commands.ts +++ b/packages/next/src/lib/commands.ts @@ -1,4 +1,6 @@ -export type CliCommand = (argv?: string[]) => void +import { getValidatedArgs } from './get-validated-args' + +export type CliCommand = (args: ReturnType) => void export const commands: { [command: string]: () => Promise } = { build: () => Promise.resolve(require('../cli/next-build').nextBuild), diff --git a/packages/next/src/server/api-utils/node.ts b/packages/next/src/server/api-utils/node.ts index 44f36814bc74f..40ea977cf9f9b 100644 --- a/packages/next/src/server/api-utils/node.ts +++ b/packages/next/src/server/api-utils/node.ts @@ -35,7 +35,6 @@ import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, } from '../../lib/constants' -import { invokeRequest } from '../lib/server-ipc/invoke-request' export function tryGetPreviewData( req: IncomingMessage | BaseNextRequest | Request, @@ -460,30 +459,6 @@ async function revalidate( throw new Error(`Invalid response ${res.status}`) } } else if (context.revalidate) { - // We prefer to use the IPC call if running under the workers mode. - const ipcPort = process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT - if (ipcPort) { - const ipcKey = process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY - const res = await invokeRequest( - `http://${ - context.hostname || 'localhost' - }:${ipcPort}?key=${ipcKey}&method=revalidate&args=${encodeURIComponent( - JSON.stringify([{ urlPath, revalidateHeaders, opts }]) - )}`, - { - method: 'GET', - headers: {}, - } - ) - const result = await res.json() - - if (result.err) { - throw new Error(result.err.message) - } - - return - } - await context.revalidate({ urlPath, revalidateHeaders, diff --git a/packages/next/src/server/base-http/node.ts b/packages/next/src/server/base-http/node.ts index 7d99b8c198b1e..64d8f8cedf96e 100644 --- a/packages/next/src/server/base-http/node.ts +++ b/packages/next/src/server/base-http/node.ts @@ -17,7 +17,7 @@ type Req = IncomingMessage & { export class NodeNextRequest extends BaseNextRequest { public headers = this._req.headers; - [NEXT_REQUEST_META]: RequestMeta = {} + [NEXT_REQUEST_META]: RequestMeta = this._req[NEXT_REQUEST_META] || {} get originalRequest() { // Need to mimic these changes to the original req object for places where we use it: diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 03c940413ebc9..f36931610e769 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -384,6 +384,7 @@ export default abstract class Server { hostname, port, } = options + this.serverOptions = options this.isRenderWorker = options._renderWorker @@ -1271,12 +1272,21 @@ export default abstract class Server { res, parsedUrl ) - if (!result.finished) { - res.setHeader('x-middleware-next', '1') - res.body('') - res.send() + + if (result.finished) { + return + } else { + const err = new Error() + ;(err as any).result = { + response: new Response(null, { + headers: { + 'x-middleware-next': '1', + }, + }), + } + ;(err as any).bubble = true + throw err } - return } // ensure we strip the basePath when not using an invoke header @@ -1290,6 +1300,10 @@ export default abstract class Server { res.statusCode = 200 return await this.run(req, res, parsedUrl) } catch (err: any) { + if (err instanceof NoFallbackError) { + throw err + } + if ( (err && typeof err === 'object' && err.code === 'ERR_INVALID_URL') || err instanceof DecodeError || @@ -1299,7 +1313,7 @@ export default abstract class Server { return this.renderError(null, req, res, '/_error', {}) } - if (this.minimalMode || this.renderOpts.dev) { + if (this.minimalMode || this.renderOpts.dev || (err as any).bubble) { throw err } this.logError(getProperError(err)) diff --git a/packages/next/src/server/config-utils.ts b/packages/next/src/server/config-utils.ts index 9270cf3c46f22..bf6431ad8672c 100644 --- a/packages/next/src/server/config-utils.ts +++ b/packages/next/src/server/config-utils.ts @@ -10,7 +10,7 @@ export function loadWebpackHook() { // hook the Node.js require so that webpack requires are // routed to the bundled and now initialized webpack version - require('../server/require-hook').addHookAliases( + require('../server/import-overrides').addHookAliases( [ ['webpack', 'next/dist/compiled/webpack/webpack-lib'], ['webpack/package', 'next/dist/compiled/webpack/package'], diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index 64eb5985699a2..9c08a33db917e 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -19,6 +19,7 @@ import { loadEnvConfig, updateInitialEnv } from '@next/env' import { flushAndExit } from '../telemetry/flush-and-exit' import { findRootDir } from '../lib/find-root' import { setHttpClientAndAgentOptions } from './setup-http-agent-env' +import { pathHasPrefix } from '../shared/lib/router/utils/path-has-prefix' export { DomainLocale, NextConfig, normalizeConfig } from './config-shared' @@ -266,19 +267,21 @@ function assignDefaults( ) } - if (images.path === imageConfigDefault.path && result.basePath) { + if ( + images.path === imageConfigDefault.path && + result.basePath && + !pathHasPrefix(images.path, result.basePath) + ) { images.path = `${result.basePath}${images.path}` } // Append trailing slash for non-default loaders and when trailingSlash is set - if (images.path) { - if ( - (images.loader !== 'default' && - images.path[images.path.length - 1] !== '/') || - result.trailingSlash - ) { - images.path += '/' - } + if ( + images.path && + !images.path.endsWith('/') && + (images.loader !== 'default' || result.trailingSlash) + ) { + images.path += '/' } if (images.loaderFile) { diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index 912708a591b70..5512665b79eef 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -714,9 +714,11 @@ export default class HotReloader implements NextJsHotReloaderInterface { const startSpan = this.hotReloaderSpan.traceChild('start') startSpan.stop() // Stop immediately to create an artificial parent span + const testMode = process.env.NEXT_TEST_MODE || process.env.__NEXT_TEST_MODE + this.versionInfo = await this.getVersionInfo( startSpan, - !!process.env.NEXT_TEST_MODE || this.telemetry.isEnabled + !!testMode || this.telemetry.isEnabled ) await this.clean(startSpan) diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 839fa1b13c0e4..e8c3f8e68ccec 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -63,12 +63,7 @@ import { DefaultFileReader } from '../future/route-matcher-providers/dev/helpers import { NextBuildContext } from '../../build/build-context' import { IncrementalCache } from '../lib/incremental-cache' import LRUCache from 'next/dist/compiled/lru-cache' -import { errorToJSON } from '../render' import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' -import { - deserializeErr, - invokeIpcMethod, -} from '../lib/server-ipc/request-utils' // Load ReactDevOverlay only when needed let ReactDevOverlayImpl: FunctionComponent @@ -102,6 +97,10 @@ export default class DevServer extends Server { UnwrapPromise> > + private invokeDevMethod({ method, args }: { method: string; args: any[] }) { + return (global as any)._nextDevHandlers[method](this.dir, ...args) + } + protected staticPathsWorker?: { [key: string]: any } & { loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths } @@ -390,7 +389,7 @@ export default class DevServer extends Server { } response.statusCode = 500 - this.renderError(err, request, response, parsedUrl.pathname) + await this.renderError(err, request, response, parsedUrl.pathname) return { finished: true } } } @@ -419,7 +418,7 @@ export default class DevServer extends Server { const err = getProperError(error) const { req, res, page } = params res.statusCode = 500 - this.renderError(err, req, res, page) + await this.renderError(err, req, res, page) return null } } @@ -489,12 +488,9 @@ export default class DevServer extends Server { type?: 'unhandledRejection' | 'uncaughtException' | 'warning' | 'app-dir' ): Promise { if (this.isRenderWorker) { - await invokeIpcMethod({ - fetchHostname: this.fetchHostname, + await this.invokeDevMethod({ method: 'logErrorWithOriginalStack', - args: [errorToJSON(err as Error), type], - ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, - ipcKey: process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY, + args: [err, type], }) return } @@ -738,12 +734,9 @@ export default class DevServer extends Server { throw new Error('Invariant ensurePage called outside render worker') } - await invokeIpcMethod({ - fetchHostname: this.fetchHostname, + await this.invokeDevMethod({ method: 'ensurePage', args: [opts], - ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, - ipcKey: process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY, }) } @@ -802,12 +795,9 @@ export default class DevServer extends Server { protected async getFallbackErrorComponents(): Promise { if (this.isRenderWorker) { - await invokeIpcMethod({ - fetchHostname: this.fetchHostname, + await this.invokeDevMethod({ method: 'getFallbackErrorComponents', args: [], - ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, - ipcKey: process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY, }) return await loadDefaultErrorComponents(this.distDir) } @@ -818,14 +808,10 @@ export default class DevServer extends Server { async getCompilationError(page: string): Promise { if (this.isRenderWorker) { - const err = await invokeIpcMethod({ - fetchHostname: this.fetchHostname, + return await this.invokeDevMethod({ method: 'getCompilationError', args: [page], - ipcPort: process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT, - ipcKey: process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY, }) - return deserializeErr(err) } throw new Error( 'Invariant getCompilationError called outside render worker' diff --git a/packages/next/src/server/esm-loader.mts b/packages/next/src/server/esm-loader.mts new file mode 100644 index 0000000000000..313bde8b39aae --- /dev/null +++ b/packages/next/src/server/esm-loader.mts @@ -0,0 +1,27 @@ +import module from 'module' + +const require = module.createRequire(import.meta.url) + +export function resolve(specifier: string, context: any, nextResolve: any) { + const { overrideReact, hookPropertyMap } = require(process.env.NEXT_YARN_PNP + ? './import-overrides' + : 'next/dist/server/import-overrides') as typeof import('./import-overrides') + + // In case the environment variable is set after the module is loaded. + overrideReact() + + const hookResolved = hookPropertyMap.get(specifier) + if (hookResolved) { + specifier = hookResolved + } + + if (specifier.endsWith('next/dist/bin/next')) { + return { + url: specifier, + shortCircuit: true, + format: 'commonjs', + } + } + + return nextResolve(specifier, context) +} diff --git a/packages/next/src/server/require-hook.ts b/packages/next/src/server/import-overrides.ts similarity index 56% rename from packages/next/src/server/require-hook.ts rename to packages/next/src/server/import-overrides.ts index 1f8688d25dcf2..db203e6d04644 100644 --- a/packages/next/src/server/require-hook.ts +++ b/packages/next/src/server/import-overrides.ts @@ -1,29 +1,26 @@ -// Synchronously inject a require hook for webpack and webpack/. It's required to use the internal ncc webpack version. -// This is needed for userland plugins to attach to the same webpack instance as Next.js'. -// Individually compiled modules are as defined for the compilation in bundles/webpack/packages/*. +const { dirname } = require('path') as typeof import('path') -import path, { dirname } from 'path' - -// This module will only be loaded once per process. - -const mod = require('module') -const resolveFilename = mod._resolveFilename -const originalRequire = mod.prototype.require -const hookPropertyMap = new Map() - -let aliasedPrebundledReact = false - -const resolve = process.env.NEXT_MINIMAL +let resolve: typeof require.resolve = process.env.NEXT_MINIMAL ? // @ts-ignore __non_webpack_require__.resolve : require.resolve -const toResolveMap = (map: Record): [string, string][] => - Object.entries(map).map(([key, value]) => [key, resolve(value)]) +let nextPaths: undefined | { paths: string[] | undefined } = undefined + +if (!process.env.NEXT_MINIMAL) { + nextPaths = { + paths: resolve.paths('next/package.json') || undefined, + } +} +export const hookPropertyMap = new Map() export const defaultOverrides = { - 'styled-jsx': dirname(resolve('styled-jsx/package.json')), - 'styled-jsx/style': resolve('styled-jsx/style'), + 'styled-jsx': process.env.NEXT_MINIMAL + ? dirname(resolve('styled-jsx/package.json')) + : dirname(resolve('styled-jsx/package.json', nextPaths)), + 'styled-jsx/style': process.env.NEXT_MINIMAL + ? resolve('styled-jsx/style') + : resolve('styled-jsx/style', nextPaths), } export const baseOverrides = { @@ -73,6 +70,11 @@ export const experimentalOverrides = { 'next/dist/compiled/react-server-dom-webpack-experimental/server.node', } +let aliasedPrebundledReact = false + +const toResolveMap = (map: Record): [string, string][] => + Object.entries(map).map(([key, value]) => [key, resolve(value, nextPaths)]) + export function addHookAliases(aliases: [string, string][] = []) { for (const [key, value] of aliases) { hookPropertyMap.set(key, value) @@ -82,8 +84,8 @@ export function addHookAliases(aliases: [string, string][] = []) { addHookAliases(toResolveMap(defaultOverrides)) // Override built-in React packages if necessary -function overrideReact() { - if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT) { +export function overrideReact() { + if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT && !aliasedPrebundledReact) { aliasedPrebundledReact = true // Require these modules with static paths to make sure they are tracked by @@ -97,45 +99,3 @@ function overrideReact() { } } overrideReact() - -mod._resolveFilename = function ( - originalResolveFilename: typeof resolveFilename, - requestMap: Map, - request: string, - parent: any, - isMain: boolean, - options: any -) { - if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT && !aliasedPrebundledReact) { - // In case the environment variable is set after the module is loaded. - overrideReact() - } - - const hookResolved = requestMap.get(request) - if (hookResolved) request = hookResolved - return originalResolveFilename.call(mod, request, parent, isMain, options) - - // We use `bind` here to avoid referencing outside variables to create potential memory leaks. -}.bind(null, resolveFilename, hookPropertyMap) - -// This is a hack to make sure that if a user requires a Next.js module that wasn't bundled -// that needs to point to the rendering runtime version, it will point to the correct one. -// This can happen on `pages` when a user requires a dependency that uses next/image for example. -// This is only needed in production as in development we fallback to the external version. -if (process.env.NODE_ENV !== 'development' && !process.env.TURBOPACK) { - mod.prototype.require = function (request: string) { - if (request.endsWith('.shared-runtime')) { - const currentRuntime = `${ - // this env var is only set in app router - !!process.env.__NEXT_PRIVATE_PREBUNDLED_REACT - ? 'next/dist/compiled/next-server/app-page.runtime' - : 'next/dist/compiled/next-server/pages.runtime' - }.prod` - const base = path.basename(request, '.shared-runtime') - const camelized = base.replace(/-([a-z])/g, (g) => g[1].toUpperCase()) - const instance = originalRequire.call(this, currentRuntime) - return instance.default.sharedModules[camelized] - } - return originalRequire.call(this, request) - } -} diff --git a/packages/next/src/server/lib/get-esm-loader-path.ts b/packages/next/src/server/lib/get-esm-loader-path.ts new file mode 100644 index 0000000000000..7038142d4d00d --- /dev/null +++ b/packages/next/src/server/lib/get-esm-loader-path.ts @@ -0,0 +1,30 @@ +export function getEsmLoaderPath() { + let esmLoaderPath = 'next/dist/esm/server/esm-loader.mjs' + + // Since loaders don't stack Yarn PnP's loader isn't + // applied when loading our loader so we need to move + // it outside of the PnP cache + // x-ref: https://github.com/yarnpkg/berry/issues/3700 + if (process.versions.pnp) { + process.env.NEXT_YARN_PNP = '1' + const fs = require('fs') as typeof import('fs') + const path = require('path') as typeof import('path') + const tmpDir = path.join( + process.env.NEXT_PRIVATE_DIR || process.cwd(), + `.next/loader` + ) + fs.mkdirSync(tmpDir, { recursive: true }) + + for (const file of [esmLoaderPath, 'next/dist/server/import-overrides']) { + const resolvedFile = require.resolve(file) + const newFile = path.join(tmpDir, path.basename(file)) + + fs.copyFileSync(resolvedFile, newFile) + + if (file === esmLoaderPath) { + esmLoaderPath = newFile + } + } + } + return esmLoaderPath +} diff --git a/packages/next/src/server/lib/mock-request.ts b/packages/next/src/server/lib/mock-request.ts index 2038751122596..90a643328ac42 100644 --- a/packages/next/src/server/lib/mock-request.ts +++ b/packages/next/src/server/lib/mock-request.ts @@ -18,6 +18,7 @@ interface MockedRequestOptions { url: string headers: IncomingHttpHeaders method: string + readable?: Stream.Readable socket?: Socket | null } @@ -33,6 +34,8 @@ export class MockedRequest extends Stream.Readable implements IncomingMessage { public readonly httpVersionMajor = 1 public readonly httpVersionMinor = 0 + private bodyReadable?: Stream.Readable + // If we don't actually have a socket, we'll just use a mock one that // always returns false for the `encrypted` property. public socket: Socket = new Proxy({} as TLSSocket, { @@ -47,13 +50,25 @@ export class MockedRequest extends Stream.Readable implements IncomingMessage { }, }) - constructor({ url, headers, method, socket = null }: MockedRequestOptions) { + constructor({ + url, + headers, + method, + socket = null, + readable, + }: MockedRequestOptions) { super() this.url = url this.headers = headers this.method = method + if (readable) { + this.bodyReadable = readable + this.bodyReadable.on('end', () => this.emit('end')) + this.bodyReadable.on('close', () => this.emit('close')) + } + if (socket) { this.socket = socket } @@ -70,9 +85,13 @@ export class MockedRequest extends Stream.Readable implements IncomingMessage { return headers } - public _read(): void { - this.emit('end') - this.emit('close') + public _read(size: number): void { + if (this.bodyReadable) { + return this.bodyReadable._read(size) + } else { + this.emit('end') + this.emit('close') + } } /** @@ -120,6 +139,7 @@ export interface MockedResponseOptions { statusCode?: number socket?: Socket | null headers?: OutgoingHttpHeaders + resWriter?: (chunk: Buffer | string) => boolean } export class MockedResponse extends Stream.Writable implements ServerResponse { @@ -151,6 +171,11 @@ export class MockedResponse extends Stream.Writable implements ServerResponse { */ public readonly headers: Headers + private resWriter: MockedResponseOptions['resWriter'] + + public readonly headPromise: Promise + private headPromiseResolve?: () => void + constructor(res: MockedResponseOptions = {}) { super() @@ -160,13 +185,24 @@ export class MockedResponse extends Stream.Writable implements ServerResponse { ? fromNodeOutgoingHttpHeaders(res.headers) : new Headers() + this.headPromise = new Promise((resolve) => { + this.headPromiseResolve = resolve + }) + // Attach listeners for the `finish`, `end`, and `error` events to the // `MockedResponse` instance. this.hasStreamed = new Promise((resolve, reject) => { this.on('finish', () => resolve(true)) this.on('end', () => resolve(true)) this.on('error', (err) => reject(err)) + }).then((val) => { + this.headPromiseResolve?.() + return val }) + + if (res.resWriter) { + this.resWriter = res.resWriter + } } public appendHeader(name: string, value: string | string[]): this { @@ -197,6 +233,9 @@ export class MockedResponse extends Stream.Writable implements ServerResponse { } public write(chunk: Buffer | string) { + if (this.resWriter) { + return this.resWriter(chunk) + } this.buffers.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) return true @@ -285,6 +324,7 @@ export class MockedResponse extends Stream.Writable implements ServerResponse { this.statusCode = statusCode this.headersSent = true + this.headPromiseResolve?.() return this } @@ -395,6 +435,8 @@ interface RequestResponseMockerOptions { url: string headers?: IncomingHttpHeaders method?: string + bodyReadable?: Stream.Readable + resWriter?: (chunk: Buffer | string) => boolean socket?: Socket | null } @@ -402,10 +444,18 @@ export function createRequestResponseMocks({ url, headers = {}, method = 'GET', + bodyReadable, + resWriter, socket = null, }: RequestResponseMockerOptions) { return { - req: new MockedRequest({ url, headers, method, socket }), - res: new MockedResponse({ socket }), + req: new MockedRequest({ + url, + headers, + method, + socket, + readable: bodyReadable, + }), + res: new MockedResponse({ socket, resWriter }), } } diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index 9c2eee1fd2086..03dfd056b6077 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -1,21 +1,22 @@ -import type { RequestHandler } from '../next' +import type { NextServer, RequestHandler } from '../next' -// this must come first as it includes require hooks -import { initializeServerWorker } from './setup-server-worker' -import { formatHostname } from './format-hostname' import next from '../next' import { PropagateToWorkersField } from './router-utils/types' -export const WORKER_SELF_EXIT_CODE = 77 - -let result: +const result: Record< + string, | undefined | { - port: number - hostname: string + requestHandler: ReturnType< + InstanceType['getRequestHandler'] + > + upgradeHandler: ReturnType< + InstanceType['getUpgradeHandler'] + > } +> = {} -let app: ReturnType | undefined +let apps: Record | undefined> = {} let sandboxContext: undefined | typeof import('../web/sandbox/context') let requireCacheHotReloader: @@ -42,9 +43,11 @@ export function deleteCache(filePaths: string[]) { } export async function propagateServerField( + dir: string, field: PropagateToWorkersField, value: any ) { + const app = apps[dir] if (!app) { throw new Error('Invariant cant propagate server field, no app initialized') } @@ -72,12 +75,15 @@ export async function initialize(opts: { isNodeDebugging: boolean keepAliveTimeout?: number serverFields?: any + server?: any experimentalTestProxy: boolean -}): Promise> { + _ipcPort?: string + _ipcKey?: string +}) { // if we already setup the server return as we only need to do // this on first worker boot - if (result) { - return result + if (result[opts.dir]) { + return result[opts.dir] } const type = process.env.__NEXT_PRIVATE_RENDER_WORKER @@ -88,35 +94,25 @@ export async function initialize(opts: { let requestHandler: RequestHandler let upgradeHandler: any - const { port, server, hostname } = await initializeServerWorker( - (...args) => { - return requestHandler(...args) - }, - (...args) => { - return upgradeHandler(...args) - }, - opts - ) - - app = next({ + const app = next({ ...opts, _routerWorker: opts.workerType === 'router', _renderWorker: opts.workerType === 'render', - hostname, + hostname: opts.hostname || 'localhost', customServer: false, - httpServer: server, + httpServer: opts.server, port: opts.port, isNodeDebugging: opts.isNodeDebugging, }) - + apps[opts.dir] = app requestHandler = app.getRequestHandler() upgradeHandler = app.getUpgradeHandler() + await app.prepare(opts.serverFields) - result = { - port, - hostname: formatHostname(hostname), + result[opts.dir] = { + requestHandler, + upgradeHandler, } - - return result + return result[opts.dir] } diff --git a/packages/next/src/server/lib/route-resolver.ts b/packages/next/src/server/lib/route-resolver.ts index 8fd36f3967af8..45d013f819082 100644 --- a/packages/next/src/server/lib/route-resolver.ts +++ b/packages/next/src/server/lib/route-resolver.ts @@ -6,7 +6,6 @@ import '../node-polyfill-fetch' import url from 'url' import path from 'path' -import http from 'http' import { findPageFile } from './find-page-file' import { getRequestMeta } from '../request-meta' import setupDebug from 'next/dist/compiled/debug' @@ -16,7 +15,6 @@ import { setupFsCheck } from './router-utils/filesystem' import { proxyRequest } from './router-utils/proxy-request' import { getResolveRoutes } from './router-utils/resolve-routes' import { PERMANENT_REDIRECT_STATUS } from '../../shared/lib/constants' -import { splitCookiesString, toNodeOutgoingHttpHeaders } from '../web/utils' import { formatHostname } from './format-hostname' import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' @@ -109,83 +107,6 @@ export async function makeResolver( } : {} - const middlewareServerAddr = await new Promise<{ - hostname: string - port: number - }>((resolve) => { - const srv = http.createServer(async (req, res) => { - const cloneableBody = getCloneableBody(req) - try { - const { run } = - require('../web/sandbox') as typeof import('../web/sandbox') - - const result = await run({ - distDir, - name: middlewareInfo.name || '/', - paths: middlewareInfo.paths || [], - edgeFunctionEntry: middlewareInfo, - request: { - headers: req.headers, - method: req.method || 'GET', - nextConfig: { - i18n: nextConfig.i18n, - basePath: nextConfig.basePath, - trailingSlash: nextConfig.trailingSlash, - }, - url: `http://${fetchHostname}:${port}${req.url}`, - body: cloneableBody, - signal: signalFromNodeResponse(res), - }, - useCache: true, - onWarning: console.warn, - }) - - for (let [key, value] of result.response.headers) { - if (key.toLowerCase() !== 'set-cookie') continue - - // Clear existing header. - result.response.headers.delete(key) - - // Append each cookie individually. - const cookies = splitCookiesString(value) - for (const cookie of cookies) { - result.response.headers.append(key, cookie) - } - } - - for (const [key, value] of Object.entries( - toNodeOutgoingHttpHeaders(result.response.headers) - )) { - if (key !== 'content-encoding' && value !== undefined) { - res.setHeader(key, value as string | string[]) - } - } - res.statusCode = result.response.status - - if (result.response.body) { - await pipeReadable(result.response.body, res) - } else { - res.end() - } - } catch (err) { - console.error(err) - res.statusCode = 500 - res.end('Internal Server Error') - } - }) - srv.on('listening', () => { - const srvAddr = srv.address() - if (!srvAddr || typeof srvAddr === 'string') { - throw new Error("Failed to determine middleware's host/port.") - } - resolve({ - hostname: srvAddr.address, - port: srvAddr.port, - }) - }) - srv.listen(0) - }) - if (middleware?.files.length) { fsChecker.middlewareMatcher = getMiddlewareRouteMatcher( middleware.matcher?.map((item) => ({ @@ -210,13 +131,48 @@ export async function makeResolver( pages: { async initialize() { return { - port: middlewareServerAddr.port, - hostname: formatHostname(middlewareServerAddr.hostname), + async requestHandler(req, res) { + if (!req.headers['x-middleware-invoke']) { + throw new Error(`Invariant unexpected request handler call`) + } + + const cloneableBody = getCloneableBody(req) + const { run } = + require('../web/sandbox') as typeof import('../web/sandbox') + + const result = await run({ + distDir, + name: middlewareInfo.name || '/', + paths: middlewareInfo.paths || [], + edgeFunctionEntry: middlewareInfo, + request: { + headers: req.headers, + method: req.method || 'GET', + nextConfig: { + i18n: nextConfig.i18n, + basePath: nextConfig.basePath, + trailingSlash: nextConfig.trailingSlash, + }, + url: `http://${fetchHostname}:${port}${req.url}`, + body: cloneableBody, + signal: signalFromNodeResponse(res), + }, + useCache: true, + onWarning: console.warn, + }) + + const err = new Error() + ;(err as any).result = result + throw err + }, + async upgradeHandler() { + throw new Error(`Invariant: unexpected upgrade handler call`) + }, } }, + deleteAppClientCache() {}, async deleteCache() {}, async clearModuleContext() {}, - async deleteAppClientCache() {}, async propagateServerField() {}, } as Partial as any, }, @@ -229,6 +185,7 @@ export async function makeResolver( ): Promise { const routeResult = await resolveRoutes({ req, + res, isUpgradeReq: false, signal: signalFromNodeResponse(res), }) diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 29c0dacc49335..f95bbdf6dbbb4 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -1,4 +1,5 @@ import type { IncomingMessage } from 'http' +import type { createWorker } from './server-ipc' // this must come first as it includes require hooks import type { @@ -16,30 +17,27 @@ import path from 'path' import loadConfig from '../config' import { serveStatic } from '../serve-static' import setupDebug from 'next/dist/compiled/debug' -import { splitCookiesString, toNodeOutgoingHttpHeaders } from '../web/utils' import { Telemetry } from '../../telemetry/storage' import { DecodeError } from '../../shared/lib/utils' -import { filterReqHeaders, ipcForbiddenHeaders } from './server-ipc/utils' import { findPagesDir } from '../../lib/find-pages-dir' import { setupFsCheck } from './router-utils/filesystem' import { proxyRequest } from './router-utils/proxy-request' -import { invokeRequest } from './server-ipc/invoke-request' import { isAbortError, pipeReadable } from '../pipe-readable' import { createRequestResponseMocks } from './mock-request' -import { createIpcServer, createWorker } from './server-ipc' import { UnwrapPromise } from '../../lib/coalesced-function' import { getResolveRoutes } from './router-utils/resolve-routes' import { NextUrlWithParsedQuery, getRequestMeta } from '../request-meta' import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix' import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix' import setupCompression from 'next/dist/compiled/compression' +import { NoFallbackError } from '../base-server' +import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' import { PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER, PERMANENT_REDIRECT_STATUS, } from '../../shared/lib/constants' -import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request' const debug = setupDebug('next:router-server:main') @@ -57,10 +55,18 @@ export interface RenderWorkers { pages?: Awaited> } +const devInstances: Record< + string, + UnwrapPromise> +> = {} + +const requestHandlers: Record = {} + export async function initialize(opts: { dir: string port: number dev: boolean + server?: import('http').Server minimalMode?: boolean hostname?: string workerType: 'router' | 'render' @@ -78,10 +84,7 @@ export async function initialize(opts: { const config = await loadConfig( opts.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER, - opts.dir, - undefined, - undefined, - true + opts.dir ) let compress: ReturnType | undefined @@ -113,6 +116,7 @@ export async function initialize(opts: { const { setupDev } = (await require('./router-utils/setup-dev')) as typeof import('./router-utils/setup-dev') + devInstance = await setupDev({ // Passed here but the initialization of this object happens below, doing the initialization before the setupDev call breaks. renderWorkers, @@ -126,89 +130,84 @@ export async function initialize(opts: { turbo: !!process.env.TURBOPACK, port: opts.port, }) - } - - const { ipcPort, ipcValidationKey } = await createIpcServer({ - async ensurePage( - match: Parameters< - InstanceType< - typeof import('../dev/hot-reloader-webpack').default - >['ensurePage'] - >[0] - ) { - // TODO: remove after ensure is pulled out of server - return await devInstance?.hotReloader.ensurePage(match) - }, - async logErrorWithOriginalStack(...args: any[]) { - // @ts-ignore - return await devInstance?.logErrorWithOriginalStack(...args) - }, - async getFallbackErrorComponents() { - await devInstance?.hotReloader?.buildFallbackError() - // Build the error page to ensure the fallback is built too. - // TODO: See if this can be moved into hotReloader or removed. - await devInstance?.hotReloader.ensurePage({ - page: '/_error', - clientOnly: false, - }) - }, - async getCompilationError(page: string) { - const errors = await devInstance?.hotReloader?.getCompilationErrors(page) - if (!errors) return - - // Return the very first error we found. - return errors[0] - }, - async revalidate({ - urlPath, - revalidateHeaders, - opts: revalidateOpts, - }: { - urlPath: string - revalidateHeaders: IncomingMessage['headers'] - opts: any - }) { - const mocked = createRequestResponseMocks({ - url: urlPath, - headers: revalidateHeaders, - }) - - // eslint-disable-next-line @typescript-eslint/no-use-before-define - await requestHandler(mocked.req, mocked.res) - await mocked.res.hasStreamed - - if ( - mocked.res.getHeader('x-nextjs-cache') !== 'REVALIDATED' && - !( - mocked.res.statusCode === 404 && revalidateOpts.unstable_onlyGenerated + devInstances[opts.dir] = devInstance + ;(global as any)._nextDevHandlers = { + async ensurePage( + dir: string, + match: Parameters< + InstanceType< + typeof import('../dev/hot-reloader-webpack').default + >['ensurePage'] + >[0] + ) { + const curDevInstance = devInstances[dir] + // TODO: remove after ensure is pulled out of server + return await curDevInstance?.hotReloader.ensurePage(match) + }, + async logErrorWithOriginalStack(dir: string, ...args: any[]) { + const curDevInstance = devInstances[dir] + // @ts-ignore + return await curDevInstance?.logErrorWithOriginalStack(...args) + }, + async getFallbackErrorComponents(dir: string) { + const curDevInstance = devInstances[dir] + await curDevInstance.hotReloader.buildFallbackError() + // Build the error page to ensure the fallback is built too. + // TODO: See if this can be moved into hotReloader or removed. + await curDevInstance.hotReloader.ensurePage({ + page: '/_error', + clientOnly: false, + }) + }, + async getCompilationError(dir: string, page: string) { + const curDevInstance = devInstances[dir] + const errors = await curDevInstance?.hotReloader?.getCompilationErrors( + page ) + if (!errors) return + + // Return the very first error we found. + return errors[0] + }, + async revalidate( + dir: string, + { + urlPath, + revalidateHeaders, + opts: revalidateOpts, + }: { + urlPath: string + revalidateHeaders: IncomingMessage['headers'] + opts: any + } ) { - throw new Error(`Invalid response ${mocked.res.statusCode}`) - } - return {} - }, - } as any) - - // Set global environment variables for the app render server to use. - process.env.__NEXT_PRIVATE_ROUTER_IPC_PORT = ipcPort + '' - process.env.__NEXT_PRIVATE_ROUTER_IPC_KEY = ipcValidationKey - process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = config.experimental - .serverActions - ? 'experimental' - : 'next' + const mocked = createRequestResponseMocks({ + url: urlPath, + headers: revalidateHeaders, + }) + const curRequestHandler = requestHandlers[dir] + // eslint-disable-next-line @typescript-eslint/no-use-before-define + await curRequestHandler(mocked.req, mocked.res) + await mocked.res.hasStreamed + + if ( + mocked.res.getHeader('x-nextjs-cache') !== 'REVALIDATED' && + !( + mocked.res.statusCode === 404 && + revalidateOpts.unstable_onlyGenerated + ) + ) { + throw new Error(`Invalid response ${mocked.res.statusCode}`) + } + return {} + }, + } as any + } renderWorkers.app = require('./render-server') as typeof import('./render-server') - const { initialEnv } = require('@next/env') as typeof import('@next/env') - renderWorkers.pages = await createWorker( - ipcPort, - ipcValidationKey, - opts.isNodeDebugging, - 'pages', - config, - initialEnv - ) + renderWorkers.pages = renderWorkers.app const renderWorkerOpts: Parameters[0] = { port: opts.port, @@ -217,65 +216,17 @@ export async function initialize(opts: { hostname: opts.hostname, minimalMode: opts.minimalMode, dev: !!opts.dev, + server: opts.server, isNodeDebugging: !!opts.isNodeDebugging, serverFields: devInstance?.serverFields || {}, experimentalTestProxy: !!opts.experimentalTestProxy, } // pre-initialize workers + const handlers = await renderWorkers.app?.initialize(renderWorkerOpts) const initialized = { - app: await renderWorkers.app?.initialize(renderWorkerOpts), - pages: await renderWorkers.pages?.initialize(renderWorkerOpts), - } - - if (devInstance) { - const originalNextDeleteCache = (global as any)._nextDeleteCache - ;(global as any)._nextDeleteCache = async (filePaths: string[]) => { - // Multiple instances of Next.js can be instantiated, since this is a global we have to call the original if it exists. - if (originalNextDeleteCache) { - await originalNextDeleteCache(filePaths) - } - try { - await Promise.all([ - renderWorkers.pages?.deleteCache(filePaths), - renderWorkers.app?.deleteCache(filePaths), - ]) - } catch (err) { - console.error(err) - } - } - const originalNextDeleteAppClientCache = (global as any) - ._nextDeleteAppClientCache - ;(global as any)._nextDeleteAppClientCache = async () => { - // Multiple instances of Next.js can be instantiated, since this is a global we have to call the original if it exists. - if (originalNextDeleteAppClientCache) { - await originalNextDeleteAppClientCache() - } - try { - await Promise.all([ - renderWorkers.pages?.deleteAppClientCache(), - renderWorkers.app?.deleteAppClientCache(), - ]) - } catch (err) { - console.error(err) - } - } - const originalNextClearModuleContext = (global as any) - ._nextClearModuleContext - ;(global as any)._nextClearModuleContext = async (targetPath: string) => { - // Multiple instances of Next.js can be instantiated, since this is a global we have to call the original if it exists. - if (originalNextClearModuleContext) { - await originalNextClearModuleContext() - } - try { - await Promise.all([ - renderWorkers.pages?.clearModuleContext(targetPath), - renderWorkers.app?.clearModuleContext(targetPath), - ]) - } catch (err) { - console.error(err) - } - } + app: handlers, + pages: handlers, } const logError = async ( @@ -331,8 +282,8 @@ export async function initialize(opts: { async function invokeRender( parsedUrl: NextUrlWithParsedQuery, type: keyof typeof renderWorkers, - handleIndex: number, invokePath: string, + handleIndex: number, additionalInvokeHeaders: Record = {} ) { // invokeRender expects /api routes to not be locale prefixed @@ -366,8 +317,6 @@ export async function initialize(opts: { throw new Error(`Failed to initialize render worker ${type}`) } - const renderUrl = `http://${workerResult.hostname}:${workerResult.port}${req.url}` - const invokeHeaders: typeof req.headers = { ...req.headers, 'x-middleware-invoke': '', @@ -375,20 +324,26 @@ export async function initialize(opts: { 'x-invoke-query': encodeURIComponent(JSON.stringify(parsedUrl.query)), ...(additionalInvokeHeaders || {}), } + Object.assign(req.headers, invokeHeaders) - debug('invokeRender', renderUrl, invokeHeaders) + debug('invokeRender', req.url, invokeHeaders) - let invokeRes try { - invokeRes = await invokeRequest( - renderUrl, - { - headers: invokeHeaders, - method: req.method, - signal: signalFromNodeResponse(res), - }, - getRequestMeta(req, '__NEXT_CLONABLE_BODY')?.cloneBodyStream() + const initResult = await renderWorkers.pages?.initialize( + renderWorkerOpts ) + + try { + await initResult?.requestHandler(req, res) + } catch (err) { + if (err instanceof NoFallbackError) { + // eslint-disable-next-line + await handleRequest(handleIndex + 1) + return + } + throw err + } + return } catch (e) { // If the client aborts before we can receive a response object (when // the headers are flushed), then we can early exit without further @@ -398,54 +353,6 @@ export async function initialize(opts: { } throw e } - - debug('invokeRender res', invokeRes.status, invokeRes.headers) - - // when we receive x-no-fallback we restart - if (invokeRes.headers.get('x-no-fallback')) { - // eslint-disable-next-line - await handleRequest(handleIndex + 1) - return - } - - for (const [key, value] of Object.entries( - filterReqHeaders( - toNodeOutgoingHttpHeaders(invokeRes.headers), - ipcForbiddenHeaders - ) - )) { - if (value !== undefined) { - if (key === 'set-cookie') { - const curValue = res.getHeader(key) as string - const newValue: string[] = [] as string[] - - for (const cookie of Array.isArray(curValue) - ? curValue - : splitCookiesString(curValue || '')) { - newValue.push(cookie) - } - for (const val of (Array.isArray(value) - ? value - : value - ? [value] - : []) as string[]) { - newValue.push(val) - } - res.setHeader(key, newValue) - } else { - res.setHeader(key, value as string) - } - } - } - res.statusCode = invokeRes.status || 200 - res.statusMessage = invokeRes.statusText || '' - - if (invokeRes.body) { - await pipeReadable(invokeRes.body, res) - } else { - res.end() - } - return } const handleRequest = async (handleIndex: number) => { @@ -483,11 +390,16 @@ export async function initialize(opts: { matchedOutput, } = await resolveRoutes({ req, + res, isUpgradeReq: false, signal: signalFromNodeResponse(res), invokedOutputs, }) + if (res.closed || res.finished) { + return + } + if (devInstance && matchedOutput?.type === 'devVirtualFsItem') { const origUrl = req.url || '/' @@ -561,7 +473,8 @@ export async function initialize(opts: { (fsChecker.appFiles.has(matchedOutput.itemPath) || fsChecker.pageFiles.has(matchedOutput.itemPath)) ) { - await invokeRender(parsedUrl, 'pages', handleIndex, '/_error', { + res.statusCode = 500 + await invokeRender(parsedUrl, 'pages', '/_error', handleIndex, { 'x-invoke-status': '500', 'x-invoke-error': JSON.stringify({ message: `A conflicting public file and page file was found for path ${matchedOutput.itemPath} https://nextjs.org/docs/messages/conflicting-public-file-page`, @@ -585,11 +498,12 @@ export async function initialize(opts: { } if (!(req.method === 'GET' || req.method === 'HEAD')) { res.setHeader('Allow', ['GET', 'HEAD']) + res.statusCode = 405 return await invokeRender( url.parse('/405', true), 'pages', - handleIndex, '/405', + handleIndex, { 'x-invoke-status': '405', } @@ -648,11 +562,12 @@ export async function initialize(opts: { if (typeof err.statusCode === 'number') { const invokePath = `/${err.statusCode}` const invokeStatus = `${err.statusCode}` + res.statusCode = err.statusCode return await invokeRender( url.parse(invokePath, true), 'pages', - handleIndex, invokePath, + handleIndex, { 'x-invoke-status': invokeStatus, } @@ -668,8 +583,8 @@ export async function initialize(opts: { return await invokeRender( parsedUrl, matchedOutput.type === 'appFile' ? 'app' : 'pages', - handleIndex, parsedUrl.pathname || '/', + handleIndex, { 'x-invoke-output': matchedOutput.itemPath, } @@ -693,18 +608,21 @@ export async function initialize(opts: { ? devInstance?.serverFields.hasAppNotFound : await fsChecker.getItem('/_not-found') + res.statusCode = 404 + if (appNotFound) { return await invokeRender( parsedUrl, 'app', - handleIndex, opts.dev ? '/not-found' : '/_not-found', + handleIndex, { 'x-invoke-status': '404', } ) } - await invokeRender(parsedUrl, 'pages', handleIndex, '/404', { + + await invokeRender(parsedUrl, 'pages', '/404', handleIndex, { 'x-invoke-status': '404', }) } @@ -722,11 +640,12 @@ export async function initialize(opts: { } else { console.error(err) } + res.statusCode = Number(invokeStatus) return await invokeRender( url.parse(invokePath, true), 'pages', - 0, invokePath, + 0, { 'x-invoke-status': invokeStatus, } @@ -749,6 +668,7 @@ export async function initialize(opts: { requestHandler = wrapRequestHandlerWorker(requestHandler) interceptTestApis() } + requestHandlers[opts.dir] = requestHandler const upgradeHandler: WorkerUpgradeHandler = async (req, socket, head) => { try { @@ -769,6 +689,7 @@ export async function initialize(opts: { const { matchedOutput, parsedUrl } = await resolveRoutes({ req, + res: socket as any, isUpgradeReq: true, signal: signalFromNodeResponse(socket), }) diff --git a/packages/next/src/server/lib/router-utils/resolve-routes.ts b/packages/next/src/server/lib/router-utils/resolve-routes.ts index 3ae8d5995036d..11097b180bc8c 100644 --- a/packages/next/src/server/lib/router-utils/resolve-routes.ts +++ b/packages/next/src/server/lib/router-utils/resolve-routes.ts @@ -1,6 +1,6 @@ import type { TLSSocket } from 'tls' import type { FsOutput } from './filesystem' -import type { IncomingMessage } from 'http' +import type { IncomingMessage, ServerResponse } from 'http' import type { NextConfigComplete } from '../../config-shared' import type { RenderWorker, initialize } from '../router-server' import type { PatchMatcher } from '../../../shared/lib/router/utils/path-match' @@ -14,7 +14,6 @@ import { Header } from '../../../lib/load-custom-routes' import { stringifyQuery } from '../../server-route-utils' import { formatHostname } from '../format-hostname' import { toNodeOutgoingHttpHeaders } from '../../web/utils' -import { invokeRequest } from '../server-ipc/invoke-request' import { isAbortError } from '../../pipe-readable' import { getCookieParser, setLazyProp } from '../../api-utils' import { getHostname } from '../../../shared/lib/get-hostname' @@ -28,16 +27,15 @@ import { detectDomainLocale } from '../../../shared/lib/i18n/detect-domain-local import { normalizeLocalePath } from '../../../shared/lib/i18n/normalize-locale-path' import { removePathPrefix } from '../../../shared/lib/router/utils/remove-path-prefix' -import { - NextUrlWithParsedQuery, - addRequestMeta, - getRequestMeta, -} from '../../request-meta' +import { NextUrlWithParsedQuery, addRequestMeta } from '../../request-meta' import { compileNonPath, matchHas, prepareDestination, } from '../../../shared/lib/router/utils/prepare-destination' +import { createRequestResponseMocks } from '../mock-request' + +import '../../node-polyfill-web-streams' const debug = setupDebug('next:router-server:resolve-routes') @@ -98,11 +96,12 @@ export function getResolveRoutes( async function resolveRoutes({ req, + res, isUpgradeReq, - signal, invokedOutputs, }: { req: IncomingMessage + res: ServerResponse isUpgradeReq: boolean signal: AbortSignal invokedOutputs?: Set @@ -445,44 +444,59 @@ export function getResolveRoutes( if (!workerResult) { throw new Error(`Failed to initialize render worker "middleware"`) } - const stringifiedQuery = stringifyQuery( - req as any, - getRequestMeta(req, '__NEXT_INIT_QUERY') || {} - ) - const parsedInitUrl = new URL( - getRequestMeta(req, '__NEXT_INIT_URL') || '/', - 'http://n' - ) - - const curUrl = config.skipMiddlewareUrlNormalize - ? `${parsedInitUrl.pathname}${parsedInitUrl.search}` - : `${parsedUrl.pathname}${stringifiedQuery ? '?' : ''}${ - stringifiedQuery || '' - }` - - const renderUrl = `http://${workerResult.hostname}:${workerResult.port}${curUrl}` const invokeHeaders: typeof req.headers = { - ...req.headers, 'x-invoke-path': '', 'x-invoke-query': '', 'x-invoke-output': '', 'x-middleware-invoke': '1', } + Object.assign(req.headers, invokeHeaders) - debug('invoking middleware', renderUrl, invokeHeaders) + debug('invoking middleware', req.url, invokeHeaders) - let middlewareRes + let middlewareRes: Response | undefined = undefined + let bodyStream: ReadableStream | undefined = undefined try { - middlewareRes = await invokeRequest( - renderUrl, - { - headers: invokeHeaders, - method: req.method, - signal, + let readableController: ReadableStreamController + const { res: mockedRes } = await createRequestResponseMocks({ + url: req.url || '/', + method: req.method || 'GET', + headers: filterReqHeaders(invokeHeaders, ipcForbiddenHeaders), + resWriter(chunk) { + readableController.enqueue(Buffer.from(chunk)) + return true }, - getRequestMeta(req, '__NEXT_CLONABLE_BODY')?.cloneBodyStream() + }) + + const initResult = await renderWorkers.pages?.initialize( + renderWorkerOpts ) + + mockedRes.on('close', () => { + readableController.close() + }) + + try { + await initResult?.requestHandler(req, res, parsedUrl) + } catch (err: any) { + if (!('result' in err) || !('response' in err.result)) { + throw err + } + middlewareRes = err.result.response as Response + res.statusCode = middlewareRes.status + + if (middlewareRes.body) { + bodyStream = middlewareRes.body + } else if (middlewareRes.status) { + bodyStream = new ReadableStream({ + start(controller) { + controller.enqueue('') + controller.close() + }, + }) + } + } } catch (e) { // If the client aborts before we can receive a response object // (when the headers are flushed), then we can early exit without @@ -497,6 +511,14 @@ export function getResolveRoutes( throw e } + if (res.closed || res.finished || !middlewareRes) { + return { + parsedUrl, + resHeaders, + finished: true, + } + } + const middlewareHeaders = toNodeOutgoingHttpHeaders( middlewareRes.headers ) as Record @@ -622,7 +644,7 @@ export function getResolveRoutes( parsedUrl, resHeaders, finished: true, - bodyStream: middlewareRes.body, + bodyStream, statusCode: middlewareRes.status, } } diff --git a/packages/next/src/server/lib/router-utils/setup-dev.ts b/packages/next/src/server/lib/router-utils/setup-dev.ts index 425506a3d3460..c0019bff3ed29 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev.ts @@ -103,6 +103,7 @@ import { TurboPackConnectedAction, } from '../../dev/hot-reloader-types' import { debounce } from '../../utils' +import { deleteCache } from '../../../build/webpack/plugins/nextjs-require-cache-hot-reloader' import { normalizeMetadataRoute } from '../../../lib/metadata/get-metadata-route' const wsServer = new ws.Server({ noServer: true }) @@ -157,8 +158,8 @@ async function startWatcher(opts: SetupOpts) { ) async function propagateToWorkers(field: PropagateToWorkersField, args: any) { - await opts.renderWorkers.app?.propagateServerField(field, args) - await opts.renderWorkers.pages?.propagateServerField(field, args) + await opts.renderWorkers.app?.propagateServerField(opts.dir, field, args) + await opts.renderWorkers.pages?.propagateServerField(opts.dir, field, args) } const serverFields: { @@ -310,21 +311,20 @@ async function startWatcher(opts: SetupOpts) { async function processResult( result: TurbopackResult ): Promise> { - await (global as any)._nextDeleteCache?.( - result.serverPaths - .map((p) => path.join(distDir, p)) - .concat([ - // We need to clear the chunk cache in react - require.resolve( - 'next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js' - ), - // And this redirecting module as well - require.resolve( - 'next/dist/compiled/react-server-dom-webpack/client.edge.js' - ), - ]) - ) - + for (const file of result.serverPaths + .map((p) => path.join(distDir, p)) + .concat([ + // We need to clear the chunk cache in react + require.resolve( + 'next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js' + ), + // And this redirecting module as well + require.resolve( + 'next/dist/compiled/react-server-dom-webpack/client.edge.js' + ), + ])) { + deleteCache(file) + } return result } @@ -380,8 +380,7 @@ async function startWatcher(opts: SetupOpts) { sendHmrDebounce() } - const clearCache = (filePath: string) => - (global as any)._nextDeleteCache?.([filePath]) + const clearCache = (filePath: string) => deleteCache(filePath) async function loadPartialManifest( name: string, diff --git a/packages/next/src/server/lib/setup-server-worker.ts b/packages/next/src/server/lib/setup-server-worker.ts index ba7d195fa4ff2..eb08e1bc5d1da 100644 --- a/packages/next/src/server/lib/setup-server-worker.ts +++ b/packages/next/src/server/lib/setup-server-worker.ts @@ -17,7 +17,7 @@ process.on('uncaughtException', (err) => { console.error(err) }) -export const WORKER_SELF_EXIT_CODE = 77 +export const RESTART_EXIT_CODE = 77 const MAXIMUM_HEAP_SIZE_ALLOWED = (v8.getHeapStatistics().heap_size_limit / 1024 / 1024) * 0.9 @@ -67,7 +67,7 @@ export async function initializeServerWorker( 'The server is running out of memory, restarting to free up memory.' ) server.close() - process.exit(WORKER_SELF_EXIT_CODE) + process.exit(RESTART_EXIT_CODE) } }) }) diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 656162e850b84..b6e43bbb10130 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -1,21 +1,26 @@ +import '../next' import '../node-polyfill-fetch' import '../require-hook' import type { IncomingMessage, ServerResponse } from 'http' +import fs from 'fs' +import path from 'path' import http from 'http' import https from 'https' +import Watchpack from 'watchpack' import * as Log from '../../build/output/log' import setupDebug from 'next/dist/compiled/debug' import { getDebugPort } from './utils' import { formatHostname } from './format-hostname' import { initialize } from './router-server' -import fs from 'fs' import { + RESTART_EXIT_CODE, WorkerRequestHandler, WorkerUpgradeHandler, } from './setup-server-worker' import { checkIsNodeDebugging } from './is-node-debugging' +import { CONFIG_FILES } from '../../shared/lib/constants' import chalk from '../../lib/chalk' const debug = setupDebug('next:start-server') @@ -50,6 +55,7 @@ export async function getRequestHandlers({ dir, port, isDev, + server, hostname, minimalMode, isNodeDebugging, @@ -59,6 +65,7 @@ export async function getRequestHandlers({ dir: string port: number isDev: boolean + server?: import('http').Server hostname: string minimalMode?: boolean isNodeDebugging?: boolean @@ -71,6 +78,7 @@ export async function getRequestHandlers({ hostname, dev: isDev, minimalMode, + server, workerType: 'router', isNodeDebugging: isNodeDebugging || false, keepAliveTimeout, @@ -261,10 +269,10 @@ export async function startServer({ } try { - const cleanup = () => { + const cleanup = (code: number | null) => { debug('start-server process cleanup') server.close() - process.exit(0) + process.exit(code ?? 0) } const exception = (err: Error) => { // This is the render worker, we keep the process alive @@ -280,6 +288,7 @@ export async function startServer({ dir, port, isDev, + server, hostname, minimalMode, isNodeDebugging: Boolean(isNodeDebugging), @@ -300,4 +309,34 @@ export async function startServer({ }) server.listen(port, hostname) }) + + if (isDev) { + function watchConfigFiles( + dirToWatch: string, + onChange: (filename: string) => void + ) { + const wp = new Watchpack() + wp.watch({ + files: CONFIG_FILES.map((file) => path.join(dirToWatch, file)), + }) + wp.on('change', onChange) + } + watchConfigFiles(dir, async (filename) => { + if (process.env.__NEXT_DISABLE_MEMORY_WATCHER) { + Log.info( + `Detected change, manual restart required due to '__NEXT_DISABLE_MEMORY_WATCHER' usage` + ) + return + } + + // Adding a new line to avoid the logs going directly after the spinner in `next build` + Log.warn('') + Log.warn( + `Found a change in ${path.basename( + filename + )}. Restarting the server to apply the changes...` + ) + process.exit(RESTART_EXIT_CODE) + }) + } } diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 656db3bbb5fb6..7153958ef0e2f 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -43,6 +43,7 @@ import { SERVER_DIRECTORY, NEXT_FONT_MANIFEST, PHASE_PRODUCTION_BUILD, + INTERNAL_HEADERS, } from '../shared/lib/constants' import { findDir } from '../lib/find-pages-dir' import { UrlWithParsedQuery } from 'url' @@ -918,11 +919,7 @@ export default class NextNodeServer extends BaseServer { } catch (err: any) { if (err instanceof NoFallbackError) { if (this.isRenderWorker) { - res.setHeader('x-no-fallback', '1') - res.send() - return { - finished: true, - } + throw err } return { @@ -1000,13 +997,17 @@ export default class NextNodeServer extends BaseServer { private normalizeReq( req: BaseNextRequest | IncomingMessage ): BaseNextRequest { - return req instanceof IncomingMessage ? new NodeNextRequest(req) : req + return !(req instanceof NodeNextRequest) + ? new NodeNextRequest(req as IncomingMessage) + : req } private normalizeRes( res: BaseNextResponse | ServerResponse ): BaseNextResponse { - return res instanceof ServerResponse ? new NodeNextResponse(res) : res + return !(res instanceof NodeNextResponse) + ? new NodeNextResponse(res as ServerResponse) + : res } public getRequestHandler(): NodeRequestHandler { @@ -1464,7 +1465,9 @@ export default class NextNodeServer extends BaseServer { checkIsOnDemandRevalidate(params.request, this.renderOpts.previewProps) .isOnDemandRevalidate ) { - return { finished: false } + return { + response: new Response(null, { headers: { 'x-middleware-next': '1' } }), + } as FetchEventResult } let url: string @@ -1611,6 +1614,11 @@ export default class NextNodeServer extends BaseServer { let result: Awaited< ReturnType > + let bubblingResult = false + + for (const key of INTERNAL_HEADERS) { + delete req.headers[key] + } // Strip the internal headers. this.stripInternalHeaders(req) @@ -1625,7 +1633,15 @@ export default class NextNodeServer extends BaseServer { parsed: parsed, }) - if (isMiddlewareInvoke && 'response' in result) { + if ('response' in result) { + if (isMiddlewareInvoke) { + bubblingResult = true + const err = new Error() + ;(err as any).result = result + ;(err as any).bubble = true + throw err + } + for (const [key, value] of Object.entries( toNodeOutgoingHttpHeaders(result.response.headers) )) { @@ -1643,7 +1659,11 @@ export default class NextNodeServer extends BaseServer { } return { finished: true } } - } catch (err) { + } catch (err: any) { + if (bubblingResult) { + throw err + } + if (isError(err) && err.code === 'ENOENT') { await this.render404(req, res, parsed) return { finished: true } @@ -1651,14 +1671,14 @@ export default class NextNodeServer extends BaseServer { if (err instanceof DecodeError) { res.statusCode = 400 - this.renderError(err, req, res, parsed.pathname || '') + await this.renderError(err, req, res, parsed.pathname || '') return { finished: true } } const error = getProperError(err) console.error(error) res.statusCode = 500 - this.renderError(error, req, res, parsed.pathname || '') + await this.renderError(error, req, res, parsed.pathname || '') return { finished: true } } diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index 6cdd4282007b9..0c1ddd2bc6f11 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -4,6 +4,56 @@ import type { UrlWithParsedQuery } from 'url' import type { NextConfigComplete } from './config-shared' import type { IncomingMessage, ServerResponse } from 'http' import type { NextUrlWithParsedQuery } from './request-meta' +import { spawnSync } from 'child_process' +import { getEsmLoaderPath } from './lib/get-esm-loader-path' +import { + RESTART_EXIT_CODE, + WorkerRequestHandler, + WorkerUpgradeHandler, +} from './lib/setup-server-worker' + +// if we are not inside of the esm loader enabled +// worker we need to re-spawn with correct args +// we can't do this if imported in jest test file otherwise +// it duplicates tests +if ( + typeof jest === 'undefined' && + !process.env.NEXT_PRIVATE_WORKER && + (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT || + process.env.NODE_ENV === 'development') +) { + const nodePath = process.argv0 + + const newArgs = [ + '--experimental-loader', + getEsmLoaderPath(), + '--no-warnings', + ...process.argv.splice(1), + ] + function startWorker() { + try { + const result = spawnSync(nodePath, newArgs, { + stdio: 'inherit', + env: { + ...process.env, + NEXT_PRIVATE_WORKER: '1', + }, + }) + + if ( + result.status === RESTART_EXIT_CODE && + process.env.NODE_ENV === 'development' + ) { + startWorker() + } + process.exit(0) + } catch (err) { + console.error(err) + process.exit(1) + } + } + startWorker() +} import './require-hook' import './node-polyfill-fetch' @@ -19,10 +69,6 @@ import { PHASE_PRODUCTION_SERVER } from '../shared/lib/constants' import { getTracer } from './lib/trace/tracer' import { NextServerSpan } from './lib/trace/constants' import { formatUrl } from '../shared/lib/router/utils/format-url' -import { - WorkerRequestHandler, - WorkerUpgradeHandler, -} from './lib/setup-server-worker' import { checkIsNodeDebugging } from './lib/is-node-debugging' let ServerImpl: typeof Server diff --git a/packages/next/src/server/require-hook.js b/packages/next/src/server/require-hook.js new file mode 100644 index 0000000000000..ce9e30e6e9ffe --- /dev/null +++ b/packages/next/src/server/require-hook.js @@ -0,0 +1,52 @@ +// Synchronously inject a require hook for webpack and webpack/. It's required to use the internal ncc webpack version. +// This is needed for userland plugins to attach to the same webpack instance as Next.js'. +// Individually compiled modules are as defined for the compilation in bundles/webpack/packages/*. + +// This module will only be loaded once per process. +const path = require('path') +const mod = require('module') +const originalRequire = mod.prototype.require +const resolveFilename = mod._resolveFilename + +const { overrideReact, hookPropertyMap } = require('./import-overrides') + +mod._resolveFilename = function ( + originalResolveFilename, + requestMap, + request, + parent, + isMain, + options +) { + // In case the environment variable is set after the module is loaded. + overrideReact() + + const hookResolved = requestMap.get(request) + if (hookResolved) request = hookResolved + + return originalResolveFilename.call(mod, request, parent, isMain, options) + + // We use `bind` here to avoid referencing outside variables to create potential memory leaks. +}.bind(null, resolveFilename, hookPropertyMap) + +// This is a hack to make sure that if a user requires a Next.js module that wasn't bundled +// that needs to point to the rendering runtime version, it will point to the correct one. +// This can happen on `pages` when a user requires a dependency that uses next/image for example. +// This is only needed in production as in development we fallback to the external version. +if (process.env.NODE_ENV !== 'development' && !process.env.TURBOPACK) { + mod.prototype.require = function (request) { + if (request.endsWith('.shared-runtime')) { + const isAppRequire = process.env.__NEXT_PRIVATE_RUNTIME_TYPE === 'app' + const currentRuntime = `${ + isAppRequire + ? 'next/dist/compiled/next-server/app-page.runtime' + : 'next/dist/compiled/next-server/pages.runtime' + }.prod` + const base = path.basename(request, '.shared-runtime') + const camelized = base.replace(/-([a-z])/g, (g) => g[1].toUpperCase()) + const instance = originalRequire.call(this, currentRuntime) + return instance.default.sharedModules[camelized] + } + return originalRequire.call(this, request) + } +} diff --git a/packages/next/src/server/require.ts b/packages/next/src/server/require.ts index bbc8e91dc2835..f020ea27e7eba 100644 --- a/packages/next/src/server/require.ts +++ b/packages/next/src/server/require.ts @@ -116,10 +116,18 @@ export function requirePage( }) } - return process.env.NEXT_MINIMAL - ? // @ts-ignore - __non_webpack_require__(pagePath) - : require(pagePath) + // since require is synchronous we can set the specific runtime + // we are requiring for the require-hook and then clear after + try { + process.env.__NEXT_PRIVATE_RUNTIME_TYPE = isAppPath ? 'app' : 'pages' + const mod = process.env.NEXT_MINIMAL + ? // @ts-ignore + __non_webpack_require__(pagePath) + : require(pagePath) + return mod + } finally { + process.env.__NEXT_PRIVATE_RUNTIME_TYPE = '' + } } export function requireFontManifest(distDir: string) { diff --git a/packages/next/src/shared/lib/constants.ts b/packages/next/src/shared/lib/constants.ts index 3d0f78e7bd018..86a1c2059b585 100644 --- a/packages/next/src/shared/lib/constants.ts +++ b/packages/next/src/shared/lib/constants.ts @@ -10,6 +10,14 @@ export const COMPILER_NAMES = { edgeServer: 'edge-server', } as const +export const INTERNAL_HEADERS = [ + 'x-invoke-path', + 'x-invoke-status', + 'x-invoke-error', + 'x-invoke-query', + 'x-middleware-invoke', +] as const + export type CompilerNameValues = ValueOf export const COMPILER_INDEXES: { diff --git a/packages/next/taskfile-swc.js b/packages/next/taskfile-swc.js index 8ff3427c60e36..0d28ca9d0b5c0 100644 --- a/packages/next/taskfile-swc.js +++ b/packages/next/taskfile-swc.js @@ -140,7 +140,10 @@ module.exports = function (task) { if (ext) { const extRegex = new RegExp(ext.replace('.', '\\.') + '$', 'i') // Remove the extension if stripExtension is enabled or replace it with `.js` - file.base = file.base.replace(extRegex, stripExtension ? '' : '.js') + file.base = file.base.replace( + extRegex, + stripExtension ? '' : `.${ext === '.mts' ? 'm' : ''}js` + ) } if (output.map) { diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 321490234153b..43da4071afb25 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -2443,7 +2443,7 @@ export async function server(task, opts) { export async function server_esm(task, opts) { await task - .source('src/server/**/!(*.test).+(js|ts|tsx)') + .source('src/server/**/!(*.test).+(js|mts|ts|tsx)') .swc('server', { dev: opts.dev, esm: true }) .target('dist/esm/server') } diff --git a/test/development/basic/node-builtins.test.ts b/test/development/basic/node-builtins.test.ts index 27797f6c564d0..3c5c6a6a335f8 100644 --- a/test/development/basic/node-builtins.test.ts +++ b/test/development/basic/node-builtins.test.ts @@ -86,7 +86,7 @@ createNextDescribe( expect(parsedData.https).toBe(true) expect(parsedData.os).toBe('\n') expect(parsedData.path).toBe('/hello/world/test.txt') - expect(parsedData.process).toInclude('next-render-worker-pages') + expect(parsedData.process).toInclude('next-router-worker') expect(parsedData.querystring).toBe('a=b') expect(parsedData.stringDecoder).toBe(true) expect(parsedData.sys).toBe(true) @@ -112,7 +112,7 @@ createNextDescribe( expect(parsedData.https).toBe(true) expect(parsedData.os).toBe('\n') expect(parsedData.path).toBe('/hello/world/test.txt') - expect(parsedData.process).toInclude('next-render-worker-pages') + expect(parsedData.process).toInclude('next-router-worker') expect(parsedData.querystring).toBe('a=b') expect(parsedData.stringDecoder).toBe(true) expect(parsedData.sys).toBe(true) diff --git a/test/development/watch-config-file/index.test.ts b/test/development/watch-config-file/index.test.ts index 4051ffe312bf6..1d7ddb36f3e11 100644 --- a/test/development/watch-config-file/index.test.ts +++ b/test/development/watch-config-file/index.test.ts @@ -9,28 +9,28 @@ createNextDescribe( ({ next }) => { it('should output config file change', async () => { await check(async () => next.cliOutput, /ready/) - await next.patchFile( - 'next.config.js', - ` - const nextConfig = { - reactStrictMode: true, - async redirects() { - return [ - { - source: '/about', - destination: '/', - permanent: false, - }, - ] - }, - } - module.exports = nextConfig` - ) - await check( - async () => next.cliOutput, - /Found a change in next\.config\.js\. Restarting the server to apply the changes\.\.\./ - ) + await check(async () => { + await next.patchFile( + 'next.config.js', + ` + console.log(${Date.now()}) + const nextConfig = { + reactStrictMode: true, + async redirects() { + return [ + { + source: '/about', + destination: '/', + permanent: false, + }, + ] + }, + } + module.exports = nextConfig` + ) + return next.cliOutput + }, /Found a change in next\.config\.js\. Restarting the server to apply the changes\.\.\./) await check(() => next.fetch('/about').then((res) => res.status), 200) }) diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index 2a6d221a5cf9f..2c0bc2045e431 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -305,9 +305,7 @@ createNextDescribe( const res = await next.fetch('/dashboard') expect(res.headers.get('x-edge-runtime')).toBe('1') expect(res.headers.get('vary')).toBe( - isNextDeploy - ? 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url' - : 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url, Accept-Encoding' + 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url' ) }) diff --git a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts index 1f83176fa745f..fc1916897c808 100644 --- a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts @@ -450,8 +450,8 @@ createNextDescribe( expect(await res.text()).toBe('Hello from import-test.js') }) - it('should use stable react for pages', async () => { - const ssrPaths = ['/pages-react', '/pages-react-edge'] + it('should use bundled react for pages with app', async () => { + const ssrPaths = ['/pages-react', '/edge-pages-react'] const promises = ssrPaths.map(async (pathname) => { const resPages$ = await next.render$(pathname) const ssrPagesReactVersions = [ @@ -461,7 +461,7 @@ createNextDescribe( ] ssrPagesReactVersions.forEach((version) => { - expect(version).not.toMatch('-canary-') + expect(version).toMatch('-canary-') }) }) await Promise.all(promises) @@ -496,10 +496,10 @@ createNextDescribe( `) browserPagesReactVersions.forEach((version) => - expect(version).not.toMatch('-canary-') + expect(version).toMatch('-canary-') ) browserEdgePagesReactVersions.forEach((version) => - expect(version).not.toMatch('-canary-') + expect(version).toMatch('-canary-') ) }) diff --git a/test/integration/amphtml-fragment-style/pages/index.js b/test/integration/amphtml-fragment-style/pages/index.js index f57bb4c1e36fe..c45defad01f66 100644 --- a/test/integration/amphtml-fragment-style/pages/index.js +++ b/test/integration/amphtml-fragment-style/pages/index.js @@ -1,6 +1,6 @@ export const config = { amp: true } -export default () => ( +const Comp = () => (

Hello world!

) + +Comp.getInitialProps = () => ({}) +export default Comp diff --git a/test/integration/api-body-parser/pages/api/index.js b/test/integration/api-body-parser/pages/api/index.js index 28ef9244fcefe..bb894d917ddfc 100644 --- a/test/integration/api-body-parser/pages/api/index.js +++ b/test/integration/api-body-parser/pages/api/index.js @@ -1,5 +1,12 @@ -export default ({ method, body }, res) => { - if (method === 'POST') { - res.status(200).json(body) +export default (req, res) => { + if ( + process.env.CUSTOM_SERVER && + typeof req.fromCustomServer === 'undefined' + ) { + throw new Error('missing custom req field') + } + + if (req.method === 'POST') { + res.status(200).json(req.body) } } diff --git a/test/integration/api-body-parser/server.js b/test/integration/api-body-parser/server.js index 0f786557e5fce..f37ee6845f7db 100644 --- a/test/integration/api-body-parser/server.js +++ b/test/integration/api-body-parser/server.js @@ -14,6 +14,7 @@ app.prepare().then(() => { server.use(express.json({ limit: '5mb' })) server.all('*', (req, res) => { + req.fromCustomServer = true handleNextRequests(req, res) }) diff --git a/test/integration/api-body-parser/test/index.test.js b/test/integration/api-body-parser/test/index.test.js index 83501cfa63727..ae6d540c14462 100644 --- a/test/integration/api-body-parser/test/index.test.js +++ b/test/integration/api-body-parser/test/index.test.js @@ -27,9 +27,7 @@ function runTests() { killApp(app) }) - // TODO: we can't allow req fields with the proxying required for separate - // workers - it.skip('should not throw if request body is already parsed in custom middleware', async () => { + it('should not throw if request body is already parsed in custom middleware', async () => { await startServer() const data = await makeRequest() expect(data).toEqual([{ title: 'Nextjs' }]) @@ -71,7 +69,11 @@ async function makeRequestWithInvalidContentType() { const startServer = async (optEnv = {}, opts) => { const scriptPath = join(appDir, 'server.js') context.appPort = appPort = await getPort() - const env = Object.assign({ ...process.env }, { PORT: `${appPort}` }, optEnv) + const env = Object.assign( + { ...process.env }, + { PORT: `${appPort}`, CUSTOM_SERVER: 'true' }, + optEnv + ) server = await initNextServerScript( scriptPath, diff --git a/test/integration/cli/test/index.test.js b/test/integration/cli/test/index.test.js index b1df03915f75b..06bc0fe2eb993 100644 --- a/test/integration/cli/test/index.test.js +++ b/test/integration/cli/test/index.test.js @@ -8,6 +8,7 @@ import { nextBuild, runNextCommand, runNextCommandDev, + killProcess, } from 'next-test-utils' import fs from 'fs-extra' import path, { join } from 'path' @@ -21,7 +22,8 @@ const dirDuplicateSass = join(__dirname, '../duplicate-sass') const testExitSignal = async ( killSignal = '', args = [], - readyRegex = /Creating an optimized production/ + readyRegex = /Creating an optimized production/, + expectedExitSignal ) => { let instance const killSigint = (inst) => { @@ -38,14 +40,18 @@ const testExitSignal = async ( }).catch((err) => expect.fail(err.message)) await check(() => output, readyRegex) - instance.kill(killSignal) + await killProcess(instance.pid, killSignal) const { code, signal } = await cmdPromise - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitSignal = process.platform === `win32` ? killSignal : null - expect(signal).toBe(expectedExitSignal) - expect(code).toBe(0) + + if (!expectedExitSignal) { + // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. + // See: https://nodejs.org/api/process.html#process_signal_events + expectedExitSignal = process.platform === `win32` ? killSignal : null + expect(code).toBe(0) + } else { + expect(signal).toBe(expectedExitSignal) + } } describe('CLI Usage', () => { @@ -628,11 +634,21 @@ describe('CLI Usage', () => { test('should exit when SIGINT is signalled', async () => { const port = await findPort() - await testExitSignal('SIGINT', ['dev', dirBasic, '-p', port], /- Local:/) + await testExitSignal( + 'SIGINT', + ['dev', dirBasic, '-p', port], + /- Local:/, + 'SIGINT' + ) }) test('should exit when SIGTERM is signalled', async () => { const port = await findPort() - await testExitSignal('SIGTERM', ['dev', dirBasic, '-p', port], /- Local:/) + await testExitSignal( + 'SIGTERM', + ['dev', dirBasic, '-p', port], + /- Local:/, + 'SIGTERM' + ) }) test('invalid directory', async () => { diff --git a/test/integration/middleware-overrides-node.js-api/middleware.js b/test/integration/middleware-overrides-node.js-api/middleware.js index 1bea9d9ca3cb3..a9110fb87f022 100644 --- a/test/integration/middleware-overrides-node.js-api/middleware.js +++ b/test/integration/middleware-overrides-node.js-api/middleware.js @@ -1,5 +1,5 @@ export default function middleware() { process.cwd = () => 'fixed-value' - console.log(process.cwd(), process.env) + console.log(process.cwd(), !!process.env) return new Response() } diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts index 5e28fd7c602a1..792d333380fce 100644 --- a/test/lib/next-test-utils.ts +++ b/test/lib/next-test-utils.ts @@ -509,9 +509,12 @@ export function buildTS( }) } -export async function killProcess(pid: number): Promise { +export async function killProcess( + pid: number, + signal: string | number = 'SIGTERM' +): Promise { return await new Promise((resolve, reject) => { - treeKill(pid, (err) => { + treeKill(pid, signal, (err) => { if (err) { if ( process.platform === 'win32' && diff --git a/test/production/custom-server/custom-server.test.ts b/test/production/custom-server/custom-server.test.ts index a26c6c1e313a2..cb643e015b545 100644 --- a/test/production/custom-server/custom-server.test.ts +++ b/test/production/custom-server/custom-server.test.ts @@ -21,10 +21,10 @@ createNextDescribe( expect($('body').text()).toMatch(/app: .+-canary/) }) - it('should render pages with react stable', async () => { + it('should render pages with react canary', async () => { const $ = await next.render$(`/2`) expect($('body').text()).toMatch(/pages:/) - expect($('body').text()).not.toMatch(/canary/) + expect($('body').text()).toMatch(/canary/) }) }) } diff --git a/test/production/custom-server/server.js b/test/production/custom-server/server.js index 113493cc75eae..81c9b3a203a2b 100644 --- a/test/production/custom-server/server.js +++ b/test/production/custom-server/server.js @@ -30,7 +30,7 @@ async function main() { res.statusCode = 500 res.end('Internal Server Error') } - }).listen(port, '0.0.0.0', (err) => { + }).listen(port, undefined, (err) => { if (err) throw err // Start mode console.log(`- Local: http://${hostname}:${port}`) From 591d4e11ef72283ac9f91780f1e65b38375d7239 Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Mon, 11 Sep 2023 15:19:57 -0500 Subject: [PATCH 10/16] docs: clarify setting cookies docs (#55149) --- docs/02-app/02-api-reference/04-functions/cookies.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/02-api-reference/04-functions/cookies.mdx b/docs/02-app/02-api-reference/04-functions/cookies.mdx index 86f27f6c25c5b..0ad04f465482b 100644 --- a/docs/02-app/02-api-reference/04-functions/cookies.mdx +++ b/docs/02-app/02-api-reference/04-functions/cookies.mdx @@ -62,7 +62,7 @@ export default function Page() { A method that takes a cookie name, value, and options and sets the outgoing request cookie. -> **Good to know**: `.set()` is only available in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). +> **Good to know**: HTTP does not allow setting cookies after streaming starts, so you must use `.set()` in a [Server Action](/docs/app/building-your-application/data-fetching/forms-and-mutations) or [Route Handler](/docs/app/building-your-application/routing/route-handlers). ```js filename="app/actions.js" 'use server' From 476fe4a1f694e0ca90ee0362651d3e73f475449c Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Mon, 11 Sep 2023 20:24:23 +0000 Subject: [PATCH 11/16] v13.4.20-canary.24 --- 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 +- packages/third-parties/package.json | 4 ++-- pnpm-lock.yaml | 16 ++++++++-------- 18 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lerna.json b/lerna.json index 4527c6cdb1f65..e4358f6306582 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.4.20-canary.23" + "version": "13.4.20-canary.24" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index e6ccbd64c2918..1c52fd9179f9f 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.4.20-canary.23", + "version": "13.4.20-canary.24", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 46a85384264eb..4b6f55a92e894 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.4.20-canary.23", + "version": "13.4.20-canary.24", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-config", "dependencies": { - "@next/eslint-plugin-next": "13.4.20-canary.23", + "@next/eslint-plugin-next": "13.4.20-canary.24", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.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 aeb2e3893ea85..ed9195a4d2a0e 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.4.20-canary.23", + "version": "13.4.20-canary.24", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index fad6305c8c889..0b1dc7e76e67e 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "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 2d95b13c04181..5c10f4a3b06e7 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.4.20-canary.23", + "version": "13.4.20-canary.24", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 879859b59cad9..4ea40e8ccf9ad 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index f7a22d5d6dc6b..2c789eb210f23 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 431c12e6fd87e..4398e3fa0ef94 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 881062c5e9e3c..7f6d726439104 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.4.20-canary.23", + "version": "13.4.20-canary.24", "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 7837e4c89ccd1..5e05749a6fef2 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.4.20-canary.23", + "version": "13.4.20-canary.24", "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 69bd5c51c8262..6f69f02f69a22 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.4.20-canary.23", + "version": "13.4.20-canary.24", "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 44cecb79651d4..6af005cf59138 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index cd46dc8a7fe5a..f80dc95489b70 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -90,7 +90,7 @@ ] }, "dependencies": { - "@next/env": "13.4.20-canary.23", + "@next/env": "13.4.20-canary.24", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -145,11 +145,11 @@ "@mswjs/interceptors": "0.23.0", "@napi-rs/cli": "2.16.2", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.4.20-canary.23", - "@next/polyfill-nomodule": "13.4.20-canary.23", - "@next/react-dev-overlay": "13.4.20-canary.23", - "@next/react-refresh-utils": "13.4.20-canary.23", - "@next/swc": "13.4.20-canary.23", + "@next/polyfill-module": "13.4.20-canary.24", + "@next/polyfill-nomodule": "13.4.20-canary.24", + "@next/react-dev-overlay": "13.4.20-canary.24", + "@next/react-refresh-utils": "13.4.20-canary.24", + "@next/swc": "13.4.20-canary.24", "@opentelemetry/api": "1.4.1", "@playwright/test": "^1.35.1", "@segment/ajv-human-errors": "2.1.2", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index f5d798422c1c9..ba5aae05348e1 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.4.20-canary.23", + "version": "13.4.20-canary.24", "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 2a4eeec8bcef1..d6a909229b58a 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.4.20-canary.23", + "version": "13.4.20-canary.24", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index a8229aee0e66c..cae29825081f9 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "13.4.20-canary.23", + "version": "13.4.20-canary.24", "private": true, "repository": { "url": "vercel/next.js", @@ -23,7 +23,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "13.4.20-canary.23", + "next": "13.4.20-canary.24", "outdent": "0.8.0", "prettier": "2.5.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f348375d32d5..9d768e9b76d42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -729,7 +729,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.3.3 @@ -790,7 +790,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../next-env '@swc/helpers': specifier: 0.5.1 @@ -917,19 +917,19 @@ importers: specifier: 1.1.0 version: 1.1.0 '@next/polyfill-module': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../next-polyfill-nomodule '@next/react-dev-overlay': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../react-dev-overlay '@next/react-refresh-utils': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../react-refresh-utils '@next/swc': - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../next-swc '@opentelemetry/api': specifier: 1.4.1 @@ -1688,7 +1688,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 13.4.20-canary.23 + specifier: 13.4.20-canary.24 version: link:../next outdent: specifier: 0.8.0 From ddc26c9904735a4ec7fb7a8ca74b50a8d1bfba35 Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:16:51 -0700 Subject: [PATCH 12/16] test(turbopack): migrate 404-related tests from next-dev (#55243) Closes WEB-1535 --- .../pages/404-navigate/input/pages/_error.tsx | 15 ------ .../pages/404-navigate/input/pages/index.tsx | 48 ------------------- .../pages/404-navigate/input/pages/link.tsx | 12 ----- .../404-navigate/input/pages/not-found.tsx | 9 ---- test/turbopack-tests-manifest.js | 4 ++ 5 files changed, 4 insertions(+), 84 deletions(-) delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/_error.tsx delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/index.tsx delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/link.tsx delete mode 100644 packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/not-found.tsx diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/_error.tsx b/packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/_error.tsx deleted file mode 100644 index 44ee917818d51..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/_error.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useTestHarness } from '@turbo/pack-test-harness' - -export default function ErrorPage(props: { static: 'static' }) { - useTestHarness((harness) => harness.markAsHydrated()) - - return
{props.static}
-} - -export function getStaticProps() { - return { - props: { - static: 'static', - }, - } -} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/index.tsx b/packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/index.tsx deleted file mode 100644 index e73498717980c..0000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/pages/404-navigate/input/pages/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useRef } from 'react' -import { Harness, useTestHarness } from '@turbo/pack-test-harness' - -export default function Page() { - const iframeRef = useRef(null) - - useTestHarness((harness) => runTests(harness, iframeRef.current!)) - - return ( -