From 4223611af8c95c33e989c4918d4feead4ac55a5e Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 15 Nov 2021 11:37:44 +0100 Subject: [PATCH 01/19] Ensure Git feature.manyFiles is enabled (#31408) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 206dce7ee28e29e..eefe90fce34cefd 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next", "clean-trace-jaeger": "rm -rf test/integration/basic/.next && TRACE_TARGET=JAEGER node --trace-deprecation --enable-source-maps packages/next/dist/bin/next build test/integration/basic", "debug": "node --inspect packages/next/dist/bin/next", - "postinstall": "node scripts/install-native.mjs" + "postinstall": "git config feature.manyFiles true && node scripts/install-native.mjs" }, "pre-commit": "lint-staged", "devDependencies": { From 61ca7369c61b3a084e31fdb84ee6cede4c87f9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Kne=C5=BEevi=C4=87?= Date: Mon, 15 Nov 2021 11:50:13 +0100 Subject: [PATCH 02/19] Fix function syntax for API Routes documentation (#31414) There is a non-valid mix of classic and arrow function notation in the documentation. I've fixed it to be a classical function notation because all the other examples are like that. ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- docs/api-routes/response-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-routes/response-helpers.md b/docs/api-routes/response-helpers.md index 498b2499c552410..f13467c5b424b76 100644 --- a/docs/api-routes/response-helpers.md +++ b/docs/api-routes/response-helpers.md @@ -89,7 +89,7 @@ type ResponseData { message: string } -export default function handler(req: NextApiRequest, res: NextApiResponse) => { +export default function handler(req: NextApiRequest, res: NextApiResponse) { res.status(200).json({ message: 'Hello from Next.js!' }) } ``` From 0afc1d2b5a0f0586aeeb46976667201657ce987a Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 15 Nov 2021 12:01:49 +0100 Subject: [PATCH 03/19] Fix storybook styled-jsx example with styled-jsx 5 (#31357) ## Documentation / Examples - [x] Make sure the linting passes by running `yarn lint` x-ref: https://github.com/vercel/styled-jsx/issues/761 Since next.js is using styled-jsx v5 beta now, it requires wrapping a `StyleRegistry` component on the root top. ref: [styled-jsx doc](https://github.com/vercel/styled-jsx/tree/beta#server-side-rendering) --- .../.storybook/preview.js | 10 ++++++++++ examples/with-storybook-styled-jsx-scss/package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/with-storybook-styled-jsx-scss/.storybook/preview.js b/examples/with-storybook-styled-jsx-scss/.storybook/preview.js index 6dd87694930ad25..504ceb585fa1a1e 100644 --- a/examples/with-storybook-styled-jsx-scss/.storybook/preview.js +++ b/examples/with-storybook-styled-jsx-scss/.storybook/preview.js @@ -1,3 +1,13 @@ +import { StyleRegistry } from 'styled-jsx' + +export const decorators = [ + (Story) => ( + + + + ), +] + export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, } diff --git a/examples/with-storybook-styled-jsx-scss/package.json b/examples/with-storybook-styled-jsx-scss/package.json index 4cdb06be7859a76..8a931e3dbad61d1 100644 --- a/examples/with-storybook-styled-jsx-scss/package.json +++ b/examples/with-storybook-styled-jsx-scss/package.json @@ -9,7 +9,7 @@ "build-storybook": "build-storybook --no-dll" }, "dependencies": { - "next": "^10.0.0", + "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, From 9da8d6563215a9ea7f4ac56b154b3303c5515cd8 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 15 Nov 2021 14:15:18 +0100 Subject: [PATCH 04/19] Add minify debug env var to investigate minifier bugs (#31417) --- .../plugins/terser-webpack-plugin/src/index.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/next/build/webpack/plugins/terser-webpack-plugin/src/index.js b/packages/next/build/webpack/plugins/terser-webpack-plugin/src/index.js index 86db0360682faae..60ef06330f2884f 100644 --- a/packages/next/build/webpack/plugins/terser-webpack-plugin/src/index.js +++ b/packages/next/build/webpack/plugins/terser-webpack-plugin/src/index.js @@ -47,13 +47,14 @@ function buildError(error, file) { return new Error(`${file} from Terser\n${error.message}`) } +const debugMinify = process.env.NEXT_DEBUG_MINIFY + export class TerserPlugin { constructor(options = {}) { - const { cacheDir, terserOptions = {}, parallel, swcMinify } = options + const { terserOptions = {}, parallel, swcMinify } = options this.options = { swcMinify, - cacheDir, parallel, terserOptions, } @@ -128,6 +129,18 @@ export class TerserPlugin { numberOfAssetsForMinify += 1 } + if (debugMinify && debugMinify === '1') { + console.dir( + { + name, + source: source.source().toString(), + }, + { + breakLength: Infinity, + maxStringLength: Infinity, + } + ) + } return { name, info, inputSource: source, output, eTag } }) ) From e5171758d3b72b90269225e6f3b371a3b2bcc0cb Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 15 Nov 2021 14:19:55 +0100 Subject: [PATCH 05/19] v12.0.4-canary.13 --- 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/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/package.json | 12 ++++++------ packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index 2f4f45f2751d75e..145a554a72f23e3 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.4-canary.12" + "version": "12.0.4-canary.13" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 33358f4e1b01bb1..aed0dc5f0d4dfa6 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 3b52f15c0d0f856..0c6ffb2e404b190 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "12.0.4-canary.12", + "@next/eslint-plugin-next": "12.0.4-canary.13", "@rushstack/eslint-patch": "^1.0.6", "@typescript-eslint/parser": "^4.20.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 2fcf2899a007570..1e9e965ebb6db06 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": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 97065f20d6dca6c..9f8867aea136769 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 68f15ae0f641ea4..d614766a0db2e28 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 68a052e3d16b856..4336d5a0314868c 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 220d37df00c3e2f..de3355aec7177a0 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 155a375c5e64ccc..5bdfd6c48354787 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "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 ac689d847c40d98..3ef00be511b2d69 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "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 fe653e4f22a8f8f..f4565741156558c 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/package.json b/packages/next/package.json index f2afec28623b268..86e3d17c40b9b95 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,10 +70,10 @@ "@babel/runtime": "7.15.4", "@hapi/accept": "5.0.2", "@napi-rs/triples": "1.0.3", - "@next/env": "12.0.4-canary.12", - "@next/polyfill-module": "12.0.4-canary.12", - "@next/react-dev-overlay": "12.0.4-canary.12", - "@next/react-refresh-utils": "12.0.4-canary.12", + "@next/env": "12.0.4-canary.13", + "@next/polyfill-module": "12.0.4-canary.13", + "@next/react-dev-overlay": "12.0.4-canary.13", + "@next/react-refresh-utils": "12.0.4-canary.13", "acorn": "8.5.0", "assert": "2.0.0", "browserify-zlib": "0.2.0", @@ -156,7 +156,7 @@ "@babel/traverse": "7.15.0", "@babel/types": "7.15.0", "@napi-rs/cli": "1.2.1", - "@next/polyfill-nomodule": "12.0.4-canary.12", + "@next/polyfill-nomodule": "12.0.4-canary.13", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index b1fbc6256250a10..af4bf3d160f8604 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": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "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 cb1d88b8d07f0d6..b0124f71a99e8fe 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": "12.0.4-canary.12", + "version": "12.0.4-canary.13", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From 8c547139e4250a8e99e2c093a498cce2d6e921cf Mon Sep 17 00:00:00 2001 From: Byron Date: Mon, 15 Nov 2021 06:01:20 -0800 Subject: [PATCH 06/19] add filename to babel warning about anonymous function default exports (#31322) fixes #29667 I didn't add an integration test, but I can take a stab at it if you point me in the right direction! --- .../next/build/babel/plugins/no-anonymous-default-export.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/next/build/babel/plugins/no-anonymous-default-export.ts b/packages/next/build/babel/plugins/no-anonymous-default-export.ts index f97c76b0cf4d7d1..3cb1c775be62de6 100644 --- a/packages/next/build/babel/plugins/no-anonymous-default-export.ts +++ b/packages/next/build/babel/plugins/no-anonymous-default-export.ts @@ -40,6 +40,7 @@ export default function NoAnonymousDefaultExport({ chalk.yellow.bold( 'Anonymous arrow functions cause Fast Refresh to not preserve local component state.' ), + chalk.cyan(this.file.opts.filename), 'Please add a name to your function, for example:', '', chalk.bold('Before'), @@ -64,6 +65,7 @@ export default function NoAnonymousDefaultExport({ chalk.yellow.bold( 'Anonymous function declarations cause Fast Refresh to not preserve local component state.' ), + chalk.cyan(this.file.opts.filename), 'Please add a name to your function, for example:', '', chalk.bold('Before'), From 06d4bbbb2441fee598846567e4778a44adf8dc3f Mon Sep 17 00:00:00 2001 From: Orri Arnarsson Date: Mon, 15 Nov 2021 14:12:20 +0000 Subject: [PATCH 07/19] docs: next-iron-session renamed to iron-session (#31292) ## Documentation / Examples - [x] Make sure the linting passes by running `yarn lint` --- docs/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/authentication.md b/docs/authentication.md index b92ec0a00316da3..984e45a9182cce4 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -119,7 +119,7 @@ Now that we've discussed authentication patterns, let's look at specific provide If you have an existing database with user data, you'll likely want to utilize an open-source solution that's provider agnostic. -- If you want a low-level, encrypted, and stateless session utility use [`next-iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session). +- If you want a low-level, encrypted, and stateless session utility use [`iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session). - If you want a full-featured authentication system with built-in providers (Google, Facebook, GitHub…), JWT, JWE, email/password, magic links and more… use [`next-auth`](https://github.com/nextauthjs/next-auth-example). Both of these libraries support either authentication pattern. If you're interested in [Passport](http://www.passportjs.org/), we also have examples for it using secure and encrypted cookies: From a47955718ecc7e0e61a7823d05f9cfd9d0c50528 Mon Sep 17 00:00:00 2001 From: Bradley Turek Date: Mon, 15 Nov 2021 07:37:08 -0700 Subject: [PATCH 08/19] Clarify with commas (#31224) Adds a few commas where they could improve the clarity of the sentence. - [X] Make sure the linting passes by running `yarn lint` Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com> --- docs/routing/introduction.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/routing/introduction.md b/docs/routing/introduction.md index 575c65db1792f06..2481440ff15cef4 100644 --- a/docs/routing/introduction.md +++ b/docs/routing/introduction.md @@ -6,7 +6,7 @@ description: Next.js has a built-in, opinionated, and file-system based Router. Next.js has a file-system based router built on the [concept of pages](/docs/basic-features/pages.md). -When a file is added to the `pages` directory it's automatically available as a route. +When a file is added to the `pages` directory, it's automatically available as a route. The files inside the `pages` directory can be used to define most common patterns. @@ -19,14 +19,14 @@ The router will automatically route files named `index` to the root of the direc #### Nested routes -The router supports nested files. If you create a nested folder structure files will be automatically routed in the same way still. +The router supports nested files. If you create a nested folder structure, files will automatically be routed in the same way still. - `pages/blog/first-post.js` → `/blog/first-post` - `pages/dashboard/settings/username.js` → `/dashboard/settings/username` #### Dynamic route segments -To match a dynamic segment you can use the bracket syntax. This allows you to match named parameters. +To match a dynamic segment, you can use the bracket syntax. This allows you to match named parameters. - `pages/blog/[slug].js` → `/blog/:slug` (`/blog/hello-world`) - `pages/[username]/settings.js` → `/:username/settings` (`/foo/settings`) @@ -68,7 +68,7 @@ function Home() { export default Home ``` -In the example above we have multiple links, each one maps a path (`href`) to a known page: +The example above uses multiple links. Each one maps a path (`href`) to a known page: - `/` → `pages/index.js` - `/about` → `pages/about.js` From 7d82e07df85fc1bd19aca6dad77e64fa73432343 Mon Sep 17 00:00:00 2001 From: Vincent Voyer Date: Mon, 15 Nov 2021 14:49:15 +0000 Subject: [PATCH 09/19] docs(auth): fix iron-session example url + API (#31413) This fixes the iron-session example url from an old url to the new, always-up-to-date url. I believe the Next.js team owns the previous url but it's not up-to-date. This commit also changes req.session.get("user") => req.session.user to reflect the new API. --- docs/authentication.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index 984e45a9182cce4..2afc9a05f9cf71a 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -48,7 +48,7 @@ const Profile = () => { export default Profile ``` -You can view this [example in action](https://next-with-iron-session.vercel.app/). Check out the [`with-iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session) example to see how it works. +You can view this [example in action](https://iron-session-example.vercel.app/). Check out the [`with-iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session) example to see how it works. ### Authenticating Server-Rendered Pages @@ -71,10 +71,7 @@ import withSession from '../lib/session' import Layout from '../components/Layout' export const getServerSideProps = withSession(async function ({ req, res }) { - // Get the user's session based on the request - const user = req.session.get('user') - - if (!user) { + if (!req.session.user) { return { redirect: { destination: '/login', From 16d56e2c49b2d4bff93e6e74e8939efae2ccda4d Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 15 Nov 2021 16:29:34 +0100 Subject: [PATCH 10/19] Refactor server/render for SSR streaming (#31231) Initial step to refactor the rendering logic by decoupling the handler and renderer: 1. Delegate Flight rendering to server/render 2. Reuse the piper glue code for both Fizz and Flight streams 3. Add buffering for ReadableStream In 1), this PR also makes sure that gSSP/gSP are correctly executed before the Flight stream and `pageProps` and `router` are correctly delivered to the component. Related to #30994. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- .../next-middleware-ssr-loader/index.ts | 84 +------ packages/next/server/render.tsx | 208 ++++++++++++++---- packages/next/types/misc.d.ts | 2 + 3 files changed, 180 insertions(+), 114 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts index b0ee97c93888f67..d056811f7ef58b0 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts @@ -28,16 +28,6 @@ export default async function middlewareRSCLoader(this: any) { import { RouterContext } from 'next/dist/shared/lib/router-context' import { renderToHTML } from 'next/dist/server/web/render' - import React, { createElement } from 'react' - - ${ - isServerComponent - ? ` - import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' - import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack'` - : '' - } - ${appDefinition} ${documentDefinition} @@ -57,47 +47,7 @@ export default async function middlewareRSCLoader(this: any) { throw new Error('Your page must export a \`default\` component') } - function wrapReadable(readable) { - const encoder = new TextEncoder() - const transformStream = new TransformStream() - const writer = transformStream.writable.getWriter() - const reader = readable.getReader() - const process = () => { - reader.read().then(({ done, value }) => { - if (!done) { - writer.write(typeof value === 'string' ? encoder.encode(value) : value) - process() - } else { - writer.close() - } - }) - } - process() - return transformStream.readable - } - - ${ - isServerComponent - ? ` - const renderFlight = props => renderToReadableStream(createElement(Page, props), rscManifest) - - let responseCache - const FlightWrapper = props => { - let response = responseCache - if (!response) { - responseCache = response = createFromReadableStream(renderFlight(props)) - } - return response.readRoot() - } - const Component = props => { - return createElement( - React.Suspense, - { fallback: null }, - createElement(FlightWrapper, props) - ) - }` - : `const Component = Page` - } + const Component = Page async function render(request) { const url = request.nextUrl @@ -111,28 +61,10 @@ export default async function middlewareRSCLoader(this: any) { }) } - ${ - isServerComponent - ? ` - // Flight data request - const isFlightDataRequest = query.__flight__ !== undefined - if (isFlightDataRequest) { - delete query.__flight__ - return new Response( - wrapReadable( - renderFlight({ - router: { - route: pathname, - asPath: pathname, - pathname: pathname, - query, - } - }) - ) - ) - }` - : '' + const renderServerComponentData = ${ + isServerComponent ? `query.__flight__ !== undefined` : 'false' } + delete query.__flight__ const renderOpts = { Component, @@ -156,7 +88,10 @@ export default async function middlewareRSCLoader(this: any) { basePath: ${JSON.stringify(basePath || '')}, supportsDynamicHTML: true, concurrentFeatures: true, - renderServerComponent: ${isServerComponent ? 'true' : 'false'}, + renderServerComponentData, + serverComponentManifest: ${ + isServerComponent ? 'rscManifest' : 'null' + }, } const transformStream = new TransformStream() @@ -173,7 +108,8 @@ export default async function middlewareRSCLoader(this: any) { ) result.pipe({ write: str => writer.write(encoder.encode(str)), - end: () => writer.close() + end: () => writer.close(), + // Not implemented: cork/uncork/on/removeListener }) } catch (err) { return new Response( diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 88103632fbe960f..2c41bce6fdbb9f7 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -3,6 +3,8 @@ import { ParsedUrlQuery } from 'querystring' import type { Writable as WritableType } from 'stream' import React from 'react' import ReactDOMServer from 'react-dom/server' +import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack' +import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' import { StyleRegistry, createStyleRegistry } from 'styled-jsx' import { UnwrapPromise } from '../lib/coalesced-function' import { @@ -203,7 +205,8 @@ export type RenderOptsPartial = { devOnlyCacheBusterQueryString?: string resolvedUrl?: string resolvedAsPath?: string - renderServerComponent?: null | (() => Promise) + serverComponentManifest?: any + renderServerComponentData?: boolean distDir?: string locale?: string locales?: string[] @@ -274,6 +277,49 @@ function checkRedirectValues( } } +// Create the wrapper component for a Flight stream. +function createServerComponentRenderer( + OriginalComponent: React.ComponentType, + serverComponentManifest: NonNullable +) { + let responseCache: any + const ServerComponentWrapper = (props: any) => { + let response = responseCache + if (!response) { + responseCache = response = createFromReadableStream( + renderToReadableStream( + , + serverComponentManifest + ) + ) + } + return response.readRoot() + } + const Component = (props: any) => { + return ( + + + + ) + } + + // Although it's not allowed to attach some static methods to Component, + // we still re-assign all the component APIs to keep the behavior unchanged. + for (const methodName of [ + 'getInitialProps', + 'getStaticProps', + 'getServerSideProps', + 'getStaticPaths', + ]) { + const method = (OriginalComponent as any)[methodName] + if (method) { + ;(Component as any)[methodName] = method + } + } + + return Component +} + export async function renderToHTML( req: IncomingMessage, res: ServerResponse, @@ -298,7 +344,6 @@ export async function renderToHTML( App, Document, pageConfig = {}, - Component, buildManifest, fontManifest, reactLoadableManifest, @@ -306,7 +351,8 @@ export async function renderToHTML( getStaticProps, getStaticPaths, getServerSideProps, - renderServerComponent, + serverComponentManifest, + renderServerComponentData, isDataReq, params, previewProps, @@ -316,6 +362,12 @@ export async function renderToHTML( concurrentFeatures, } = renderOpts + const isServerComponent = !!serverComponentManifest + const OriginalComponent = renderOpts.Component + const Component = isServerComponent + ? createServerComponentRenderer(OriginalComponent, serverComponentManifest) + : renderOpts.Component + const getFontDefinition = (url: string): string => { if (fontManifest) { return getFontDefinitionFromManifest(url, fontManifest) @@ -359,8 +411,6 @@ export async function renderToHTML( const hasPageGetInitialProps = !!(Component as any).getInitialProps - const isRSC = !!renderServerComponent - const pageIsDynamic = isDynamicRoute(pathname) const isAutoExport = @@ -940,9 +990,29 @@ export async function renderToHTML( props.pageProps = {} } + // Pass router to the Server Component as a temporary workaround. + if (isServerComponent) { + props.pageProps = Object.assign({}, props.pageProps, { router }) + } + // the response might be finished on the getInitialProps call if (isResSent(res) && !isSSG) return null + if (renderServerComponentData) { + return new RenderResult((res_, next) => { + const { startWriting } = connectReactServerReadableStreamToPiper( + res_.write, + next + ) + startWriting( + renderToReadableStream( + , + serverComponentManifest + ).getReader() + ) + }) + } + // we preload the buildManifest for auto-export dynamic pages // to speed up hydrating query values let filteredBuildManifest = buildManifest @@ -1081,7 +1151,7 @@ export async function renderToHTML( return concurrentFeatures ? process.browser - ? await renderToReadableStream(content) + ? await renderToWebStream(content) : await renderToNodeStream(content, generateStaticHTML) : piperFromArray([ReactDOMServer.renderToString(content)]) } @@ -1154,7 +1224,7 @@ export async function renderToHTML( err: renderOpts.err ? serializeError(dev, renderOpts.err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML gsp: !!getStaticProps ? true : undefined, // whether the page is getStaticProps gssp: !!getServerSideProps ? true : undefined, // whether the page is getServerSideProps - rsc: isRSC ? true : undefined, // whether the page is a server components page + rsc: isServerComponent ? true : undefined, // whether the page is a server components page customServer, // whether the user is using a custom server gip: hasPageGetInitialProps ? true : undefined, // whether the page has getInitialProps appGip: !defaultAppGetInitialProps ? true : undefined, // whether the _app has getInitialProps @@ -1467,49 +1537,107 @@ function renderToNodeStream( }) } -function renderToReadableStream( +function connectReactServerReadableStreamToPiper( + write: (s: string) => void, + next: (err?: Error) => void +) { + let bufferedString = '' + let flushTimeout: null | NodeJS.Timeout = null + + function flushBuffer() { + // Intentionally delayed writing when using ReadableStream due to the lack + // of cork/uncork APIs. + if (!flushTimeout) { + flushTimeout = setTimeout(() => { + write(bufferedString) + bufferedString = '' + flushTimeout = null + }, 0) + } + } + + function startWriting(reader: ReadableStreamDefaultReader) { + const decoder = new TextDecoder() + const process = () => { + reader.read().then(({ done, value }: any) => { + if (!done) { + const s = typeof value === 'string' ? value : decoder.decode(value) + bufferedString += s + flushBuffer() + process() + } else { + // Make sure it's scheduled after the current flushing. + setTimeout(() => next(), 0) + } + }) + } + process() + } + + return { + startWriting, + } +} + +function renderToWebStream( element: React.ReactElement ): Promise { return new Promise((resolve, reject) => { - let reader: any = null let resolved = false + let underlyingStream: { + write: (s: string) => void + next: (err?: Error) => void + } | null = null + const doResolve = () => { - if (resolved) return - resolved = true - const piper: NodeWritablePiper = (res, next) => { - const streamReader: ReadableStreamDefaultReader = reader - const decoder = new TextDecoder() - const process = async () => { - streamReader.read().then(({ done, value }) => { - if (!done) { - const s = - typeof value === 'string' ? value : decoder.decode(value) - res.write(s) - process() - } else { - next() - } - }) + resolve((res, next) => { + underlyingStream = { + write: res.write, + next, } - process() - } - resolve(piper) + }) } - const readable = (ReactDOMServer as any).renderToReadableStream(element, { - onError(err: Error) { - if (!resolved) { - resolved = true - reject(err) + const { startWriting } = connectReactServerReadableStreamToPiper( + (s: string) => { + if (!underlyingStream) { + throw new Error( + 'invariant: `write` called without an underlying stream. This is a bug in Next.js' + ) } + underlyingStream.write(s) }, - onCompleteShell() { - doResolve() - }, - }) - // Start reader and lock stream immediately to consume readable, - // Otherwise the bytes before `onCompleteShell` will be missed. - reader = readable.getReader() + (err) => { + if (!underlyingStream) { + throw new Error( + 'invariant: `next` called without an underlying stream. This is a bug in Next.js' + ) + } + underlyingStream.next(err) + } + ) + + const reader = (ReactDOMServer as any) + .renderToReadableStream(element, { + onError(err: Error) { + if (!resolved) { + resolved = true + reject(err) + } + }, + onCompleteShell() { + if (!resolved) { + resolved = true + doResolve() + // Queue startWriting in microtasks to make sure reader is + // initialized. + Promise.resolve().then(() => { + startWriting(reader) + }) + } + }, + }) + .getReader() }) } diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index d63e17eb18bae3c..541a6ca8b113f90 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -1,6 +1,8 @@ /* eslint-disable import/no-extraneous-dependencies */ declare module 'next/dist/compiled/babel/plugin-transform-modules-commonjs' declare module 'next/dist/compiled/babel/plugin-syntax-jsx' +declare module 'next/dist/compiled/react-server-dom-webpack' +declare module 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' declare module 'browserslist' declare module 'cssnano-simple' { From 2d9ac399c145fc201bce9ebcef5edd4e996d9243 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 15 Nov 2021 17:36:53 +0100 Subject: [PATCH 11/19] Remove moment locale replace from craCompat as it's a default in Next.js 12 (#31431) --- packages/next/build/webpack-config.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index f25337ef17cbea1..d40ba507c94dff3 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1759,12 +1759,6 @@ export default async function getBaseWebpackConfig( webpackConfig.module?.rules && webpackConfig.plugins ) { - // CRA prevents loading all locales by default - // https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/webpack.config.js#L721 - webpackConfig.plugins.push( - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) - ) - // CRA allows importing non-webpack handled files with file-loader // these need to be the last rule to prevent catching other items // https://github.com/facebook/create-react-app/blob/fddce8a9e21bf68f37054586deb0c8636a45f50b/packages/react-scripts/config/webpack.config.js#L594 From eb0bd63af48ea9bec85670ad1bcbc455c5f879ec Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 15 Nov 2021 12:33:21 -0500 Subject: [PATCH 12/19] Fix basePath replacing server-side and `normalizeLocalePath()` when path is empty string (#30978) This fixes our `basePath` detection/replacing server-side as we were incorrectly considering `/docss` a match for a `basePath` of `/docs` which caused us to have an unexpected value in the `normalizeLocalePath` function. - Fixes #22429 - Regression introduced in #17757 - Fixes: https://github.com/vercel/next.js/issues/31423 Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- packages/next/server/dev/next-dev-server.ts | 6 ++--- packages/next/server/next-server.ts | 3 ++- packages/next/server/router.ts | 25 ++++++++++++++----- packages/next/server/web/next-url.ts | 3 ++- .../shared/lib/i18n/normalize-locale-path.ts | 5 +++- .../shared/lib/router/utils/parse-next-url.ts | 5 ++-- .../i18n-support-base-path/next.config.js | 2 +- .../i18n-support-base-path/test/index.test.js | 4 --- test/integration/i18n-support/test/shared.js | 21 ++++++++++++++++ 9 files changed, 55 insertions(+), 19 deletions(-) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 339d5e183bfcece..be90bcb2d0d55a7 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -34,7 +34,7 @@ import Server, { FindComponentsResult, } from '../next-server' import { normalizePagePath } from '../normalize-page-path' -import Router, { Params, route } from '../router' +import Router, { hasBasePath, Params, replaceBasePath, route } from '../router' import { eventCliSession } from '../../telemetry/events' import { Telemetry } from '../../telemetry/storage' import { setGlobal } from '../../trace' @@ -543,11 +543,11 @@ export default class DevServer extends Server { const { basePath } = this.nextConfig let originalPathname: string | null = null - if (basePath && parsedUrl.pathname?.startsWith(basePath)) { + if (basePath && hasBasePath(parsedUrl.pathname || '/', basePath)) { // strip basePath before handling dev bundles // If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/` originalPathname = parsedUrl.pathname - parsedUrl.pathname = parsedUrl.pathname!.slice(basePath.length) || '/' + parsedUrl.pathname = replaceBasePath(parsedUrl.pathname || '/', basePath) } const { pathname } = parsedUrl diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index c6ef07ff2168375..24a226a18a9b80e 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -67,6 +67,7 @@ import Router, { DynamicRoutes, PageChecker, Params, + replaceBasePath, route, Route, } from './router' @@ -369,7 +370,7 @@ export default class Server { }) if (url.basePath) { - req.url = req.url!.replace(this.nextConfig.basePath, '') || '/' + req.url = replaceBasePath(req.url!, this.nextConfig.basePath) addRequestMeta(req, '_nextHadBasePath', true) } diff --git a/packages/next/server/router.ts b/packages/next/server/router.ts index a9a9f6e4fb710d7..c4faf4767a4ed75 100644 --- a/packages/next/server/router.ts +++ b/packages/next/server/router.ts @@ -44,9 +44,22 @@ export type PageChecker = (pathname: string) => Promise const customRouteTypes = new Set(['rewrite', 'redirect', 'header']) -function replaceBasePath(basePath: string, pathname: string) { - // If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/` - return pathname!.replace(basePath, '') || '/' +export function hasBasePath(pathname: string, basePath: string): boolean { + return ( + typeof pathname === 'string' && + (pathname === basePath || pathname.startsWith(basePath + '/')) + ) +} + +export function replaceBasePath(pathname: string, basePath: string): string { + // ensure basePath is only stripped if it matches exactly + // and doesn't contain extra chars e.g. basePath /docs + // should replace for /docs, /docs/, /docs/a but not /docsss + if (hasBasePath(pathname, basePath)) { + pathname = pathname.substr(basePath.length) + if (!pathname.startsWith('/')) pathname = `/${pathname}` + } + return pathname } export default class Router { @@ -142,7 +155,7 @@ export default class Router { const applyCheckTrue = async (checkParsedUrl: NextUrlWithParsedQuery) => { const originalFsPathname = checkParsedUrl.pathname - const fsPathname = replaceBasePath(this.basePath, originalFsPathname!) + const fsPathname = replaceBasePath(originalFsPathname!, this.basePath) for (const fsRoute of this.fsRoutes) { const fsParams = fsRoute.match(fsPathname) @@ -283,8 +296,8 @@ export default class Router { const keepLocale = isCustomRoute const currentPathnameNoBasePath = replaceBasePath( - this.basePath, - currentPathname + currentPathname, + this.basePath ) if (!keepBasePath) { diff --git a/packages/next/server/web/next-url.ts b/packages/next/server/web/next-url.ts index 4f30e80ed05bb0c..a82cff13c804b15 100644 --- a/packages/next/server/web/next-url.ts +++ b/packages/next/server/web/next-url.ts @@ -2,6 +2,7 @@ import type { PathLocale } from '../../shared/lib/i18n/normalize-locale-path' import type { DomainLocale, I18NConfig } from '../config-shared' import { getLocaleMetadata } from '../../shared/lib/i18n/get-locale-metadata' import cookie from 'next/dist/compiled/cookie' +import { replaceBasePath } from '../router' /** * TODO @@ -48,7 +49,7 @@ export class NextURL extends URL { const { headers = {}, basePath, i18n } = this._options if (basePath && this._url.pathname.startsWith(basePath)) { - this._url.pathname = this._url.pathname.replace(basePath, '') || '/' + this._url.pathname = replaceBasePath(this._url.pathname, basePath) this._basePath = basePath } else { this._basePath = '' diff --git a/packages/next/shared/lib/i18n/normalize-locale-path.ts b/packages/next/shared/lib/i18n/normalize-locale-path.ts index ee21339dfc598d8..d687605e14a28b8 100644 --- a/packages/next/shared/lib/i18n/normalize-locale-path.ts +++ b/packages/next/shared/lib/i18n/normalize-locale-path.ts @@ -21,7 +21,10 @@ export function normalizeLocalePath( const pathnameParts = pathname.split('/') ;(locales || []).some((locale) => { - if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) { + if ( + pathnameParts[1] && + pathnameParts[1].toLowerCase() === locale.toLowerCase() + ) { detectedLocale = locale pathnameParts.splice(1, 1) pathname = pathnameParts.join('/') || '/' diff --git a/packages/next/shared/lib/router/utils/parse-next-url.ts b/packages/next/shared/lib/router/utils/parse-next-url.ts index 1b86fb113236e6d..57985ecfaf0c511 100644 --- a/packages/next/shared/lib/router/utils/parse-next-url.ts +++ b/packages/next/shared/lib/router/utils/parse-next-url.ts @@ -4,6 +4,7 @@ import { parseUrl } from './parse-url' import type { NextConfig, DomainLocale } from '../../../../server/config-shared' import type { ParsedUrl } from './parse-url' import type { PathLocale } from '../../i18n/normalize-locale-path' +import { hasBasePath, replaceBasePath } from '../../../../server/router' interface Params { headers?: { [key: string]: string | string[] | undefined } @@ -15,8 +16,8 @@ export function parseNextUrl({ headers, nextConfig, url = '/' }: Params) { const urlParsed: ParsedNextUrl = parseUrl(url) const { basePath } = nextConfig - if (basePath && urlParsed.pathname.startsWith(basePath)) { - urlParsed.pathname = urlParsed.pathname.replace(basePath, '') || '/' + if (basePath && hasBasePath(urlParsed.pathname, basePath)) { + urlParsed.pathname = replaceBasePath(urlParsed.pathname, basePath) urlParsed.basePath = basePath } diff --git a/test/integration/i18n-support-base-path/next.config.js b/test/integration/i18n-support-base-path/next.config.js index 1c73f3a4211a3cb..b9ea28786229676 100644 --- a/test/integration/i18n-support-base-path/next.config.js +++ b/test/integration/i18n-support-base-path/next.config.js @@ -1,6 +1,6 @@ module.exports = { // target: 'experimental-serverless-trace', - // basePath: '/docs', + basePath: '/docs', i18n: { // localeDetection: false, locales: [ diff --git a/test/integration/i18n-support-base-path/test/index.test.js b/test/integration/i18n-support-base-path/test/index.test.js index 48de91414112c98..4ce30b352bc490d 100644 --- a/test/integration/i18n-support-base-path/test/index.test.js +++ b/test/integration/i18n-support-base-path/test/index.test.js @@ -41,7 +41,6 @@ describe('i18n Support basePath', () => { isDev: true, } beforeAll(async () => { - nextConfig.replace('// basePath', 'basePath') nextConfig.replace(/__EXTERNAL_PORT__/g, ctx.externalPort) await fs.remove(join(appDir, '.next')) curCtx.appPort = await findPort() @@ -57,7 +56,6 @@ describe('i18n Support basePath', () => { describe('production mode', () => { beforeAll(async () => { - nextConfig.replace('// basePath', 'basePath') nextConfig.replace(/__EXTERNAL_PORT__/g, ctx.externalPort) await fs.remove(join(appDir, '.next')) await nextBuild(appDir) @@ -78,7 +76,6 @@ describe('i18n Support basePath', () => { beforeAll(async () => { await fs.remove(join(appDir, '.next')) nextConfig.replace('// target', 'target') - nextConfig.replace('// basePath', 'basePath') nextConfig.replace(/__EXTERNAL_PORT__/g, ctx.externalPort) await nextBuild(appDir) @@ -194,7 +191,6 @@ describe('i18n Support basePath', () => { describe('with localeDetection disabled', () => { beforeAll(async () => { await fs.remove(join(appDir, '.next')) - nextConfig.replace('// basePath', 'basePath') nextConfig.replace('// localeDetection', 'localeDetection') await nextBuild(appDir) diff --git a/test/integration/i18n-support/test/shared.js b/test/integration/i18n-support/test/shared.js index 1300d6e3c76639c..b76143614412e7e 100644 --- a/test/integration/i18n-support/test/shared.js +++ b/test/integration/i18n-support/test/shared.js @@ -35,6 +35,27 @@ async function addDefaultLocaleCookie(browser) { } export function runTests(ctx) { + if (ctx.basePath) { + it.only('should handle basePath like pathname', async () => { + const { basePath } = ctx + + for (const pathname of [ + `${basePath}extra`, + `/en${basePath}`, + `${basePath}extra/en`, + `${basePath}en`, + `/en${basePath}`, + ]) { + console.error('checking', pathname) + const res = await fetchViaHTTP(ctx.appPort, pathname, undefined, { + redirect: 'manual', + }) + expect(res.status).toBe(404) + expect(await res.text()).toContain('This page could not be found') + } + }) + } + it('should redirect external domain correctly', async () => { const res = await fetchViaHTTP( ctx.appPort, From ef5795327a99ec8a3dfbe0b40fc1fdde3cdc58fb Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 15 Nov 2021 18:49:50 +0100 Subject: [PATCH 13/19] Close stream when fatal error occurs (#31164) * support custom 500 error in streaming * remove unused imports --- packages/next/build/entries.ts | 1 + .../next-middleware-ssr-loader/index.ts | 72 +++++++++++++------ packages/next/server/dev/hot-reloader.ts | 1 + .../app/pages/err.js | 5 ++ .../app/pages/err/render.js | 7 ++ .../app/pages/err/suspense.js | 20 ++++++ .../test/index.test.js | 13 ++++ 7 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 test/integration/react-streaming-and-server-components/app/pages/err.js create mode 100644 test/integration/react-streaming-and-server-components/app/pages/err/render.js create mode 100644 test/integration/react-streaming-and-server-components/app/pages/err/suspense.js diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 3c4feca3e299aed..a4b97bc00bd4544 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -162,6 +162,7 @@ export function createEntrypoints( page, absoluteAppPath: pages['/_app'], absoluteDocumentPath: pages['/_document'], + absoluteErrorPath: pages['/_error'], absolutePagePath, isServerComponent: isFlight, buildId, diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts index d056811f7ef58b0..bf2fd5bfe8b68af 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts @@ -5,6 +5,7 @@ export default async function middlewareRSCLoader(this: any) { absolutePagePath, absoluteAppPath, absoluteDocumentPath, + absoluteErrorPath, basePath, isServerComponent: isServerComponentQuery, assetPrefix, @@ -14,23 +15,28 @@ export default async function middlewareRSCLoader(this: any) { const isServerComponent = isServerComponentQuery === 'true' const stringifiedAbsolutePagePath = stringifyRequest(this, absolutePagePath) const stringifiedAbsoluteAppPath = stringifyRequest(this, absoluteAppPath) + const stringifiedAbsoluteErrorPath = stringifyRequest(this, absoluteErrorPath) + const stringified500PagePath = stringifyRequest(this, './pages/500') const stringifiedAbsoluteDocumentPath = stringifyRequest( this, absoluteDocumentPath ) - let appDefinition = `const App = require(${stringifiedAbsoluteAppPath}).default` - let documentDefinition = `const Document = require(${stringifiedAbsoluteDocumentPath}).default` - const transformed = ` import { adapter } from 'next/dist/server/web/adapter' - import { RouterContext } from 'next/dist/shared/lib/router-context' import { renderToHTML } from 'next/dist/server/web/render' - ${appDefinition} - ${documentDefinition} - + import App from ${stringifiedAbsoluteAppPath} + import Document from ${stringifiedAbsoluteDocumentPath} + + let ErrorPage + try { + ErrorPage = require(${stringified500PagePath}).default + } catch (_) { + ErrorPage = require(${stringifiedAbsoluteErrorPath}).default + } + const { default: Page, config, @@ -66,6 +72,7 @@ export default async function middlewareRSCLoader(this: any) { } delete query.__flight__ + const req = { url: pathname } const renderOpts = { Component, pageConfig: config || {}, @@ -97,32 +104,51 @@ export default async function middlewareRSCLoader(this: any) { const transformStream = new TransformStream() const writer = transformStream.writable.getWriter() const encoder = new TextEncoder() - + let result + let renderError + let statusCode = 200 try { - const result = await renderToHTML( - { url: pathname }, + result = await renderToHTML( + req, {}, pathname, query, renderOpts ) - result.pipe({ - write: str => writer.write(encoder.encode(str)), - end: () => writer.close(), - // Not implemented: cork/uncork/on/removeListener - }) } catch (err) { - return new Response( - (err || 'An error occurred while rendering ' + pathname + '.').toString(), - { - status: 500, - headers: { 'x-middleware-ssr': '1' } - } - ) + renderError = err + statusCode = 500 + } + if (renderError) { + try { + const errorRes = { statusCode, err: renderError } + result = await renderToHTML( + req, + errorRes, + pathname, + query, + { ...renderOpts, Component: ErrorPage } + ) + } catch (err) { + return new Response( + (err || 'An error occurred while rendering ' + pathname + '.').toString(), + { + status: 500, + headers: { 'x-middleware-ssr': '1' } + } + ) + } } + result.pipe({ + write: str => writer.write(encoder.encode(str)), + end: () => writer.close(), + // Not implemented: cork/uncork/on/removeListener + }) + return new Response(transformStream.readable, { - headers: { 'x-middleware-ssr': '1' } + headers: { 'x-middleware-ssr': '1' }, + status: statusCode }) } diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 7a0164864c2c5b9..3887dace73edac3 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -528,6 +528,7 @@ export default class HotReloader { page, absoluteAppPath: this.pagesMapping['/_app'], absoluteDocumentPath: this.pagesMapping['/_document'], + absoluteErrorPath: this.pagesMapping['/_error'], absolutePagePath, isServerComponent, buildId: this.buildId, diff --git a/test/integration/react-streaming-and-server-components/app/pages/err.js b/test/integration/react-streaming-and-server-components/app/pages/err.js new file mode 100644 index 000000000000000..61c7136f7ba7e48 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/err.js @@ -0,0 +1,5 @@ +const page = () => 'page with err' +page.getInitialProps = () => { + throw new Error('oops') +} +export default page diff --git a/test/integration/react-streaming-and-server-components/app/pages/err/render.js b/test/integration/react-streaming-and-server-components/app/pages/err/render.js new file mode 100644 index 000000000000000..c05e49826ae7796 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/err/render.js @@ -0,0 +1,7 @@ +let did = false +export default function Error() { + if (!did && typeof window === 'undefined') { + did = true + throw new Error('oops') + } +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/err/suspense.js b/test/integration/react-streaming-and-server-components/app/pages/err/suspense.js new file mode 100644 index 000000000000000..645988d009dc376 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/err/suspense.js @@ -0,0 +1,20 @@ +import { Suspense } from 'react' + +let did = false +function Error() { + if (!did && typeof window === 'undefined') { + did = true + throw new Error('broken page') + } +} + +export default function page() { + return ( + <> +

Hey Error

+ + + + + ) +} diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index d0d26c4671a1867..371953a331b90ed 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -24,6 +24,7 @@ const nativeModuleTestAppDir = join(__dirname, '../unsupported-native-module') const distDir = join(__dirname, '../app/.next') const documentPage = new File(join(appDir, 'pages/_document.jsx')) const appPage = new File(join(appDir, 'pages/_app.js')) +const error500Page = new File(join(appDir, 'pages/500.js')) const documentWithGip = ` import { Html, Head, Main, NextScript } from 'next/document' @@ -55,6 +56,12 @@ function App({ Component, pageProps }) { export default App ` +const page500 = ` +export default function Page500() { + return 'custom-500-page' +} +` + async function nextBuild(dir) { return await _nextBuild(dir, [], { stdout: true, @@ -100,11 +107,13 @@ describe('concurrentFeatures - prod', () => { const context = { appDir } beforeAll(async () => { + error500Page.write(page500) context.appPort = await findPort() await nextBuild(context.appDir) context.server = await nextStart(context.appDir, context.appPort) }) afterAll(async () => { + error500Page.delete() await killApp(context.server) }) @@ -155,10 +164,12 @@ describe('concurrentFeatures - dev', () => { const context = { appDir } beforeAll(async () => { + error500Page.write(page500) context.appPort = await findPort() context.server = await nextDev(context.appDir, context.appPort) }) afterAll(async () => { + error500Page.delete() await killApp(context.server) }) @@ -217,6 +228,7 @@ async function runBasicTests(context) { ) const path404HTML = await renderViaHTTP(context.appPort, '/404') + const path500HTML = await renderViaHTTP(context.appPort, '/err') const pathNotFoundHTML = await renderViaHTTP( context.appPort, '/this-is-not-found' @@ -230,6 +242,7 @@ async function runBasicTests(context) { expect(dynamicRouteHTML2).toContain('[pid]') expect(path404HTML).toContain('custom-404-page') + expect(path500HTML).toContain('custom-500-page') expect(pathNotFoundHTML).toContain('custom-404-page') }) From be03a1d17bf8aa16cae3c0658ad553ae93fd1ef6 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Nov 2021 12:46:12 -0600 Subject: [PATCH 14/19] Remove .only and ensure jest lint rules apply for all tests (#31456) --- .eslintrc.json | 5 +- .../basic/styled-components-disabled.test.ts | 1 + test/integration/i18n-support/test/shared.js | 2 +- .../production-swcminify/test/security.js | 54 ------------------- test/integration/production/test/security.js | 54 ------------------- .../test/css.js | 14 ++--- .../required-server-files-i18n.test.ts | 2 +- 7 files changed, 13 insertions(+), 119 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1cee5507a590152..26824c94ba6675d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -30,14 +30,15 @@ }, "overrides": [ { - "files": ["test/**/*.test.js"], + "files": ["test/**/*.js", "test/**/*.ts", "**/*.test.ts"], "extends": ["plugin:jest/recommended"], "rules": { "jest/expect-expect": "off", "jest/no-disabled-tests": "off", "jest/no-conditional-expect": "off", "jest/valid-title": "off", - "jest/no-interpolation-in-snapshots": "off" + "jest/no-interpolation-in-snapshots": "off", + "jest/no-export": "off" } }, { "files": ["**/__tests__/**"], "env": { "jest": true } }, diff --git a/test/development/basic/styled-components-disabled.test.ts b/test/development/basic/styled-components-disabled.test.ts index af49dd5deb910d4..3e96e2081e5c51c 100644 --- a/test/development/basic/styled-components-disabled.test.ts +++ b/test/development/basic/styled-components-disabled.test.ts @@ -54,6 +54,7 @@ describe('styled-components SWC transform', () => { throw new Error('did not find mismatch') } catch (err) { // Verify that it really has the logs + // eslint-disable-next-line jest/no-try-expect expect(await matchLogs$(browser)).toBe(true) } } finally { diff --git a/test/integration/i18n-support/test/shared.js b/test/integration/i18n-support/test/shared.js index b76143614412e7e..f24c92461b060d0 100644 --- a/test/integration/i18n-support/test/shared.js +++ b/test/integration/i18n-support/test/shared.js @@ -36,7 +36,7 @@ async function addDefaultLocaleCookie(browser) { export function runTests(ctx) { if (ctx.basePath) { - it.only('should handle basePath like pathname', async () => { + it('should handle basePath like pathname', async () => { const { basePath } = ctx for (const pathname of [ diff --git a/test/integration/production-swcminify/test/security.js b/test/integration/production-swcminify/test/security.js index fd674399249bf57..5d1c2f880a58971 100644 --- a/test/integration/production-swcminify/test/security.js +++ b/test/integration/production-swcminify/test/security.js @@ -291,60 +291,6 @@ module.exports = (context) => { expect(hostname).not.toBe('example.com') }) - it('should handle encoded value in the pathname correctly /', async () => { - const res = await fetchViaHTTP( - context.appPort, - '/redirect/me/to-about/%2fgoogle.com', - undefined, - { - redirect: 'manual', - } - ) - - const { pathname, hostname } = url.parse( - res.headers.get('location') || '' - ) - expect(res.status).toBe(307) - expect(pathname).toBe('/%2fgoogle.com/about') - expect(hostname).not.toBe('google.com') - }) - - it('should handle encoded value in the pathname to query correctly (/)', async () => { - const res = await fetchViaHTTP( - context.appPort, - '/redirect-query-test/%2Fgoogle.com', - undefined, - { - redirect: 'manual', - } - ) - - const { pathname, hostname, query } = url.parse( - res.headers.get('location') || '' - ) - expect(res.status).toBe(307) - expect(pathname).toBe('/about') - expect(query).toBe('foo=%2Fgoogle.com') - expect(hostname).not.toBe('google.com') - expect(hostname).not.toMatch(/google/) - }) - - it('should handle encoded / value for trailing slash correctly', async () => { - const res = await fetchViaHTTP( - context.appPort, - '/%2fexample.com/', - undefined, - { redirect: 'manual' } - ) - - const { pathname, hostname } = url.parse( - res.headers.get('location') || '' - ) - expect(res.status).toBe(308) - expect(pathname).toBe('/%2fexample.com') - expect(hostname).not.toBe('example.com') - }) - if (browserName !== 'internet explorer') { it('should not execute script embedded inside svg image', async () => { let browser diff --git a/test/integration/production/test/security.js b/test/integration/production/test/security.js index fd674399249bf57..5d1c2f880a58971 100644 --- a/test/integration/production/test/security.js +++ b/test/integration/production/test/security.js @@ -291,60 +291,6 @@ module.exports = (context) => { expect(hostname).not.toBe('example.com') }) - it('should handle encoded value in the pathname correctly /', async () => { - const res = await fetchViaHTTP( - context.appPort, - '/redirect/me/to-about/%2fgoogle.com', - undefined, - { - redirect: 'manual', - } - ) - - const { pathname, hostname } = url.parse( - res.headers.get('location') || '' - ) - expect(res.status).toBe(307) - expect(pathname).toBe('/%2fgoogle.com/about') - expect(hostname).not.toBe('google.com') - }) - - it('should handle encoded value in the pathname to query correctly (/)', async () => { - const res = await fetchViaHTTP( - context.appPort, - '/redirect-query-test/%2Fgoogle.com', - undefined, - { - redirect: 'manual', - } - ) - - const { pathname, hostname, query } = url.parse( - res.headers.get('location') || '' - ) - expect(res.status).toBe(307) - expect(pathname).toBe('/about') - expect(query).toBe('foo=%2Fgoogle.com') - expect(hostname).not.toBe('google.com') - expect(hostname).not.toMatch(/google/) - }) - - it('should handle encoded / value for trailing slash correctly', async () => { - const res = await fetchViaHTTP( - context.appPort, - '/%2fexample.com/', - undefined, - { redirect: 'manual' } - ) - - const { pathname, hostname } = url.parse( - res.headers.get('location') || '' - ) - expect(res.status).toBe(308) - expect(pathname).toBe('/%2fexample.com') - expect(hostname).not.toBe('example.com') - }) - if (browserName !== 'internet explorer') { it('should not execute script embedded inside svg image', async () => { let browser diff --git a/test/integration/react-streaming-and-server-components/test/css.js b/test/integration/react-streaming-and-server-components/test/css.js index 141e88a7a8328fc..f2cf95d9af075d5 100644 --- a/test/integration/react-streaming-and-server-components/test/css.js +++ b/test/integration/react-streaming-and-server-components/test/css.js @@ -17,11 +17,11 @@ export default function (context) { expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) }) // TODO: fix this test - // it.skip('should include css modules with `serverComponents: true`', async () => { - // const browser = await webdriver(context.appPort, '/css-modules') - // const currentColor = await browser.eval( - // `window.getComputedStyle(document.querySelector('h1')).color` - // ) - // expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) - // }) + it.skip('should include css modules with `serverComponents: true`', async () => { + const browser = await webdriver(context.appPort, '/css-modules') + const currentColor = await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) + }) } diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts index 6285ccfb69a7590..b9b311108f8253e 100644 --- a/test/production/required-server-files-i18n.test.ts +++ b/test/production/required-server-files-i18n.test.ts @@ -701,7 +701,7 @@ describe('should set-up next', () => { expect(JSON.parse($('#router').text()).locale).toBe('en') }) - it('should have the correct asPath for fallback page', async () => { + it('should have the correct asPath for fallback page locale', async () => { const res = await fetchViaHTTP(appPort, '/fr/fallback/[slug]', undefined, { headers: { 'x-matched-path': '/fr/fallback/[slug]', From 66d9b4e14a2b59475480f24ad7c4a19d0f7d7eb7 Mon Sep 17 00:00:00 2001 From: "jj@jjsweb.site" Date: Mon, 15 Nov 2021 12:54:42 -0600 Subject: [PATCH 15/19] v12.0.4-canary.14 --- 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/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/package.json | 12 ++++++------ packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index 145a554a72f23e3..ec2c7bdc071f4c2 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.4-canary.13" + "version": "12.0.4-canary.14" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index aed0dc5f0d4dfa6..47884fca1a19ac2 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 0c6ffb2e404b190..08f1b21622111e2 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "12.0.4-canary.13", + "@next/eslint-plugin-next": "12.0.4-canary.14", "@rushstack/eslint-patch": "^1.0.6", "@typescript-eslint/parser": "^4.20.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 1e9e965ebb6db06..257a78e4dbbbdc3 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": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 9f8867aea136769..5a5d7d9a4538a1b 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index d614766a0db2e28..ce5047de35e3a00 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 4336d5a0314868c..4267a827f1148c9 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index de3355aec7177a0..2b05f27d9ecb6e6 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 5bdfd6c48354787..56b4dd8b2c9416d 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "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 3ef00be511b2d69..4595c0765a498c0 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "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 f4565741156558c..7526b321a755bab 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/package.json b/packages/next/package.json index 86e3d17c40b9b95..fe203120d03f7b1 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,10 +70,10 @@ "@babel/runtime": "7.15.4", "@hapi/accept": "5.0.2", "@napi-rs/triples": "1.0.3", - "@next/env": "12.0.4-canary.13", - "@next/polyfill-module": "12.0.4-canary.13", - "@next/react-dev-overlay": "12.0.4-canary.13", - "@next/react-refresh-utils": "12.0.4-canary.13", + "@next/env": "12.0.4-canary.14", + "@next/polyfill-module": "12.0.4-canary.14", + "@next/react-dev-overlay": "12.0.4-canary.14", + "@next/react-refresh-utils": "12.0.4-canary.14", "acorn": "8.5.0", "assert": "2.0.0", "browserify-zlib": "0.2.0", @@ -156,7 +156,7 @@ "@babel/traverse": "7.15.0", "@babel/types": "7.15.0", "@napi-rs/cli": "1.2.1", - "@next/polyfill-nomodule": "12.0.4-canary.13", + "@next/polyfill-nomodule": "12.0.4-canary.14", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index af4bf3d160f8604..59c3d7f20d695c0 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": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "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 b0124f71a99e8fe..f582454ca627732 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": "12.0.4-canary.13", + "version": "12.0.4-canary.14", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From b51a020941f79dccf818bad841bf377d6fd7bd1d Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Mon, 15 Nov 2021 20:52:44 +0100 Subject: [PATCH 16/19] middleware: add request referrer support (#31343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes: https://github.com/vercel/next.js/issues/30353 According with spec, `'about:client'` is the default value is the user doesn't provide it. It needs to add a test there, looks like there no unit tests for these classes 🤔 --- packages/next/server/web/spec-compliant/request.ts | 10 ++++++---- test/unit/web-runtime/request.test.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/next/server/web/spec-compliant/request.ts b/packages/next/server/web/spec-compliant/request.ts index a06b5c0f3b8b707..aa4751e640708bd 100644 --- a/packages/next/server/web/spec-compliant/request.ts +++ b/packages/next/server/web/spec-compliant/request.ts @@ -10,6 +10,7 @@ class BaseRequest extends Body implements Request { credentials: RequestCredentials headers: Headers method: string + referrer: string redirect: RequestRedirect url: NextURL } @@ -48,6 +49,7 @@ class BaseRequest extends Body implements Request { init.credentials || getProp(input, 'credentials') || 'same-origin', headers, method, + referrer: init.referrer || 'about:client', redirect: init.redirect || getProp(input, 'redirect') || 'follow', url: new NextURL(typeof input === 'string' ? input : input.url), } @@ -65,6 +67,10 @@ class BaseRequest extends Body implements Request { return this[INTERNALS].method } + get referrer() { + return this[INTERNALS].referrer + } + get headers() { return this[INTERNALS].headers } @@ -97,10 +103,6 @@ class BaseRequest extends Body implements Request { return notImplemented('Request', 'destination') } - get referrer() { - return notImplemented('Request', 'referrer') - } - get referrerPolicy() { return notImplemented('Request', 'referrerPolicy') } diff --git a/test/unit/web-runtime/request.test.ts b/test/unit/web-runtime/request.test.ts index 5b549ce573c2cca..2edcd389be9d345 100644 --- a/test/unit/web-runtime/request.test.ts +++ b/test/unit/web-runtime/request.test.ts @@ -35,3 +35,13 @@ it('parses and reconstructs the URL alone', async () => { it('throws when the URL is malformed', async () => { expect(() => new Request('meeeh')).toThrowError('Invalid URL') }) + +it('Request.referrer is `about:client` by default', async () => { + const request = new Request('https://vercel.com') + expect(request.referrer).toBe('about:client') +}) + +it('Request.referrer can be customized', async () => { + const request = new Request('https://vercel.com', { referrer: 'client' }) + expect(request.referrer).toBe('client') +}) From a9eb0de8e792b237264e248bc70d0e834cd5fe30 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Nov 2021 13:55:32 -0600 Subject: [PATCH 17/19] Ensure swc dep is copied for isolated tests (#31462) --- test/lib/create-next-install.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/lib/create-next-install.js b/test/lib/create-next-install.js index dd9ddd76cd064ae..30d3d701a46f446 100644 --- a/test/lib/create-next-install.js +++ b/test/lib/create-next-install.js @@ -11,6 +11,25 @@ async function createNextInstall(dependencies) { const installDir = path.join(tmpDir, `next-install-${Date.now()}`) const tmpRepoDir = path.join(tmpDir, `next-repo-${Date.now()}`) + // ensure swc binary is present in the native folder if + // not already built + for (const folder of await fs.readdir( + path.join(origRepoDir, 'node_modules/@next') + )) { + if (folder.startsWith('swc-')) { + const swcPkgPath = path.join(origRepoDir, 'node_modules/@next', folder) + await fs.copy( + swcPkgPath, + path.join(origRepoDir, 'packages/next/native'), + { + filter: (item) => + item === swcPkgPath || + (item.endsWith('.node') && !fs.pathExistsSync(item)), + } + ) + } + } + for (const item of ['package.json', 'yarn.lock', 'packages']) { await fs.copy(path.join(origRepoDir, item), path.join(tmpRepoDir, item), { filter: (item) => { @@ -23,6 +42,7 @@ async function createNextInstall(dependencies) { }, }) } + const pkgPaths = await linkPackages(tmpRepoDir) await fs.ensureDir(installDir) From b2046400086fcfc18d77a4695f71fae1a4e4dbc4 Mon Sep 17 00:00:00 2001 From: "jj@jjsweb.site" Date: Mon, 15 Nov 2021 14:07:57 -0600 Subject: [PATCH 18/19] v12.0.4-canary.15 --- 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/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/package.json | 12 ++++++------ packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index ec2c7bdc071f4c2..e7979b98f7c27a0 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.4-canary.14" + "version": "12.0.4-canary.15" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 47884fca1a19ac2..70fc02f602f2d45 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 08f1b21622111e2..c42080a5251d5ef 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "12.0.4-canary.14", + "@next/eslint-plugin-next": "12.0.4-canary.15", "@rushstack/eslint-patch": "^1.0.6", "@typescript-eslint/parser": "^4.20.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 257a78e4dbbbdc3..96665ed9064fd94 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": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 5a5d7d9a4538a1b..d3fe9d06d59cc57 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index ce5047de35e3a00..22143b8077c245f 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 4267a827f1148c9..a7ac0d97f515cbf 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 2b05f27d9ecb6e6..dd53880103f42d3 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 56b4dd8b2c9416d..7725f923a248867 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "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 4595c0765a498c0..033c93acb8f95ce 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "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 7526b321a755bab..8e796fe38e242db 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/package.json b/packages/next/package.json index fe203120d03f7b1..c742c79cfac0e84 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,10 +70,10 @@ "@babel/runtime": "7.15.4", "@hapi/accept": "5.0.2", "@napi-rs/triples": "1.0.3", - "@next/env": "12.0.4-canary.14", - "@next/polyfill-module": "12.0.4-canary.14", - "@next/react-dev-overlay": "12.0.4-canary.14", - "@next/react-refresh-utils": "12.0.4-canary.14", + "@next/env": "12.0.4-canary.15", + "@next/polyfill-module": "12.0.4-canary.15", + "@next/react-dev-overlay": "12.0.4-canary.15", + "@next/react-refresh-utils": "12.0.4-canary.15", "acorn": "8.5.0", "assert": "2.0.0", "browserify-zlib": "0.2.0", @@ -156,7 +156,7 @@ "@babel/traverse": "7.15.0", "@babel/types": "7.15.0", "@napi-rs/cli": "1.2.1", - "@next/polyfill-nomodule": "12.0.4-canary.14", + "@next/polyfill-nomodule": "12.0.4-canary.15", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 59c3d7f20d695c0..7adf26e9ca2ae61 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": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "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 f582454ca627732..31feff2d3f5c504 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": "12.0.4-canary.14", + "version": "12.0.4-canary.15", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From abd87a515ea5954b7f96de455a74cb80eb83757f Mon Sep 17 00:00:00 2001 From: "jj@jjsweb.site" Date: Mon, 15 Nov 2021 14:43:54 -0600 Subject: [PATCH 19/19] v12.0.4 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/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/package.json | 12 ++++++------ packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index e7979b98f7c27a0..974015f6d012b71 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.4-canary.15" + "version": "12.0.4" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 70fc02f602f2d45..7239fa113544e17 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "12.0.4-canary.15", + "version": "12.0.4", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index c42080a5251d5ef..22c43d55f8c04af 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "12.0.4-canary.15", + "version": "12.0.4", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "12.0.4-canary.15", + "@next/eslint-plugin-next": "12.0.4", "@rushstack/eslint-patch": "^1.0.6", "@typescript-eslint/parser": "^4.20.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 96665ed9064fd94..dc920f75a3a5f8d 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": "12.0.4-canary.15", + "version": "12.0.4", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index d3fe9d06d59cc57..3461f4e05c56228 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "12.0.4-canary.15", + "version": "12.0.4", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 22143b8077c245f..8ead414a30c7c94 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.4-canary.15", + "version": "12.0.4", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index a7ac0d97f515cbf..f0c60a36b90402a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.4-canary.15", + "version": "12.0.4", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index dd53880103f42d3..e7e59de0478b638 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.4-canary.15", + "version": "12.0.4", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 7725f923a248867..95328f602e76f4f 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "12.0.4-canary.15", + "version": "12.0.4", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 033c93acb8f95ce..a4cea9f2618d378 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "12.0.4-canary.15", + "version": "12.0.4", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 8e796fe38e242db..fa41347f3ecfd48 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "12.0.4-canary.15", + "version": "12.0.4", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/package.json b/packages/next/package.json index c742c79cfac0e84..80aa6b0aeca614c 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.4-canary.15", + "version": "12.0.4", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,10 +70,10 @@ "@babel/runtime": "7.15.4", "@hapi/accept": "5.0.2", "@napi-rs/triples": "1.0.3", - "@next/env": "12.0.4-canary.15", - "@next/polyfill-module": "12.0.4-canary.15", - "@next/react-dev-overlay": "12.0.4-canary.15", - "@next/react-refresh-utils": "12.0.4-canary.15", + "@next/env": "12.0.4", + "@next/polyfill-module": "12.0.4", + "@next/react-dev-overlay": "12.0.4", + "@next/react-refresh-utils": "12.0.4", "acorn": "8.5.0", "assert": "2.0.0", "browserify-zlib": "0.2.0", @@ -156,7 +156,7 @@ "@babel/traverse": "7.15.0", "@babel/types": "7.15.0", "@napi-rs/cli": "1.2.1", - "@next/polyfill-nomodule": "12.0.4-canary.15", + "@next/polyfill-nomodule": "12.0.4", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 7adf26e9ca2ae61..46b0e5b6eca2a8f 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": "12.0.4-canary.15", + "version": "12.0.4", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 31feff2d3f5c504..416281adbb25ddf 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": "12.0.4-canary.15", + "version": "12.0.4", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js",