diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 9284955029dde..65105fcc1e919 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -358,10 +358,10 @@ export default async function createsUser(formData) { } ``` -Once the fields have been validated on the server, you can return a serializable object in your action and use the React [`useFormState`](https://react.dev/reference/react-dom/hooks/useFormState) hook to show a message to the user. +Once the fields have been validated on the server, you can return a serializable object in your action and use the React [`useActionState`](https://react.dev/reference/react/useActionState) hook to show a message to the user. -- By passing the action to `useFormState`, the action's function signature changes to receive a new `prevState` or `initialState` parameter as its first argument. -- `useFormState` is a React hook and therefore must be used in a Client Component. +- By passing the action to `useActionState`, the action's function signature changes to receive a new `prevState` or `initialState` parameter as its first argument. +- `useActionState` is a React hook and therefore must be used in a Client Component. ```tsx filename="app/actions.ts" switcher 'use server' @@ -385,12 +385,12 @@ export async function createUser(prevState, formData) { } ``` -Then, you can pass your action to the `useFormState` hook and use the returned `state` to display an error message. +Then, you can pass your action to the `useActionState` hook and use the returned `state` to display an error message. ```tsx filename="app/ui/signup.tsx" switcher 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { createUser } from '@/app/actions' const initialState = { @@ -398,7 +398,7 @@ const initialState = { } export function Signup() { - const [state, formAction] = useFormState(createUser, initialState) + const [state, formAction] = useActionState(createUser, initialState) return (
@@ -417,7 +417,7 @@ export function Signup() { ```jsx filename="app/ui/signup.js" switcher 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { createUser } from '@/app/actions' const initialState = { @@ -425,7 +425,7 @@ const initialState = { } export function Signup() { - const [state, formAction] = useFormState(createUser, initialState) + const [state, formAction] = useActionState(createUser, initialState) return ( @@ -739,7 +739,7 @@ export async function createTodo(prevState, formData) { > **Good to know:** > -> - Aside from throwing the error, you can also return an object to be handled by `useFormState`. See [Server-side validation and error handling](#server-side-validation-and-error-handling). +> - Aside from throwing the error, you can also return an object to be handled by `useActionState`. See [Server-side validation and error handling](#server-side-validation-and-error-handling). ### Revalidating data @@ -1002,5 +1002,5 @@ For more information on Server Actions, check out the following React docs: - [`"use server"`](https://react.dev/reference/react/use-server) - [``](https://react.dev/reference/react-dom/components/form) - [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) -- [`useFormState`](https://react.dev/reference/react-dom/hooks/useFormState) +- [`useActionState`](https://react.dev/reference/react/useActionState) - [`useOptimistic`](https://react.dev/reference/react/useOptimistic) diff --git a/docs/02-app/01-building-your-application/04-caching/index.mdx b/docs/02-app/01-building-your-application/04-caching/index.mdx index 3d68b46d0f0c3..a3975e8ca9fcb 100644 --- a/docs/02-app/01-building-your-application/04-caching/index.mdx +++ b/docs/02-app/01-building-your-application/04-caching/index.mdx @@ -369,7 +369,7 @@ The cache is stored in the browser's temporary memory. Two factors determine how While a page refresh will clear **all** cached segments, the automatic invalidation period only affects the individual segment from the time it was prefetched. -> **Note**: There is [experimental support](/docs/app/api-reference/next-config-js/staleTimes) for configuring these values as of This configuration option is available as of [v14.2.0](https://github.com/vercel/next.js/releases/tag/v14.2.0). +> **Note**: There is [experimental support](/docs/app/api-reference/next-config-js/staleTimes) for configuring these values, available as of [v14.2.0](https://github.com/vercel/next.js/releases/tag/v14.2.0). ### Invalidation diff --git a/docs/02-app/01-building-your-application/09-authentication/index.mdx b/docs/02-app/01-building-your-application/09-authentication/index.mdx index 36934af91ae42..14289f4bcf91a 100644 --- a/docs/02-app/01-building-your-application/09-authentication/index.mdx +++ b/docs/02-app/01-building-your-application/09-authentication/index.mdx @@ -29,7 +29,7 @@ The examples on this page walk through basic username and password auth for educ ### Sign-up and login functionality -You can use the [``](https://react.dev/reference/react-dom/components/form) element with React's [Server Actions](/docs/app/building-your-application/rendering/server-components), [`useFormStatus()`](https://react.dev/reference/react-dom/hooks/useFormStatus), and [`useFormState()`](https://react.dev/reference/react-dom/hooks/useFormState) to capture user credentials, validate form fields, and call your Authentication Provider's API or database. +You can use the [``](https://react.dev/reference/react-dom/components/form) element with React's [Server Actions](/docs/app/building-your-application/rendering/server-components), [`useFormStatus()`](https://react.dev/reference/react-dom/hooks/useFormStatus), and [`useActionState()`](https://react.dev/reference/react/useActionState) to capture user credentials, validate form fields, and call your Authentication Provider's API or database. Since Server Actions always execute on the server, they provide a secure environment for handling authentication logic. @@ -200,16 +200,16 @@ export async function signup(state, formData) { } ``` -Back in your ``, you can use React's `useFormState()` hook to display validation errors to the user: +Back in your ``, you can use React's `useActionState()` hook to display validation errors to the user: ```tsx filename="app/ui/signup-form.tsx" switcher highlight={7,15,21,27-36} 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { signup } from '@/app/actions/auth' export function SignupForm() { - const [state, action] = useFormState(signup, undefined) + const [state, action] = useActionState(signup, undefined) return ( @@ -248,11 +248,11 @@ export function SignupForm() { ```jsx filename="app/ui/signup-form.js" switcher highlight={7,15,21,27-36} 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { signup } from '@/app/actions/auth' export function SignupForm() { - const [state, action] = useFormState(signup, undefined) + const [state, action] = useActionState(signup, undefined) return ( @@ -293,7 +293,8 @@ You can also use the `useFormStatus()` hook to handle the pending state on form ```tsx filename="app/ui/signup-form.tsx" highlight={6} switcher 'use client' -import { useFormStatus, useFormState } from 'react-dom' +import { useActionState } from 'react' +import { useFormStatus } from 'react-dom' export function SignupButton() { const { pending } = useFormStatus() @@ -309,7 +310,8 @@ export function SignupButton() { ```jsx filename="app/ui/signup-form.js" highlight={6} switcher 'use client' -import { useFormStatus, useFormState } from 'react-dom' +import { useActionState } from 'react' +import { useFormStatus } from 'react-dom' export function SignupButton() { const { pending } = useFormStatus() diff --git a/docs/02-app/02-api-reference/05-next-config-js/serverComponentsExternalPackages.mdx b/docs/02-app/02-api-reference/05-next-config-js/serverExternalPackages.mdx similarity index 93% rename from docs/02-app/02-api-reference/05-next-config-js/serverComponentsExternalPackages.mdx rename to docs/02-app/02-api-reference/05-next-config-js/serverExternalPackages.mdx index 69526e96f1edf..620fdf3a4b1f0 100644 --- a/docs/02-app/02-api-reference/05-next-config-js/serverComponentsExternalPackages.mdx +++ b/docs/02-app/02-api-reference/05-next-config-js/serverExternalPackages.mdx @@ -1,5 +1,5 @@ --- -title: serverComponentsExternalPackages +title: serverExternalPackages description: Opt-out specific dependencies from the Server Components bundling and use native Node.js `require`. --- @@ -10,9 +10,7 @@ If a dependency is using Node.js specific features, you can choose to opt-out sp ```js filename="next.config.js" /** @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - serverComponentsExternalPackages: ['@acme/ui'], - }, + serverExternalPackages: ['@acme/ui'], } module.exports = nextConfig diff --git a/docs/03-pages/02-api-reference/03-next-config-js/bundlePagesRouterDependencies.mdx b/docs/03-pages/02-api-reference/03-next-config-js/bundlePagesRouterDependencies.mdx new file mode 100644 index 0000000000000..9643981dba694 --- /dev/null +++ b/docs/03-pages/02-api-reference/03-next-config-js/bundlePagesRouterDependencies.mdx @@ -0,0 +1,17 @@ +--- +title: bundlePagesRouterDependencies +description: Enable automatic dependency bundling for Pages Router +--- + +Enable automatic server-side dependency bundling for Pages Router applications. Matches the automatic dependency bundling in App Router. + +```js filename="next.config.js" +/** @type {import('next').NextConfig} */ +const nextConfig = { + bundlePagesRouterDependencies: true, +} + +module.exports = nextConfig +``` + +Explicitly opt-out certain packages from being bundled using the [`serverExternalPackages`](/docs/pages/api-reference/next-config-js/serverExternalPackages) option. diff --git a/docs/03-pages/02-api-reference/03-next-config-js/serverExternalPackages.mdx b/docs/03-pages/02-api-reference/03-next-config-js/serverExternalPackages.mdx new file mode 100644 index 0000000000000..b83d8fc1daeee --- /dev/null +++ b/docs/03-pages/02-api-reference/03-next-config-js/serverExternalPackages.mdx @@ -0,0 +1,77 @@ +--- +title: serverExternalPackages +description: Opt-out specific dependencies from the dependency bundling enabled by `bundlePagesRouterDependencies`. +--- + +Opt-out specific dependencies from being included in the automatic bundling of the [`bundlePagesRouterDependencies`](/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies) option. + +These pages will then use native Node.js `require` to resolve the dependency. + +```js filename="next.config.js" +/** @type {import('next').NextConfig} */ +const nextConfig = { + serverExternalPackages: ['@acme/ui'], +} + +module.exports = nextConfig +``` + +Next.js includes a [short list of popular packages](https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/server-external-packages.json) that currently are working on compatibility and automatically opt-ed out: + +- `@appsignal/nodejs` +- `@aws-sdk/client-s3` +- `@aws-sdk/s3-presigned-post` +- `@blockfrost/blockfrost-js` +- `@highlight-run/node` +- `@jpg-store/lucid-cardano` +- `@libsql/client` +- `@mikro-orm/core` +- `@mikro-orm/knex` +- `@node-rs/argon2` +- `@node-rs/bcrypt` +- `@prisma/client` +- `@react-pdf/renderer` +- `@sentry/profiling-node` +- `@swc/core` +- `argon2` +- `autoprefixer` +- `aws-crt` +- `bcrypt` +- `better-sqlite3` +- `canvas` +- `cpu-features` +- `cypress` +- `eslint` +- `express` +- `firebase-admin` +- `isolated-vm` +- `jest` +- `jsdom` +- `libsql` +- `mdx-bundler` +- `mongodb` +- `mongoose` +- `next-mdx-remote` +- `next-seo` +- `node-pty` +- `node-web-audio-api` +- `oslo` +- `pg` +- `playwright` +- `postcss` +- `prettier` +- `prisma` +- `puppeteer-core` +- `puppeteer` +- `rimraf` +- `sharp` +- `shiki` +- `sqlite3` +- `tailwindcss` +- `ts-node` +- `typescript` +- `vscode-oniguruma` +- `undici` +- `webpack` +- `websocket` +- `zeromq` diff --git a/examples/next-forms/app/add-form.tsx b/examples/next-forms/app/add-form.tsx index 1cadaee2b4277..4f5bb968e8be7 100644 --- a/examples/next-forms/app/add-form.tsx +++ b/examples/next-forms/app/add-form.tsx @@ -1,6 +1,7 @@ "use client"; -import { useFormState, useFormStatus } from "react-dom"; +import { useActionState } from "react"; +import { useFormStatus } from "react-dom"; import { createTodo } from "@/app/actions"; const initialState = { @@ -18,7 +19,7 @@ function SubmitButton() { } export function AddForm() { - const [state, formAction] = useFormState(createTodo, initialState); + const [state, formAction] = useActionState(createTodo, initialState); return ( diff --git a/examples/next-forms/app/delete-form.tsx b/examples/next-forms/app/delete-form.tsx index acc633e972f61..b15eb1940b1df 100644 --- a/examples/next-forms/app/delete-form.tsx +++ b/examples/next-forms/app/delete-form.tsx @@ -1,6 +1,7 @@ "use client"; -import { useFormState, useFormStatus } from "react-dom"; +import { useActionState } from "react"; +import { useFormStatus } from "react-dom"; import { deleteTodo } from "@/app/actions"; const initialState = { @@ -18,7 +19,7 @@ function DeleteButton() { } export function DeleteForm({ id, todo }: { id: number; todo: string }) { - const [state, formAction] = useFormState(deleteTodo, initialState); + const [state, formAction] = useActionState(deleteTodo, initialState); return ( diff --git a/examples/with-fauna/components/EntryForm.tsx b/examples/with-fauna/components/EntryForm.tsx index eca9d68d668db..224f14d862599 100644 --- a/examples/with-fauna/components/EntryForm.tsx +++ b/examples/with-fauna/components/EntryForm.tsx @@ -3,7 +3,9 @@ import cn from "classnames"; import { createEntryAction } from "@/actions/entry"; // @ts-ignore -import { useFormState, useFormStatus } from "react-dom"; +import { useActionState } from "react"; +// @ts-ignore +import { useFormStatus } from "react-dom"; import LoadingSpinner from "@/components/LoadingSpinner"; import SuccessMessage from "@/components/SuccessMessage"; import ErrorMessage from "@/components/ErrorMessage"; @@ -20,7 +22,7 @@ const initialState = { }; export default function EntryForm() { - const [state, formAction] = useFormState(createEntryAction, initialState); + const [state, formAction] = useActionState(createEntryAction, initialState); const { pending } = useFormStatus(); return ( diff --git a/lerna.json b/lerna.json index 4b9b9692d5a19..bdbd24cc54b1b 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "14.3.0-canary.44" + "version": "14.3.0-canary.45" } diff --git a/package.json b/package.json index 9047f3e1a877b..918d48d7df562 100644 --- a/package.json +++ b/package.json @@ -196,18 +196,18 @@ "pretty-bytes": "5.3.0", "pretty-ms": "7.0.0", "random-seed": "0.3.0", - "react": "18.2.0", + "react": "19.0.0-beta-4508873393-20240430", "react-17": "npm:react@17.0.2", - "react-builtin": "npm:react@18.3.0-canary-c3048aab4-20240326", - "react-dom": "18.2.0", + "react-builtin": "npm:react@19.0.0-beta-4508873393-20240430", + "react-dom": "19.0.0-beta-4508873393-20240430", "react-dom-17": "npm:react-dom@17.0.2", - "react-dom-builtin": "npm:react-dom@18.3.0-canary-c3048aab4-20240326", - "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-c3048aab4-20240326", - "react-experimental-builtin": "npm:react@0.0.0-experimental-c3048aab4-20240326", - "react-server-dom-turbopack": "18.3.0-canary-c3048aab4-20240326", - "react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-c3048aab4-20240326", - "react-server-dom-webpack": "18.3.0-canary-c3048aab4-20240326", - "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-c3048aab4-20240326", + "react-dom-builtin": "npm:react-dom@19.0.0-beta-4508873393-20240430", + "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-4508873393-20240430", + "react-experimental-builtin": "npm:react@0.0.0-experimental-4508873393-20240430", + "react-server-dom-turbopack": "19.0.0-beta-4508873393-20240430", + "react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-4508873393-20240430", + "react-server-dom-webpack": "19.0.0-beta-4508873393-20240430", + "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-4508873393-20240430", "react-ssr-prepass": "1.0.8", "react-virtualized": "9.22.3", "relay-compiler": "13.0.2", @@ -217,8 +217,8 @@ "resolve-from": "5.0.0", "sass": "1.54.0", "satori": "0.10.9", - "scheduler-builtin": "npm:scheduler@0.24.0-canary-c3048aab4-20240326", - "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-c3048aab4-20240326", + "scheduler-builtin": "npm:scheduler@0.25.0-beta-4508873393-20240430", + "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-4508873393-20240430", "seedrandom": "3.0.5", "selenium-webdriver": "4.0.0-beta.4", "semver": "7.3.7", @@ -252,7 +252,11 @@ "@babel/types": "7.22.5", "@babel/traverse": "7.22.5", "@types/react": "18.2.74", - "@types/react-dom": "18.2.23" + "@types/react-dom": "18.2.23", + "react": "19.0.0-beta-4508873393-20240430", + "react-dom": "19.0.0-beta-4508873393-20240430", + "react-is": "19.0.0-beta-4508873393-20240430", + "scheduler": "0.25.0-beta-94eed63c49-20240425" }, "engines": { "node": ">=18.17.0", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index cb12d033d5024..247e6e0d3eb32 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "keywords": [ "react", "next", diff --git a/packages/create-next-app/templates/index.ts b/packages/create-next-app/templates/index.ts index 10558a7f02f89..f3d9dc21bf000 100644 --- a/packages/create-next-app/templates/index.ts +++ b/packages/create-next-app/templates/index.ts @@ -186,8 +186,8 @@ export const installTemplate = async ({ * Default dependencies. */ dependencies: { - react: "^18", - "react-dom": "^18", + react: "19.0.0-beta-4508873393-20240430", + "react-dom": "19.0.0-beta-4508873393-20240430", next: version, }, devDependencies: {}, diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 763fee7a7e876..37158058aacb5 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "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": "14.3.0-canary.44", + "@next/eslint-plugin-next": "14.3.0-canary.45", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.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 3fe27040f354a..ef3491b604207 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": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 99783b867b6a8..603f8493564a0 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "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 f4d77ae0242a7..c42e0da34f919 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index f36d0955c17dd..6474cb1ed584f 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 900b8b4ee1491..f16a0a40fefee 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 9408f03d2e398..e36af8603363b 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 9970a1262dcbc..c35349e5b553f 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "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 e19f426c22978..94534df27b491 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "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 49f4905d0de4f..8fe2b40a9e88f 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/crates/next-core/src/next_config.rs b/packages/next-swc/crates/next-core/src/next_config.rs index 8b749fce85146..d282c705355b4 100644 --- a/packages/next-swc/crates/next-core/src/next_config.rs +++ b/packages/next-swc/crates/next-core/src/next_config.rs @@ -120,6 +120,9 @@ pub struct NextConfig { typescript: TypeScriptConfig, use_file_system_public_routes: bool, webpack: Option, + /// A list of packages that should be treated as external in the RSC server + /// build. @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/server_external_packages) + pub server_external_packages: Option>, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)] @@ -460,9 +463,6 @@ pub struct ExperimentalConfig { /// For use with `@next/mdx`. Compile MDX files using the new Rust compiler. /// @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/mdxRs) mdx_rs: Option, - /// A list of packages that should be treated as external in the RSC server - /// build. @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/server_components_external_packages) - pub server_components_external_packages: Option>, pub strict_next_head: Option, pub swc_plugins: Option>, pub turbo: Option, @@ -737,11 +737,10 @@ impl NextConfig { } #[turbo_tasks::function] - pub async fn server_component_externals(self: Vc) -> Result>> { + pub async fn server_external_packages(self: Vc) -> Result>> { Ok(Vc::cell( self.await? - .experimental - .server_components_external_packages + .server_external_packages .as_ref() .cloned() .unwrap_or_default(), diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index e80def16aed70..26c1e67c70117 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -728,16 +728,21 @@ async fn rsc_aliases( } } - if runtime == NextRuntime::Edge { - if ty.supports_react_server() { - alias["react"] = format!("next/dist/compiled/react{react_channel}/react.react-server"); - alias["react-dom"] = - format!("next/dist/compiled/react-dom{react_channel}/react-dom.react-server"); - } else { - // x-ref: https://github.com/facebook/react/pull/25436 - alias["react-dom"] = - format!("next/dist/compiled/react-dom{react_channel}/server-rendering-stub"); - } + if runtime == NextRuntime::Edge && ty.supports_react_server() { + alias.extend(indexmap! { + "react" => format!("next/dist/compiled/react{react_channel}/react.react-server"), + "next/dist/compiled/react" => format!("next/dist/compiled/react{react_channel}/react.react-server"), + "next/dist/compiled/react-experimental" => format!("next/dist/compiled/react-experimental/react.react-server"), + "react/jsx-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-runtime.react-server"), + "next/dist/compiled/react/jsx-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-runtime.react-server"), + "next/dist/compiled/react-experimental/jsx-runtime" => format!("next/dist/compiled/react-experimental/jsx-runtime.react-server"), + "react/jsx-dev-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-dev-runtime.react-server"), + "next/dist/compiled/react/jsx-dev-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-dev-runtime.react-server"), + "next/dist/compiled/react-experimental/jsx-dev-runtime" => format!("next/dist/compiled/react-experimental/jsx-dev-runtime.react-server"), + "react-dom" => format!("next/dist/compiled/react-dom{react_channel}/react-dom.react-server"), + "next/dist/compiled/react-dom" => format!("next/dist/compiled/react-dom{react_channel}/react-dom.react-server"), + "next/dist/compiled/react-dom-experimental" => format!("next/dist/compiled/react-dom-experimental/react-dom.react-server"), + }) } insert_exact_alias_map(import_map, project_path, alias); diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index cd15190ba7d61..aaeefdf883b8a 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -140,12 +140,12 @@ pub async fn get_server_resolve_options_context( // Add the config's own list of external packages. external_packages.extend( - (*next_config.server_component_externals().await?) + (*next_config.server_external_packages().await?) .iter() .cloned(), ); - let server_component_externals_plugin = ExternalCjsModulesResolvePlugin::new( + let server_external_packages_plugin = ExternalCjsModulesResolvePlugin::new( project_path, project_path.root(), ExternalPredicate::Only(Vc::cell(external_packages)).cell(), @@ -196,7 +196,7 @@ pub async fn get_server_resolve_options_context( Vc::upcast(module_feature_report_resolve_plugin), Vc::upcast(unsupported_modules_resolve_plugin), Vc::upcast(next_node_shared_runtime_plugin), - Vc::upcast(server_component_externals_plugin), + Vc::upcast(server_external_packages_plugin), Vc::upcast(next_external_plugin), ] } diff --git a/packages/next-swc/crates/next-core/src/next_server/resolve.rs b/packages/next-swc/crates/next-core/src/next_server/resolve.rs index fad175f7fa0b2..a3026f403df18 100644 --- a/packages/next-swc/crates/next-core/src/next_server/resolve.rs +++ b/packages/next-swc/crates/next-core/src/next_server/resolve.rs @@ -233,9 +233,8 @@ impl ResolvePlugin for ExternalCjsModulesResolvePlugin { "The request could not be resolved by Node.js from the importing module. The \ way Node.js resolves modules is slightly different from the way Next.js \ resolves modules. Next.js was able to resolve it, while Node.js would not be \ - able to.\nTry to remove this package from \ - serverComponentsExternalPackages.\nOr update the import side to use a \ - compatible request that can be resolved by Node.js.", + able to.\nTry to remove this package from serverExternalPackages.\nOr update \ + the import side to use a compatible request that can be resolved by Node.js.", ); }; break result_from_original_location; @@ -486,7 +485,7 @@ impl Issue for UnableToExternalize { StyledString::Text("Package ".to_string()), StyledString::Code(package), StyledString::Text(" (".to_string()), - StyledString::Code("serverComponentsExternalPackages".to_string()), + StyledString::Code("serverExternalPackages".to_string()), StyledString::Text(" or default list) can't be external".to_string()), ]) .cell()) @@ -510,7 +509,7 @@ impl Issue for UnableToExternalize { StyledString::Text("The request ".to_string()), StyledString::Code(self.request.to_string()), StyledString::Text(" matches ".to_string()), - StyledString::Code("serverComponentsExternalPackages".to_string()), + StyledString::Code("serverExternalPackages".to_string()), StyledString::Text( " (or the default list), but it can't be external:".to_string(), ), diff --git a/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs b/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs index 11daf1179bca2..40eabd224da83 100644 --- a/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs +++ b/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs @@ -506,12 +506,12 @@ impl ReactServerComponentValidator { "useSyncExternalStore", "useTransition", "useOptimistic", + "useActionState", ], ), ( "react-dom", vec![ - "findDOMNode", "flushSync", "unstable_batchedUpdates", "useFormStatus", diff --git a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js index 3b7a161c4b373..a003a40719177 100644 --- a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js +++ b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js @@ -1,4 +1,6 @@ -import { findDOMNode, flushSync, unstable_batchedUpdates } from 'react-dom' +import { flushSync, unstable_batchedUpdates } from 'react-dom' + +import { useActionState } from 'react' import { useFormStatus, useFormState } from 'react-dom' diff --git a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js index 742749a85ed92..2637549fadc85 100644 --- a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js +++ b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js @@ -1,4 +1,5 @@ -import { findDOMNode, flushSync, unstable_batchedUpdates } from 'react-dom'; +import { flushSync, unstable_batchedUpdates } from 'react-dom'; +import { useActionState } from 'react' import { useFormStatus, useFormState } from 'react-dom'; export default function() { return null; diff --git a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.stderr b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.stderr index 64c0485b05f86..4cd910c9da2ee 100644 --- a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.stderr +++ b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.stderr @@ -1,39 +1,40 @@ - x You're importing a component that needs findDOMNode. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. + x You're importing a component that needs flushSync. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. | Learn more: https://nextjs.org/docs/getting-started/react-essentials | | ,-[input.js:1:1] - 1 | import { findDOMNode, flushSync, unstable_batchedUpdates } from 'react-dom' - : ^^^^^^^^^^^ + 1 | import { flushSync, unstable_batchedUpdates } from 'react-dom' + : ^^^^^^^^^ `---- - x You're importing a component that needs flushSync. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. + x You're importing a component that needs unstable_batchedUpdates. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by + | default. | Learn more: https://nextjs.org/docs/getting-started/react-essentials | | ,-[input.js:1:1] - 1 | import { findDOMNode, flushSync, unstable_batchedUpdates } from 'react-dom' - : ^^^^^^^^^ + 1 | import { flushSync, unstable_batchedUpdates } from 'react-dom' + : ^^^^^^^^^^^^^^^^^^^^^^^ `---- - x You're importing a component that needs unstable_batchedUpdates. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by - | default. + x You're importing a component that needs useActionState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. | Learn more: https://nextjs.org/docs/getting-started/react-essentials | | - ,-[input.js:1:1] - 1 | import { findDOMNode, flushSync, unstable_batchedUpdates } from 'react-dom' - : ^^^^^^^^^^^^^^^^^^^^^^^ + ,-[input.js:2:1] + 2 | + 3 | import { useActionState } from 'react' + : ^^^^^^^^^^^^^^ `---- x You're importing a component that needs useFormStatus. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. | Learn more: https://nextjs.org/docs/getting-started/react-essentials | | - ,-[input.js:2:1] - 2 | - 3 | import { useFormStatus, useFormState } from 'react-dom' + ,-[input.js:4:1] + 4 | + 5 | import { useFormStatus, useFormState } from 'react-dom' : ^^^^^^^^^^^^^ `---- @@ -41,8 +42,8 @@ | Learn more: https://nextjs.org/docs/getting-started/react-essentials | | - ,-[input.js:2:1] - 2 | - 3 | import { useFormStatus, useFormState } from 'react-dom' + ,-[input.js:4:1] + 4 | + 5 | import { useFormStatus, useFormState } from 'react-dom' : ^^^^^^^^^^^^ `---- diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 85c5a18c0e270..5e2fa25118300 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index 82ebd8605dc6f..1c979f9ba35ac 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "14.3.0-canary.44", + "version": "14.3.0-canary.45", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -93,7 +93,7 @@ ] }, "dependencies": { - "@next/env": "14.3.0-canary.44", + "@next/env": "14.3.0-canary.45", "@swc/helpers": "0.5.11", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -104,8 +104,8 @@ "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "19.0.0-beta-4508873393-20240430", + "react-dom": "19.0.0-beta-4508873393-20240430", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -153,10 +153,10 @@ "@jest/types": "29.5.0", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/polyfill-module": "14.3.0-canary.44", - "@next/polyfill-nomodule": "14.3.0-canary.44", - "@next/react-refresh-utils": "14.3.0-canary.44", - "@next/swc": "14.3.0-canary.44", + "@next/polyfill-module": "14.3.0-canary.45", + "@next/polyfill-nomodule": "14.3.0-canary.45", + "@next/react-refresh-utils": "14.3.0-canary.45", + "@next/swc": "14.3.0-canary.45", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.41.2", "@taskr/clear": "1.1.0", @@ -282,7 +282,7 @@ "punycode": "2.1.1", "querystring-es3": "0.2.1", "raw-body": "2.4.1", - "react-is": "18.2.0", + "react-is": "19.0.0-canary-94eed63c49-20240425", "react-refresh": "0.12.0", "regenerator-runtime": "0.13.4", "sass-loader": "12.4.0", diff --git a/packages/next/src/build/create-compiler-aliases.ts b/packages/next/src/build/create-compiler-aliases.ts index 58730bb56479e..e3eacf57740cd 100644 --- a/packages/next/src/build/create-compiler-aliases.ts +++ b/packages/next/src/build/create-compiler-aliases.ts @@ -290,23 +290,26 @@ export function createRSCAliases( if (isEdgeServer) { if (layer === WEBPACK_LAYERS.reactServerComponents) { - alias[ - 'react$' - ] = `next/dist/compiled/react${bundledReactChannel}/react.react-server` - alias[ - 'react-dom$' - ] = `next/dist/compiled/react-dom${bundledReactChannel}/react-dom.react-server` - } else { - // x-ref: https://github.com/facebook/react/pull/25436 - alias[ - 'react-dom$' - ] = `next/dist/compiled/react-dom${bundledReactChannel}/server-rendering-stub` + alias = Object.assign(alias, { + react$: `next/dist/compiled/react${bundledReactChannel}/react.react-server`, + 'next/dist/compiled/react$': `next/dist/compiled/react${bundledReactChannel}/react.react-server`, + 'next/dist/compiled/react-experimental$': `next/dist/compiled/react-experimental/react.react-server`, + 'react/jsx-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-runtime.react-server`, + 'next/dist/compiled/react/jsx-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-runtime.react-server`, + 'next/dist/compiled/react-experimental/jsx-runtime$': `next/dist/compiled/react-experimental/jsx-runtime.react-server`, + 'react/jsx-dev-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-dev-runtime.react-server`, + 'next/dist/compiled/react/jsx-dev-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-dev-runtime.react-server`, + 'next/dist/compiled/react-experimental/jsx-dev-runtime$': `next/dist/compiled/react-experimental/jsx-dev-runtime.react-server`, + 'react-dom$': `next/dist/compiled/react-dom${bundledReactChannel}/react-dom.react-server`, + 'next/dist/compiled/react-dom$': `next/dist/compiled/react-dom${bundledReactChannel}/react-dom.react-server`, + 'next/dist/compiled/react-dom-experimental$': `next/dist/compiled/react-dom-experimental/react-dom.react-server`, + }) } } if (reactProductionProfiling) { alias[ - 'react-dom$' + 'react-dom/client$' ] = `next/dist/compiled/react-dom${bundledReactChannel}/profiling` } @@ -373,6 +376,6 @@ function getBarrelOptimizationAliases(packages: string[]): CompilerAliases { } function getReactProfilingInProduction(): CompilerAliases { return { - 'react-dom$': 'react-dom/profiling', + 'react-dom/client$': 'react-dom/profiling', } } diff --git a/packages/next/src/build/handle-externals.ts b/packages/next/src/build/handle-externals.ts index b77d1f848f5dc..bb26df1b8a4e4 100644 --- a/packages/next/src/build/handle-externals.ts +++ b/packages/next/src/build/handle-externals.ts @@ -1,5 +1,6 @@ -import { WEBPACK_LAYERS } from '../lib/constants' import type { WebpackLayerName } from '../lib/constants' +import type { NextConfigComplete } from '../server/config-shared' +import type { ResolveOptions } from 'webpack' import { defaultOverrides } from '../server/require-hook' import { BARREL_OPTIMIZATION_PREFIX } from '../shared/lib/constants' import path from '../shared/lib/isomorphic/path' @@ -10,7 +11,6 @@ import { NODE_RESOLVE_OPTIONS, } from './webpack-config' import { isWebpackAppLayer, isWebpackServerOnlyLayer } from './utils' -import type { NextConfigComplete } from '../server/config-shared' import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep' const reactPackagesRegex = /^(react|react-dom|react-server-dom-webpack)($|\/)/ @@ -25,15 +25,6 @@ const externalPattern = new RegExp( const nodeModulesRegex = /node_modules[/\\].*\.[mc]?js$/ -function containsImportInPackages( - request: string, - packages: string[] -): boolean { - return packages.some( - (pkg) => request === pkg || request.startsWith(pkg + '/') - ) -} - export function isResourceInPackages( resource: string, packageNames?: string[], @@ -57,9 +48,9 @@ export async function resolveExternal( context: string, request: string, isEsmRequested: boolean, - optOutBundlingPackages: string[], + _optOutBundlingPackages: string[], getResolve: ( - options: any + options: ResolveOptions ) => ( resolveContext: string, resolveRequest: string @@ -78,19 +69,12 @@ export async function resolveExternal( let isEsm: boolean = false const preferEsmOptions = - esmExternals && - isEsmRequested && - // For package that marked as externals that should be not bundled, - // we don't resolve them as ESM since it could be resolved as async module, - // such as `import(external package)` in the bundle, valued as a `Promise`. - !containsImportInPackages(request, optOutBundlingPackages) - ? [true, false] - : [false] + esmExternals && isEsmRequested ? [true, false] : [false] for (const preferEsm of preferEsmOptions) { - const resolve = getResolve( - preferEsm ? esmResolveOptions : nodeResolveOptions - ) + const resolveOptions = preferEsm ? esmResolveOptions : nodeResolveOptions + + const resolve = getResolve(resolveOptions) // Resolve the import with the webpack provided context, this // ensures we're resolving the correct version when multiple @@ -273,23 +257,6 @@ export function makeExternalHandler({ return resolveNextExternal(request) } - // Early return if the request needs to be bundled, such as in the client layer. - // Treat react packages and next internals as external for SSR layer, - // also map react to builtin ones with require-hook. - // Otherwise keep continue the process to resolve the externals. - if (layer === WEBPACK_LAYERS.serverSideRendering) { - const isRelative = request.startsWith('.') - const fullRequest = isRelative - ? normalizePathSep(path.join(context, request)) - : request - - // Check if it's opt out bundling package first - if (containsImportInPackages(fullRequest, optOutBundlingPackages)) { - return fullRequest - } - return resolveNextExternal(fullRequest) - } - // TODO-APP: Let's avoid this resolve call as much as possible, and eventually get rid of it. const resolveResult = await resolveExternal( dir, @@ -320,6 +287,13 @@ export function makeExternalHandler({ return } + const isOptOutBundling = optOutBundlingPackageRegex.test(res) + // Apply bundling rules to all app layers. + // Since handleExternals only handle the server layers, we don't need to exclude client here + if (!isOptOutBundling && isAppLayer) { + return + } + // ESM externals can only be imported (and not required). // Make an exception in loose mode. if (!isEsmRequested && isEsm && !looseEsmExternals && !isLocal) { @@ -370,13 +344,11 @@ export function makeExternalHandler({ const resolvedBundlingOptOutRes = resolveBundlingOptOutPackages({ resolvedRes: res, - optOutBundlingPackageRegex, config, resolvedExternalPackageDirs, - isEsm, isAppLayer, - layer, externalType, + isOptOutBundling, request, }) if (resolvedBundlingOptOutRes) { @@ -390,41 +362,32 @@ export function makeExternalHandler({ function resolveBundlingOptOutPackages({ resolvedRes, - optOutBundlingPackageRegex, config, resolvedExternalPackageDirs, - isEsm, isAppLayer, - layer, externalType, + isOptOutBundling, request, }: { resolvedRes: string - optOutBundlingPackageRegex: RegExp config: NextConfigComplete resolvedExternalPackageDirs: Map - isEsm: boolean isAppLayer: boolean - layer: WebpackLayerName | null externalType: string + isOptOutBundling: boolean request: string }) { - const shouldBeBundled = - isResourceInPackages( - resolvedRes, - config.transpilePackages, - resolvedExternalPackageDirs - ) || - (isEsm && isAppLayer) || - (!isAppLayer && config.experimental.bundlePagesExternals) - if (nodeModulesRegex.test(resolvedRes)) { - const isOptOutBundling = optOutBundlingPackageRegex.test(resolvedRes) - if (isWebpackServerOnlyLayer(layer)) { - if (isOptOutBundling) { - return `${externalType} ${request}` // Externalize if opted out - } - } else if (!shouldBeBundled || isOptOutBundling) { + const shouldBundlePages = + !isAppLayer && config.bundlePagesRouterDependencies && !isOptOutBundling + const shouldBeBundled = + shouldBundlePages || + isResourceInPackages( + resolvedRes, + config.transpilePackages, + resolvedExternalPackageDirs + ) + if (!shouldBeBundled) { return `${externalType} ${request}` // Externalize if not bundled or opted out } } diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 594d5972eec5a..483b3361eb488 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -775,18 +775,15 @@ export default async function getBaseWebpackConfig( const crossOrigin = config.crossOrigin - // The `serverComponentsExternalPackages` should not conflict with + // The `serverExternalPackages` should not conflict with // the `transpilePackages`. - if ( - config.experimental.serverComponentsExternalPackages && - finalTranspilePackages - ) { + if (config.serverExternalPackages && finalTranspilePackages) { const externalPackageConflicts = finalTranspilePackages.filter((pkg) => - config.experimental.serverComponentsExternalPackages?.includes(pkg) + config.serverExternalPackages?.includes(pkg) ) if (externalPackageConflicts.length > 0) { throw new Error( - `The packages specified in the 'transpilePackages' conflict with the 'serverComponentsExternalPackages': ${externalPackageConflicts.join( + `The packages specified in the 'transpilePackages' conflict with the 'serverExternalPackages': ${externalPackageConflicts.join( ', ' )}` ) @@ -795,7 +792,7 @@ export default async function getBaseWebpackConfig( // For original request, such as `package name` const optOutBundlingPackages = EXTERNAL_PACKAGES.concat( - ...(config.experimental.serverComponentsExternalPackages || []) + ...(config.serverExternalPackages || []) ).filter((pkg) => !finalTranspilePackages?.includes(pkg)) // For resolved request, such as `absolute path/package name/foo/bar.js` const optOutBundlingPackageRegex = new RegExp( diff --git a/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js b/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js index a5d6643a6c240..ce1271810aa34 100644 --- a/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js +++ b/packages/next/src/build/webpack/alias/react-dom-server-browser-experimental.js @@ -1,7 +1,7 @@ var l, s if (process.env.NODE_ENV === 'production') { - l = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.min.js') - s = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.min.js') + l = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.js') + s = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.js') } else { l = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js') s = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.browser.development.js') diff --git a/packages/next/src/build/webpack/alias/react-dom-server-browser.js b/packages/next/src/build/webpack/alias/react-dom-server-browser.js index f462b442ae41e..196b4b40f2a82 100644 --- a/packages/next/src/build/webpack/alias/react-dom-server-browser.js +++ b/packages/next/src/build/webpack/alias/react-dom-server-browser.js @@ -1,7 +1,7 @@ var l, s if (process.env.NODE_ENV === 'production') { - l = require('next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js') - s = require('next/dist/compiled/react-dom/cjs/react-dom-server.browser.production.min.js') + l = require('next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.js') + s = require('next/dist/compiled/react-dom/cjs/react-dom-server.browser.production.js') } else { l = require('next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js') s = require('next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js') diff --git a/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js b/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js index b14af3c288f1c..c73fadf1b4431 100644 --- a/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js +++ b/packages/next/src/build/webpack/alias/react-dom-server-edge-experimental.js @@ -7,7 +7,7 @@ function error() { var b if (process.env.NODE_ENV === 'production') { - b = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.min.js') + b = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.js') } else { b = require('next/dist/compiled/react-dom-experimental/cjs/react-dom-server.edge.development.js') } diff --git a/packages/next/src/build/webpack/alias/react-dom-server-edge.js b/packages/next/src/build/webpack/alias/react-dom-server-edge.js index f6b6056bc271e..9d2c93e96193e 100644 --- a/packages/next/src/build/webpack/alias/react-dom-server-edge.js +++ b/packages/next/src/build/webpack/alias/react-dom-server-edge.js @@ -7,7 +7,7 @@ function error() { var b if (process.env.NODE_ENV === 'production') { - b = require('next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js') + b = require('next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.js') } else { b = require('next/dist/compiled/react-dom/cjs/react-dom-server.edge.development.js') } diff --git a/packages/next/src/build/webpack/loaders/next-swc-loader.ts b/packages/next/src/build/webpack/loaders/next-swc-loader.ts index 898de1f4d54ff..0a83dcb34c88e 100644 --- a/packages/next/src/build/webpack/loaders/next-swc-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-swc-loader.ts @@ -117,7 +117,9 @@ async function loaderTransform( filename, isServer, isPageFile, - development: this.mode === 'development', + development: + this.mode === 'development' || + !!nextConfig.experimental?.allowDevelopmentBuild, hasReactRefresh, modularizeImports: nextConfig?.modularizeImports, optimizePackageImports: nextConfig?.experimental?.optimizePackageImports, diff --git a/packages/next/src/build/webpack/plugins/define-env-plugin.ts b/packages/next/src/build/webpack/plugins/define-env-plugin.ts index 862e5bc3ea916..3726900426cfe 100644 --- a/packages/next/src/build/webpack/plugins/define-env-plugin.ts +++ b/packages/next/src/build/webpack/plugins/define-env-plugin.ts @@ -159,7 +159,10 @@ export function getDefineEnv({ 'process.turbopack': isTurbopack, 'process.env.TURBOPACK': isTurbopack, // TODO: enforce `NODE_ENV` on `process.env`, and add a test: - 'process.env.NODE_ENV': dev ? 'development' : 'production', + 'process.env.NODE_ENV': + dev || config.experimental.allowDevelopmentBuild + ? 'development' + : 'production', 'process.env.NEXT_RUNTIME': isEdgeServer ? 'edge' : isNodeServer diff --git a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts index 101171976b011..8c45bf8a9933f 100644 --- a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts @@ -430,75 +430,6 @@ export class FlightClientEntryPlugin { ) } - compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, async () => { - const addedClientActionEntryList: Promise[] = [] - const actionMapsPerClientEntry: Record> = {} - - // We need to create extra action entries that are created from the - // client layer. - // Start from each entry's created SSR dependency from our previous step. - for (const [name, ssrEntryDependencies] of Object.entries( - createdSSRDependenciesForEntry - )) { - // Collect from all entries, e.g. layout.js, page.js, loading.js, ... - // add aggregate them. - const actionEntryImports = this.collectClientActionsFromDependencies({ - compilation, - dependencies: ssrEntryDependencies, - }) - - if (actionEntryImports.size > 0) { - if (!actionMapsPerClientEntry[name]) { - actionMapsPerClientEntry[name] = new Map() - } - actionMapsPerClientEntry[name] = new Map([ - ...actionMapsPerClientEntry[name], - ...actionEntryImports, - ]) - } - } - - for (const [name, actionEntryImports] of Object.entries( - actionMapsPerClientEntry - )) { - // If an action method is already created in the server layer, we don't - // need to create it again in the action layer. - // This is to avoid duplicate action instances and make sure the module - // state is shared. - let remainingClientImportedActions = false - const remainingActionEntryImports = new Map() - for (const [dep, actionNames] of actionEntryImports) { - const remainingActionNames = [] - for (const actionName of actionNames) { - const id = name + '@' + dep + '@' + actionName - if (!createdActions.has(id)) { - remainingActionNames.push(actionName) - } - } - if (remainingActionNames.length > 0) { - remainingActionEntryImports.set(dep, remainingActionNames) - remainingClientImportedActions = true - } - } - - if (remainingClientImportedActions) { - addedClientActionEntryList.push( - this.injectActionEntry({ - compiler, - compilation, - actions: remainingActionEntryImports, - entryName: name, - bundlePath: name, - fromClient: true, - }) - ) - } - } - - await Promise.all(addedClientActionEntryList) - return - }) - // Invalidate in development to trigger recompilation const invalidator = getInvalidator(compiler.outputPath) // Check if any of the entry injections need an invalidation @@ -521,6 +452,72 @@ export class FlightClientEntryPlugin { // Wait for action entries to be added. await Promise.all(addActionEntryList) + + const addedClientActionEntryList: Promise[] = [] + const actionMapsPerClientEntry: Record> = {} + + // We need to create extra action entries that are created from the + // client layer. + // Start from each entry's created SSR dependency from our previous step. + for (const [name, ssrEntryDependencies] of Object.entries( + createdSSRDependenciesForEntry + )) { + // Collect from all entries, e.g. layout.js, page.js, loading.js, ... + // add aggregate them. + const actionEntryImports = this.collectClientActionsFromDependencies({ + compilation, + dependencies: ssrEntryDependencies, + }) + + if (actionEntryImports.size > 0) { + if (!actionMapsPerClientEntry[name]) { + actionMapsPerClientEntry[name] = new Map() + } + actionMapsPerClientEntry[name] = new Map([ + ...actionMapsPerClientEntry[name], + ...actionEntryImports, + ]) + } + } + + for (const [name, actionEntryImports] of Object.entries( + actionMapsPerClientEntry + )) { + // If an action method is already created in the server layer, we don't + // need to create it again in the action layer. + // This is to avoid duplicate action instances and make sure the module + // state is shared. + let remainingClientImportedActions = false + const remainingActionEntryImports = new Map() + for (const [dep, actionNames] of actionEntryImports) { + const remainingActionNames = [] + for (const actionName of actionNames) { + const id = name + '@' + dep + '@' + actionName + if (!createdActions.has(id)) { + remainingActionNames.push(actionName) + } + } + if (remainingActionNames.length > 0) { + remainingActionEntryImports.set(dep, remainingActionNames) + remainingClientImportedActions = true + } + } + + if (remainingClientImportedActions) { + addedClientActionEntryList.push( + this.injectActionEntry({ + compiler, + compilation, + actions: remainingActionEntryImports, + entryName: name, + bundlePath: name, + fromClient: true, + }) + ) + } + } + + await Promise.all(addedClientActionEntryList) } collectClientActionsFromDependencies({ diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index 75867b17e5074..f59ee8507b412 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -7,7 +7,7 @@ import React, { use } from 'react' import { createFromReadableStream } from 'react-server-dom-webpack/client' import { HeadManagerContext } from '../shared/lib/head-manager-context.shared-runtime' -import onRecoverableError from './on-recoverable-error' +import { onRecoverableError } from './on-recoverable-error' import { callServer } from './app-call-server' import { isNextRouterError } from './components/is-next-router-error' import { @@ -165,7 +165,9 @@ export function hydrate() { const rootLayoutMissingTags = window.__next_root_layout_missing_tags const hasMissingTags = !!rootLayoutMissingTags?.length - const options = { onRecoverableError } satisfies ReactDOMClient.RootOptions + const options = { + onRecoverableError, + } satisfies ReactDOMClient.RootOptions const isError = document.documentElement.id === '__next_error__' || hasMissingTags diff --git a/packages/next/src/client/components/is-hydration-error.ts b/packages/next/src/client/components/is-hydration-error.ts index eaa9b0df90548..f66eda30984c8 100644 --- a/packages/next/src/client/components/is-hydration-error.ts +++ b/packages/next/src/client/components/is-hydration-error.ts @@ -3,6 +3,62 @@ import isError from '../../lib/is-error' const hydrationErrorRegex = /hydration failed|while hydrating|content does not match|did not match/i +const reactUnifiedMismatchWarning = `Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used` + +const reactHydrationErrorDocLink = 'https://react.dev/link/hydration-mismatch' + +export const getDefaultHydrationErrorMessage = () => { + return ( + reactUnifiedMismatchWarning + + '\nSee more info here: https://nextjs.org/docs/messages/react-hydration-error' + ) +} + export function isHydrationError(error: unknown): boolean { return isError(error) && hydrationErrorRegex.test(error.message) } + +export function isReactHydrationErrorStack(stack: string): boolean { + return stack.startsWith(reactUnifiedMismatchWarning) +} + +export function getHydrationErrorStackInfo(rawMessage: string): { + message: string | null + link?: string + stack?: string + diff?: string +} { + rawMessage = rawMessage.replace(/^Error: /, '') + if (!isReactHydrationErrorStack(rawMessage)) { + return { message: null } + } + rawMessage = rawMessage.slice(reactUnifiedMismatchWarning.length + 1).trim() + const [message, trailing] = rawMessage.split(`${reactHydrationErrorDocLink}`) + const trimmedMessage = message.trim() + // React built-in hydration diff starts with a newline, checking if length is > 1 + if (trailing && trailing.length > 1) { + const stacks: string[] = [] + const diffs: string[] = [] + trailing.split('\n').forEach((line) => { + if (line.trim() === '') return + if (line.trim().startsWith('at ')) { + stacks.push(line) + } else { + diffs.push(line) + } + }) + + return { + message: trimmedMessage, + link: reactHydrationErrorDocLink, + diff: diffs.join('\n'), + stack: stacks.join('\n'), + } + } else { + return { + message: trimmedMessage, + link: reactHydrationErrorDocLink, + stack: trailing, // without hydration diff + } + } +} diff --git a/packages/next/src/client/components/layout-router.tsx b/packages/next/src/client/components/layout-router.tsx index 1c3698c604967..f70b813e9f89b 100644 --- a/packages/next/src/client/components/layout-router.tsx +++ b/packages/next/src/client/components/layout-router.tsx @@ -86,13 +86,21 @@ function walkAddRefetch( return treeToRecreate } +const __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ( + ReactDOM as any +).__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE + // TODO-APP: Replace with new React API for finding dom nodes without a `ref` when available /** * Wraps ReactDOM.findDOMNode with additional logic to hide React Strict Mode warning */ function findDOMNode( - instance: Parameters[0] -): ReturnType { + instance: React.ReactInstance | null | undefined +): Element | Text | null { + // __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode is null during module init. + // We need to lazily reference it. + const internal_reactDOMfindDOMNode = + __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode // Tree-shake for server bundle if (typeof window === 'undefined') return null // Only apply strict mode warning when not in production @@ -105,12 +113,12 @@ function findDOMNode( originalConsoleError(...messages) } } - return ReactDOM.findDOMNode(instance) + return internal_reactDOMfindDOMNode(instance) } finally { console.error = originalConsoleError! } } - return ReactDOM.findDOMNode(instance) + return internal_reactDOMfindDOMNode(instance) } const rectProperties = [ diff --git a/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx index 895f4c2054b13..29090e8cbb1f2 100644 --- a/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx @@ -483,15 +483,17 @@ export default function HotReload({ | HydrationErrorState | undefined // Component stack is added to the error in use-error-handler in case there was a hydration errror - const componentStack = errorDetails?.componentStack + const componentStackTrace = + (error as any)._componentStack || errorDetails?.componentStack const warning = errorDetails?.warning dispatch({ type: ACTION_UNHANDLED_ERROR, reason: error, frames: parseStack(error.stack!), - componentStackFrames: componentStack - ? parseComponentStack(componentStack) - : undefined, + componentStackFrames: + typeof componentStackTrace === 'string' + ? parseComponentStack(componentStackTrace) + : undefined, warning, }) }, diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx index 674828bead911..9a9a9858300be 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx @@ -227,6 +227,7 @@ export function Errors({ ) const errorDetails: HydrationErrorState = (error as any).details || {} + const notes = errorDetails.notes || '' const [warningTemplate, serverContent, clientContent] = errorDetails.warning || [null, '', ''] @@ -238,6 +239,7 @@ export function Errors({ .replace('%s', '') // remove the %s for stack .replace(/%s$/, '') // If there's still a %s at the end, remove it .replace(/^Warning: /, '') + .replace(/^Error: /, '') : null return ( @@ -272,28 +274,36 @@ export function Errors({ id="nextjs__container_errors_desc" className="nextjs__container_errors_desc" > - {error.name}:{' '} - + {/* If there's hydration warning, skip displaying the error name */} + {hydrationWarning ? '' : error.name + ': '} +

- {hydrationWarning && ( + {notes ? ( <>

- {hydrationWarning} + {notes}

- {activeError.componentStackFrames?.length ? ( - - ) : null} - )} + ) : null} + + {hydrationWarning && + (activeError.componentStackFrames?.length || + !!errorDetails.reactOutputComponentDiff) ? ( + + ) : null} {isServerError ? (
diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx index 58f3156b22256..58b2e2d199665 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.tsx @@ -59,25 +59,84 @@ export function PseudoHtmlDiff({ firstContent, secondContent, hydrationMismatchType, + reactOutputComponentDiff, ...props }: { componentStackFrames: ComponentStackFrame[] firstContent: string secondContent: string + reactOutputComponentDiff: string | undefined hydrationMismatchType: 'tag' | 'text' | 'text-in-tag' } & React.HTMLAttributes) { const isHtmlTagsWarning = hydrationMismatchType === 'tag' + const isReactHydrationDiff = !!reactOutputComponentDiff + // For text mismatch, mismatched text will take 2 rows, so we display 4 rows of component stack const MAX_NON_COLLAPSED_FRAMES = isHtmlTagsWarning ? 6 : 4 - const shouldCollapse = componentStackFrames.length > MAX_NON_COLLAPSED_FRAMES - const [isHtmlCollapsed, toggleCollapseHtml] = useState(shouldCollapse) + const [isHtmlCollapsed, toggleCollapseHtml] = useState(true) const htmlComponents = useMemo(() => { + const componentStacks: React.ReactNode[] = [] + // React 19 unified mismatch + if (isReactHydrationDiff) { + let currentComponentIndex = componentStackFrames.length - 1 + const reactComponentDiffLines = reactOutputComponentDiff.split('\n') + const diffHtmlStack: React.ReactNode[] = [] + reactComponentDiffLines.forEach((line, index) => { + let trimmedLine = line.trim() + const isDiffLine = trimmedLine[0] === '+' || trimmedLine[0] === '-' + const spaces = ' '.repeat(componentStacks.length * 2) + + if (isDiffLine) { + const sign = trimmedLine[0] + trimmedLine = trimmedLine.slice(1).trim() // trim spaces after sign + diffHtmlStack.push( + + {sign} + {spaces} + {trimmedLine} + {'\n'} + + ) + } else if (currentComponentIndex >= 0) { + const isUserLandComponent = trimmedLine.startsWith( + '<' + componentStackFrames[currentComponentIndex].component + ) + // If it's matched userland component or it's ... we will keep the component stack in diff + if (isUserLandComponent || trimmedLine === '...') { + currentComponentIndex-- + componentStacks.push( + + {spaces} + {trimmedLine} + {'\n'} + + ) + } else if (!isHtmlCollapsed) { + componentStacks.push( + + {spaces} + {trimmedLine} + {'\n'} + + ) + } + } + }) + return componentStacks.concat(diffHtmlStack) + } + + const nestedHtmlStack: React.ReactNode[] = [] const tagNames = isHtmlTagsWarning ? // tags could have < or > in the name, so we always remove them to match [firstContent.replace(/<|>/g, ''), secondContent.replace(/<|>/g, '')] : [] - const nestedHtmlStack: React.ReactNode[] = [] + let lastText = '' const componentStack = componentStackFrames @@ -105,10 +164,8 @@ export function PseudoHtmlDiff({ componentStack.forEach((component, index, componentList) => { const spaces = ' '.repeat(nestedHtmlStack.length * 2) - // const prevComponent = componentList[index - 1] - // const nextComponent = componentList[index + 1] - // When component is the server or client tag name, highlight it + // When component is the server or client tag name, highlight it const isHighlightedTag = isHtmlTagsWarning ? index === matchedIndex[0] || index === matchedIndex[1] : tagNames.includes(component) @@ -181,7 +238,6 @@ export function PseudoHtmlDiff({ } } }) - // Hydration mismatch: text or text-tag if (!isHtmlTagsWarning) { const spaces = ' '.repeat(nestedHtmlStack.length * 2) @@ -190,22 +246,22 @@ export function PseudoHtmlDiff({ // hydration type is "text", represent [server content, client content] wrappedCodeLine = ( - + {spaces + `"${firstContent}"\n`} - + {spaces + `"${secondContent}"\n`} ) - } else { + } else if (hydrationMismatchType === 'text-in-tag') { // hydration type is "text-in-tag", represent [parent tag, mismatch content] wrappedCodeLine = ( {spaces + `<${secondContent}>\n`} - + {spaces + ` "${firstContent}"\n`} @@ -223,6 +279,8 @@ export function PseudoHtmlDiff({ isHtmlTagsWarning, hydrationMismatchType, MAX_NON_COLLAPSED_FRAMES, + isReactHydrationDiff, + reactOutputComponentDiff, ]) return ( diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx index 8e9ec07791633..f68ac1582b067 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/RuntimeError/index.tsx @@ -200,10 +200,10 @@ export const styles = css` border: none; padding: 0; } - [data-nextjs-container-errors-pseudo-html--diff-add] { + [data-nextjs-container-errors-pseudo-html--diff='add'] { color: var(--color-ansi-green); } - [data-nextjs-container-errors-pseudo-html--diff-remove] { + [data-nextjs-container-errors-pseudo-html--diff='remove'] { color: var(--color-ansi-red); } [data-nextjs-container-errors-pseudo-html--tag-error] { diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts index 731bba840c363..318514a181466 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/hydration-error-info.ts @@ -1,13 +1,37 @@ +import { getHydrationErrorStackInfo } from '../../../is-hydration-error' + export type HydrationErrorState = { - // [message, serverContent, clientContent] + // Hydration warning template format: warning?: [string, string, string] componentStack?: string serverContent?: string clientContent?: string + // React 19 hydration diff format: + notes?: string + reactOutputComponentDiff?: string } type NullableText = string | null | undefined +export const hydrationErrorState: HydrationErrorState = {} + +// https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js used as a reference +const htmlTagsWarnings = new Set([ + 'Warning: Cannot render a sync or defer '); /** - * This escaping function is designed to work with bootstrapScriptContent and importMap only. - * because we know we are escaping the entire script. We can avoid for instance + * This escaping function is designed to work with with inline scripts where the entire + * contents are escaped. Because we know we are escaping the entire script we can avoid for instance * escaping html comment string sequences that are valid javascript as well because * if there are no sebsequent '); * ensure that the script cannot be early terminated or never terminated state */ -function escapeBootstrapAndImportMapScriptContent(scriptText) { +function escapeEntireInlineScriptContent(scriptText) { { checkHtmlStringCoercion(scriptText); } @@ -2092,7 +2084,7 @@ function createRenderState$1(resumableState, nonce, externalRuntimeConfig, impor bootstrapModules = resumableState.bootstrapModules; if (bootstrapScriptContent !== undefined) { - bootstrapChunks.push(inlineScriptWithNonce, stringToChunk(escapeBootstrapAndImportMapScriptContent(bootstrapScriptContent)), endInlineScript); + bootstrapChunks.push(inlineScriptWithNonce, stringToChunk(escapeEntireInlineScriptContent(bootstrapScriptContent)), endInlineScript); } { @@ -2128,7 +2120,7 @@ function createRenderState$1(resumableState, nonce, externalRuntimeConfig, impor if (importMap !== undefined) { var map = importMap; importMapChunks.push(importMapScriptStart); - importMapChunks.push(stringToChunk(escapeBootstrapAndImportMapScriptContent(JSON.stringify(map)))); + importMapChunks.push(stringToChunk(escapeEntireInlineScriptContent(JSON.stringify(map)))); importMapChunks.push(importMapScriptEnd); } @@ -2817,6 +2809,18 @@ function pushAttribute(target, name, value) // not null or undefined return; } + case 'inert': + { + { + if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[name]) { + didWarnForNewBooleanPropsWithEmptyValue[name] = true; + + error('Received an empty string for a boolean attribute `%s`. ' + 'This will treat the attribute as if it were false. ' + 'Either pass `false` to silence this warning, or ' + 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.', name); + } + } + } + // Fallthrough for boolean props that don't have a warning for empty strings. + case 'allowFullScreen': case 'async': case 'autoPlay': @@ -2921,27 +2925,6 @@ function pushAttribute(target, name, value) // not null or undefined pushStringAttribute(target, 'xml:space', value); return; - case 'inert': - { - { - { - if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[name]) { - didWarnForNewBooleanPropsWithEmptyValue[name] = true; - - error('Received an empty string for a boolean attribute `%s`. ' + 'This will treat the attribute as if it were false. ' + 'Either pass `false` to silence this warning, or ' + 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.', name); - } - } // Boolean - - - if (value && typeof value !== 'function' && typeof value !== 'symbol') { - target.push(attributeSeparator, stringToChunk(name), attributeEmptyString); - } - - return; - } - } - // fallthrough for new boolean props without the flag on - default: if ( // shouldIgnoreAttribute // We have already filtered out null/undefined and reserved words. @@ -3148,7 +3131,7 @@ function flattenOptionChildren(children) { content += child; { - if (!didWarnInvalidOptionChildren && typeof child !== 'string' && typeof child !== 'number' && (typeof child !== 'bigint' || !enableBigIntSupport)) { + if (!didWarnInvalidOptionChildren && typeof child !== 'string' && typeof child !== 'number' && typeof child !== 'bigint') { didWarnInvalidOptionChildren = true; error('Cannot infer the option value of complex children. ' + 'Pass a `value` prop or use a plain string as children to