From 102039f37bc3c0f45ae7938692e8548203bbfd42 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Sun, 21 Sep 2025 09:53:15 +0200 Subject: [PATCH 1/7] Enable anonymous function naming in React Compiler --- package.json | 2 +- packages/next/package.json | 2 +- .../next/src/build/get-babel-loader-config.ts | 11 +- pnpm-lock.yaml | 43 ++-- .../app/function-naming/page.tsx | 23 ++ test/e2e/react-compiler/app/page.tsx | 38 +-- test/e2e/react-compiler/next.config.js | 1 + .../e2e/react-compiler/react-compiler.test.ts | 225 ++++++++++++------ 8 files changed, 224 insertions(+), 121 deletions(-) create mode 100644 test/e2e/react-compiler/app/function-naming/page.tsx diff --git a/package.json b/package.json index 2b47c8205776b6..50fd4b207ea295 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "abort-controller": "3.0.0", "alex": "9.1.0", "async-sema": "3.0.1", - "babel-plugin-react-compiler": "19.1.0-rc.2", + "babel-plugin-react-compiler": "0.0.0-experimental-3fde738-20250918", "browserslist": "4.25.1", "buffer": "5.6.0", "cheerio": "0.22.0", diff --git a/packages/next/package.json b/packages/next/package.json index e0b3e6affb4dff..1fc45d5a1529ad 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -234,7 +234,7 @@ "async-sema": "3.0.0", "axe-playwright": "2.0.3", "babel-loader": "10.0.0", - "babel-plugin-react-compiler": "19.1.0-rc.2", + "babel-plugin-react-compiler": "0.0.0-experimental-3fde738-20250918", "babel-plugin-transform-define": "2.0.0", "babel-plugin-transform-react-remove-prop-types": "0.4.24", "browserify-zlib": "0.2.0", diff --git a/packages/next/src/build/get-babel-loader-config.ts b/packages/next/src/build/get-babel-loader-config.ts index b92978278cc789..2535e974bb59f5 100644 --- a/packages/next/src/build/get-babel-loader-config.ts +++ b/packages/next/src/build/get-babel-loader-config.ts @@ -1,3 +1,4 @@ +import type { EnvironmentConfig } from 'babel-plugin-react-compiler' import path from 'path' import type { JSONValue, ReactCompilerOptions } from '../server/config-shared' import type { NextBabelLoaderOptions } from './babel/loader/types' @@ -23,9 +24,15 @@ const getReactCompilerPlugins = ( return undefined } - const defaultOptions: ReactCompilerOptions = isDev + const defaultOptions: ReactCompilerOptions & { + environment?: { + enableNameAnonymousFunctions?: EnvironmentConfig['enableNameAnonymousFunctions'] + } + } = isDev ? { - // TODO: enable `environment.enableNameAnonymousFunctions`Ï + environment: { + enableNameAnonymousFunctions: true, + }, } : {} const options: ReactCompilerOptions = diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89b2462d2c994d..713efaf73034b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -235,8 +235,8 @@ importers: specifier: 3.0.1 version: 3.0.1 babel-plugin-react-compiler: - specifier: 19.1.0-rc.2 - version: 19.1.0-rc.2 + specifier: 0.0.0-experimental-3fde738-20250918 + version: 0.0.0-experimental-3fde738-20250918 browserslist: specifier: 4.25.1 version: 4.25.1 @@ -620,16 +620,16 @@ importers: dependencies: fumadocs-core: specifier: 15.7.12 - version: 15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) + version: 15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) fumadocs-mdx: specifier: 11.10.0 - version: 11.10.0(fumadocs-core@15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922))(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react@19.2.0-canary-1eca9a27-20250922)(vite@6.2.5(@types/node@20.17.6(patch_hash=rvl3vkomen3tospgr67bzubfyu))(jiti@2.5.1)(sass@1.77.8)(tsx@4.19.2)) + version: 11.10.0(fumadocs-core@15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922))(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react@19.2.0-canary-1eca9a27-20250922)(vite@6.2.5(@types/node@20.17.6(patch_hash=rvl3vkomen3tospgr67bzubfyu))(jiti@2.5.1)(sass@1.77.8)(tsx@4.19.2)) fumadocs-ui: specifier: 15.7.12 - version: 15.7.12(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(tailwindcss@4.1.13) + version: 15.7.12(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(tailwindcss@4.1.13) next: specifier: 15.5.3 - version: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) + version: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) react: specifier: 19.2.0-canary-1eca9a27-20250922 version: 19.2.0-canary-1eca9a27-20250922 @@ -1304,8 +1304,8 @@ importers: specifier: 10.0.0 version: 10.0.0(@babel/core@7.26.10)(webpack@5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.15))(esbuild@0.25.9)) babel-plugin-react-compiler: - specifier: 19.1.0-rc.2 - version: 19.1.0-rc.2 + specifier: 0.0.0-experimental-3fde738-20250918 + version: 0.0.0-experimental-3fde738-20250918 babel-plugin-transform-define: specifier: 2.0.0 version: 2.0.0 @@ -7129,8 +7129,8 @@ packages: peerDependencies: '@babel/core': 7.26.10 - babel-plugin-react-compiler@19.1.0-rc.2: - resolution: {integrity: sha512-kSNA//p5fMO6ypG8EkEVPIqAjwIXm5tMjfD1XRPL/sRjYSbJ6UsvORfaeolNWnZ9n310aM0xJP7peW26BuCVzA==} + babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918: + resolution: {integrity: sha512-TTkfqDLLGpRX7pw1tsjA573aeQ8RXpLubFXwW4xJ/6t9Uk+34QrfXGXluIr+xDe82p9aFfwkWThtXvJz/WkaFQ==} babel-plugin-transform-async-to-promises@0.8.15: resolution: {integrity: sha512-fDXP68ZqcinZO2WCiimCL9zhGjGXOnn3D33zvbh+yheZ/qOrNVVDDIBtAaM3Faz8TRvQzHiRKsu3hfrBAhEncQ==} @@ -14859,7 +14859,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.11.0: @@ -24412,7 +24411,7 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-react-compiler@19.1.0-rc.2: + babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918: dependencies: '@babel/types': 7.27.0 @@ -28205,7 +28204,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922): + fumadocs-core@15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922): dependencies: '@formatjs/intl-localematcher': 0.6.1 '@orama/orama': 3.1.13 @@ -28226,20 +28225,20 @@ snapshots: unist-util-visit: 5.0.0 optionalDependencies: '@types/react': 19.1.13 - next: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) + next: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) react: 19.2.0-canary-1eca9a27-20250922 react-dom: 19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922) transitivePeerDependencies: - supports-color - fumadocs-mdx@11.10.0(fumadocs-core@15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922))(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react@19.2.0-canary-1eca9a27-20250922)(vite@6.2.5(@types/node@20.17.6(patch_hash=rvl3vkomen3tospgr67bzubfyu))(jiti@2.5.1)(sass@1.77.8)(tsx@4.19.2)): + fumadocs-mdx@11.10.0(fumadocs-core@15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922))(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react@19.2.0-canary-1eca9a27-20250922)(vite@6.2.5(@types/node@20.17.6(patch_hash=rvl3vkomen3tospgr67bzubfyu))(jiti@2.5.1)(sass@1.77.8)(tsx@4.19.2)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.0.0 chokidar: 4.0.3 esbuild: 0.25.9 estree-util-value-to-estree: 3.4.0 - fumadocs-core: 15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) + fumadocs-core: 15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) js-yaml: 4.1.0 lru-cache: 11.2.1 picocolors: 1.1.1 @@ -28251,13 +28250,13 @@ snapshots: unist-util-visit: 5.0.0 zod: 4.1.9 optionalDependencies: - next: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) + next: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) react: 19.2.0-canary-1eca9a27-20250922 vite: 6.2.5(@types/node@20.17.6(patch_hash=rvl3vkomen3tospgr67bzubfyu))(jiti@2.5.1)(sass@1.77.8)(tsx@4.19.2) transitivePeerDependencies: - supports-color - fumadocs-ui@15.7.12(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(tailwindcss@4.1.13): + fumadocs-ui@15.7.12(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(tailwindcss@4.1.13): dependencies: '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) @@ -28270,7 +28269,7 @@ snapshots: '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.2.0-canary-1eca9a27-20250922) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.1.7(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) class-variance-authority: 0.7.1 - fumadocs-core: 15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) + fumadocs-core: 15.7.12(@types/react@19.1.13)(next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8))(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) lodash.merge: 4.6.2 next-themes: 0.4.6(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922) postcss-selector-parser: 7.1.0 @@ -28281,7 +28280,7 @@ snapshots: tailwind-merge: 3.3.1 optionalDependencies: '@types/react': 19.1.13 - next: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) + next: 15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8) tailwindcss: 4.1.13 transitivePeerDependencies: - '@mixedbread/sdk' @@ -32389,7 +32388,7 @@ snapshots: next-tick@1.0.0: {} - next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8): + next@15.5.3(@babel/core@7.26.10)(@opentelemetry/api@1.6.0)(@playwright/test@1.51.1)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-3fde738-20250918)(react-dom@19.2.0-canary-1eca9a27-20250922(react@19.2.0-canary-1eca9a27-20250922))(react@19.2.0-canary-1eca9a27-20250922)(sass@1.77.8): dependencies: '@next/env': 15.5.3 '@swc/helpers': 0.5.15 @@ -32409,7 +32408,7 @@ snapshots: '@next/swc-win32-x64-msvc': 15.5.3 '@opentelemetry/api': 1.6.0 '@playwright/test': 1.51.1 - babel-plugin-react-compiler: 19.1.0-rc.2 + babel-plugin-react-compiler: 0.0.0-experimental-3fde738-20250918 sass: 1.77.8 sharp: 0.34.4 transitivePeerDependencies: diff --git a/test/e2e/react-compiler/app/function-naming/page.tsx b/test/e2e/react-compiler/app/function-naming/page.tsx new file mode 100644 index 00000000000000..9ce930c2efa180 --- /dev/null +++ b/test/e2e/react-compiler/app/function-naming/page.tsx @@ -0,0 +1,23 @@ +'use client' + +import { useEffect, useState } from 'react' + +export default function Page() { + const [callFrame, setCallFrame] = useState(null) + useEffect(() => { + const error = new Error('test-top-frame') + console.error(error) + + const callStack = new Error('test-top-frame').stack.split( + 'test-top-frame\n' + )[1] + // indices might change due to different compiler optimizations + const callFrame = callStack.split('\n')[0] + setCallFrame(callFrame) + }, []) + return ( +
+      {String(callFrame)}
+    
+ ) +} diff --git a/test/e2e/react-compiler/app/page.tsx b/test/e2e/react-compiler/app/page.tsx index dc25e277baa39a..1a61822ef3acc4 100644 --- a/test/e2e/react-compiler/app/page.tsx +++ b/test/e2e/react-compiler/app/page.tsx @@ -1,27 +1,31 @@ 'use client' -import { useEffect } from 'react' +import { Profiler, useReducer } from 'react' -export default function Page() { - let $_: any - if (typeof window !== 'undefined') { - // eslint-disable-next-line no-eval - $_ = eval('$') - } +if (typeof window !== 'undefined') { + ;(window as any).staticChildRenders = 0 +} - useEffect(() => { - if (Array.isArray($_)) { - document.getElementById('react-compiler-enabled-message')!.textContent = - `React compiler is enabled with ${$_!.length} memo slots` - } - }) +function StaticChild() { + return ( + { + ;(window as any).staticChildRenders += 1 + }} + id="test" + > +
static child
+
+ ) +} +export default function Page() { + const [count, increment] = useReducer((n) => n + 1, 1) return ( <> -
-

-

hello world

-

+
Parent commits: {count}
+ + ) } diff --git a/test/e2e/react-compiler/next.config.js b/test/e2e/react-compiler/next.config.js index 0eb59ba2d7ee56..67d7c6e2103d65 100644 --- a/test/e2e/react-compiler/next.config.js +++ b/test/e2e/react-compiler/next.config.js @@ -5,6 +5,7 @@ const nextConfig = { experimental: { reactCompiler: true, }, + reactProductionProfiling: true, } module.exports = nextConfig diff --git a/test/e2e/react-compiler/react-compiler.test.ts b/test/e2e/react-compiler/react-compiler.test.ts index 51baf5f3a07ed4..f9764191ab77ea 100644 --- a/test/e2e/react-compiler/react-compiler.test.ts +++ b/test/e2e/react-compiler/react-compiler.test.ts @@ -16,88 +16,157 @@ function normalizeCodeLocInfo(str) { ) } -describe.each(['default', 'babelrc'])('react-compiler %s', (variant) => { - const dependencies = (global as any).isNextDeploy - ? // `link` is incompatible with the npm version used when this test is deployed - { - 'reference-library': 'file:./reference-library', - } - : { - 'reference-library': 'link:./reference-library', - } - const { next, isNextDev } = nextTestSetup({ - files: - variant === 'babelrc' - ? __dirname - : { - app: new FileRef(join(__dirname, 'app')), - 'next.config.js': new FileRef(join(__dirname, 'next.config.js')), - 'reference-library': new FileRef( - join(__dirname, 'reference-library') - ), - }, - - dependencies: { - 'babel-plugin-react-compiler': '19.1.0-rc.2', - ...dependencies, - }, - }) - - it('should show an experimental warning', async () => { - await retry(() => { - expect(next.cliOutput).toContain('Experiments (use with caution)') - expect(stripAnsi(next.cliOutput)).toContain('✓ reactCompiler') +describe.each(['default', 'babelrc'] as const)( + 'react-compiler %s', + (variant) => { + const dependencies = (global as any).isNextDeploy + ? // `link` is incompatible with the npm version used when this test is deployed + { + 'reference-library': 'file:./reference-library', + } + : { + 'reference-library': 'link:./reference-library', + } + const { next, isNextDev, isTurbopack } = nextTestSetup({ + files: + variant === 'babelrc' + ? __dirname + : { + app: new FileRef(join(__dirname, 'app')), + 'next.config.js': new FileRef(join(__dirname, 'next.config.js')), + 'reference-library': new FileRef( + join(__dirname, 'reference-library') + ), + }, + // TODO: set only config instead once bundlers are consistent + buildArgs: ['--profile'], + dependencies: { + 'babel-plugin-react-compiler': '0.0.0-experimental-3fde738-20250918', + ...dependencies, + }, + }) + + it('should show an experimental warning', async () => { + await retry(() => { + expect(next.cliOutput).toContain('Experiments (use with caution)') + expect(stripAnsi(next.cliOutput)).toContain('✓ reactCompiler') + }) }) - }) - it('should render', async () => { - const browser = await next.browser('/') + it('should memoize Components', async () => { + const browser = await next.browser('/') + + expect(await browser.eval('window.staticChildRenders')).toEqual(1) + expect( + await browser.elementByCss('[data-testid="parent-commits"]').text() + ).toEqual('Parent commits: 1') + + await browser.elementByCss('button').click() + await browser.elementByCss('button').click() + await browser.elementByCss('button').click() + + expect(await browser.eval('window.staticChildRenders')).toEqual(1) + expect( + await browser.elementByCss('[data-testid="parent-commits"]').text() + ).toEqual('Parent commits: 4') + }) + + it('should work with a library that uses the react-server condition', async () => { + const outputIndex = next.cliOutput.length + await next.render('/library-react-server') + + const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) + expect(cliOutput).not.toMatch(/error/) + }) - await retry(async () => { - const text = await browser - .elementByCss('#react-compiler-enabled-message') + it('should work with a library using use client', async () => { + const outputIndex = next.cliOutput.length + await next.render('/library-client') + + const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) + expect(cliOutput).not.toMatch(/error/) + }) + + it('should name functions in dev', async () => { + const browser = await next.browser('/function-naming') + await browser.waitForElementByCss( + '[data-testid="call-frame"][aria-busy="false"]', + 5000 + ) + + const callFrame = await browser + .elementByCss('[data-testid="call-frame"]') .text() - expect(text).toMatch(/React compiler is enabled with \d+ memo slots/) + const devFunctionName = + variant === 'babelrc' + ? // next/babel transpiles away arrow functions defeating the React Compiler naming + 'PageUseEffect' + : // expected naming heuristic from React Compiler. This may change in future. + // Just make sure this is the heuristic from the React Compiler not something else. + 'Page[useEffect()]' + if (isNextDev) { + if (isTurbopack) { + // FIXME: https://linear.app/vercel/issue/NAR-351 + await expect(browser).toDisplayCollapsedRedbox(` + { + "description": "test-top-frame", + "environmentLabel": null, + "label": "Console Error", + "source": null, + "stack": [ + "", + ], + } + `) + } else { + await expect(browser).toDisplayCollapsedRedbox(` + { + "description": "test-top-frame", + "environmentLabel": null, + "label": "Console Error", + "source": "app/function-naming/page.tsx (8:19) @ ${devFunctionName} + > 8 | const error = new Error('test-top-frame') + | ^", + "stack": [ + "${devFunctionName} app/function-naming/page.tsx (8:19)", + ], + } + `) + } + // We care more about the sourcemapped frame in the Redbox. + // This assertion is only here to show that the negative assertion below is valid. + expect(normalizeCodeLocInfo(callFrame)).toEqual( + ` at ${devFunctionName} (**)` + ) + } else { + expect(normalizeCodeLocInfo(callFrame)).not.toEqual( + ` at ${devFunctionName} (**)` + ) + } }) - }) - - it('should work with a library that uses the react-server condition', async () => { - const outputIndex = next.cliOutput.length - await next.render('/library-react-server') - - const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) - expect(cliOutput).not.toMatch(/error/) - }) - - it('should work with a library using use client', async () => { - const outputIndex = next.cliOutput.length - await next.render('/library-client') - - const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) - expect(cliOutput).not.toMatch(/error/) - }) - - it('throws if the React Compiler is used in a React Server environment', async () => { - const outputIndex = next.cliOutput.length - const browser = await next.browser('/library-missing-react-server') - - const cliOutput = normalizeCodeLocInfo( - stripAnsi(next.cliOutput.slice(outputIndex)) - ) - if (isNextDev) { - // TODO(NDX-663): Unhelpful error message. - // Should say that the library should have a react-server entrypoint that doesn't use the React Compiler. - expect(cliOutput).toContain( - '' + - "\n ⨯ TypeError: Cannot read properties of undefined (reading 'H')" + - // location not important. Just that this is the only frame. - // TODO: Stack should start at product code. Possible React limitation. - '\n at Container (**)' + - // Will just point to original file location - '\n 2 |' + + it('throws if the React Compiler is used in a React Server environment', async () => { + const outputIndex = next.cliOutput.length + const browser = await next.browser('/library-missing-react-server') + + const cliOutput = normalizeCodeLocInfo( + stripAnsi(next.cliOutput.slice(outputIndex)) ) + if (isNextDev) { + // TODO(NDX-663): Unhelpful error message. + // Should say that the library should have a react-server entrypoint that doesn't use the React Compiler. + expect(cliOutput).toContain( + '' + + "\n ⨯ TypeError: Cannot read properties of undefined (reading 'H')" + + // location not important. Just that this is the only frame. + // TODO: Stack should start at product code. Possible React limitation. + '\n at Container (**)' + + // Will just point to original file location + '\n 2 |' + ) - await assertHasRedbox(browser) - } - }) -}) + await assertHasRedbox(browser) + } + }) + } +) From 854cefaaf4d9d53c1b5663058620fe6469db7132 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 23 Sep 2025 11:07:56 +0200 Subject: [PATCH 2/7] GPT 4.1 port to Rust --- .../src/next_shared/webpack_rules/babel.rs | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/crates/next-core/src/next_shared/webpack_rules/babel.rs b/crates/next-core/src/next_shared/webpack_rules/babel.rs index ba7f676678dd51..cad705625251b6 100644 --- a/crates/next-core/src/next_shared/webpack_rules/babel.rs +++ b/crates/next-core/src/next_shared/webpack_rules/babel.rs @@ -23,6 +23,22 @@ use crate::{ }, }; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +struct ReactCompilerEnvironmentConfig { + #[serde(skip_serializing_if = "Option::is_none")] + enable_name_anonymous_functions: Option, +} + +// Wrapper struct to augment the configured React Compiler options with defaults determined by +// Next.js +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +struct ParsedReactCompilerOptions { + #[serde(flatten)] + inner: T, + #[serde(skip_serializing_if = "Option::is_none")] + environment: Option, +} + // https://babeljs.io/docs/config-files // TODO: Also support a `babel` key in a package.json file const BABEL_CONFIG_FILES: &[&str] = &[ @@ -115,6 +131,34 @@ pub async fn get_babel_loader_rules( let react_compiler_options = next_config.react_compiler_options().await?; + // Enable environment.enableNameAnonymousFunctions in development + let parsed_react_compiler_options = if let Some(ref opts) = react_compiler_options.as_ref() { + let environment = + if builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Development) { + Some(ReactCompilerEnvironmentConfig { + enable_name_anonymous_functions: Some(true), + }) + } else { + None + }; + Some(ParsedReactCompilerOptions { + inner: (**opts).clone(), + environment, + }) + } else { + None + }; + + // print serialized parsed_react_compiler_options + if let Some(parsed_react_compiler_options) = &parsed_react_compiler_options { + // debug print + println!( + "Parsed React Compiler Options: {}", + serde_json::to_string_pretty(parsed_react_compiler_options) + .expect("parsed react compiler options JSON serialization should never fail") + ); + } + // if there's no babel config and react-compiler shouldn't be enabled, bail out early if babel_config_path.is_none() && (react_compiler_options.is_none() @@ -145,16 +189,15 @@ pub async fn get_babel_loader_rules( } let mut loader_conditions = Vec::new(); - if let Some(react_compiler_options) = &*react_compiler_options + if let Some(parsed_opts) = parsed_react_compiler_options.as_ref() && let Some(babel_plugin_path) = resolve_babel_plugin_react_compiler(next_config, project_path).await? { - let react_compiler_options = react_compiler_options.await?; let react_compiler_plugins = serde_json::Value::Array(vec![serde_json::Value::Array(vec![ serde_json::Value::String(babel_plugin_path.into_owned()), - serde_json::to_value(&*react_compiler_options) - .expect("react compiler options JSON serialization should never fail"), + serde_json::to_value(parsed_opts) + .expect("parsed react compiler options JSON serialization should never fail"), ])]); loader_options.insert("reactCompilerPlugins".to_owned(), react_compiler_plugins); @@ -165,7 +208,7 @@ pub async fn get_babel_loader_rules( // // NOTE: we already bail out at the earlier if `foreign` condition is set or if // `browser` is not set. - match react_compiler_options.compilation_mode { + match parsed_opts.inner.compilation_mode { ReactCompilerCompilationMode::Annotation => { loader_conditions.push(ConditionItem::Base { path: None, From c94a6cea76380ed65b590f13678334a2adaef41a Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 23 Sep 2025 11:32:14 +0200 Subject: [PATCH 3/7] Claude Sonnet 4 port to Rust --- .../src/next_shared/webpack_rules/babel.rs | 49 +++++++++---------- .../e2e/react-compiler/react-compiler.test.ts | 3 +- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/crates/next-core/src/next_shared/webpack_rules/babel.rs b/crates/next-core/src/next_shared/webpack_rules/babel.rs index cad705625251b6..81dff937ad0d44 100644 --- a/crates/next-core/src/next_shared/webpack_rules/babel.rs +++ b/crates/next-core/src/next_shared/webpack_rules/babel.rs @@ -26,13 +26,14 @@ use crate::{ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct ReactCompilerEnvironmentConfig { #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "enableNameAnonymousFunctions")] enable_name_anonymous_functions: Option, } // Wrapper struct to augment the configured React Compiler options with defaults determined by // Next.js #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -struct ParsedReactCompilerOptions { +struct ResolvedReactCompilerOptions { #[serde(flatten)] inner: T, #[serde(skip_serializing_if = "Option::is_none")] @@ -129,10 +130,20 @@ pub async fn get_babel_loader_rules( } } - let react_compiler_options = next_config.react_compiler_options().await?; + let configured_react_compiler_options = next_config.react_compiler_options().await?; - // Enable environment.enableNameAnonymousFunctions in development - let parsed_react_compiler_options = if let Some(ref opts) = react_compiler_options.as_ref() { + // if there's no babel config and react-compiler shouldn't be enabled, bail out early + if babel_config_path.is_none() + && (configured_react_compiler_options.is_none() + || !builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Browser)) + { + return Ok(Vec::new()); + } + + // Create extended react compiler options with environment.enableNameAnonymousFunctions if in + // development + let react_compiler_options = if let Some(ref opts) = configured_react_compiler_options.as_ref() + { let environment = if builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Development) { Some(ReactCompilerEnvironmentConfig { @@ -141,7 +152,8 @@ pub async fn get_babel_loader_rules( } else { None }; - Some(ParsedReactCompilerOptions { + + Some(ResolvedReactCompilerOptions { inner: (**opts).clone(), environment, }) @@ -149,24 +161,6 @@ pub async fn get_babel_loader_rules( None }; - // print serialized parsed_react_compiler_options - if let Some(parsed_react_compiler_options) = &parsed_react_compiler_options { - // debug print - println!( - "Parsed React Compiler Options: {}", - serde_json::to_string_pretty(parsed_react_compiler_options) - .expect("parsed react compiler options JSON serialization should never fail") - ); - } - - // if there's no babel config and react-compiler shouldn't be enabled, bail out early - if babel_config_path.is_none() - && (react_compiler_options.is_none() - || !builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Browser)) - { - return Ok(Vec::new()); - } - // - See `packages/next/src/build/babel/loader/types.d.ts` for all the configuration options. // - See `packages/next/src/build/get-babel-loader-config.ts` for how we use this in webpack. let serde_json::Value::Object(mut loader_options) = serde_json::json!({ @@ -189,15 +183,15 @@ pub async fn get_babel_loader_rules( } let mut loader_conditions = Vec::new(); - if let Some(parsed_opts) = parsed_react_compiler_options.as_ref() + if let Some(react_compiler_plugin_options) = react_compiler_options.as_ref() && let Some(babel_plugin_path) = resolve_babel_plugin_react_compiler(next_config, project_path).await? { let react_compiler_plugins = serde_json::Value::Array(vec![serde_json::Value::Array(vec![ serde_json::Value::String(babel_plugin_path.into_owned()), - serde_json::to_value(parsed_opts) - .expect("parsed react compiler options JSON serialization should never fail"), + serde_json::to_value(react_compiler_plugin_options) + .expect("react compiler options JSON serialization should never fail"), ])]); loader_options.insert("reactCompilerPlugins".to_owned(), react_compiler_plugins); @@ -208,7 +202,8 @@ pub async fn get_babel_loader_rules( // // NOTE: we already bail out at the earlier if `foreign` condition is set or if // `browser` is not set. - match parsed_opts.inner.compilation_mode { + let inner_opts = react_compiler_plugin_options.inner.await?; + match inner_opts.compilation_mode { ReactCompilerCompilationMode::Annotation => { loader_conditions.push(ConditionItem::Base { path: None, diff --git a/test/e2e/react-compiler/react-compiler.test.ts b/test/e2e/react-compiler/react-compiler.test.ts index f9764191ab77ea..dc34b1fbce2d2e 100644 --- a/test/e2e/react-compiler/react-compiler.test.ts +++ b/test/e2e/react-compiler/react-compiler.test.ts @@ -98,8 +98,9 @@ describe.each(['default', 'babelrc'] as const)( .elementByCss('[data-testid="call-frame"]') .text() const devFunctionName = - variant === 'babelrc' + variant === 'babelrc' && !isTurbopack ? // next/babel transpiles away arrow functions defeating the React Compiler naming + // TODO: Does Webpack or Turbopack get the Babel config right? 'PageUseEffect' : // expected naming heuristic from React Compiler. This may change in future. // Just make sure this is the heuristic from the React Compiler not something else. From 425c2f86c50213f18d30580a079b17a96b3ae0c4 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 23 Sep 2025 15:08:27 +0200 Subject: [PATCH 4/7] Rust check --- crates/next-core/src/next_shared/webpack_rules/babel.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/next-core/src/next_shared/webpack_rules/babel.rs b/crates/next-core/src/next_shared/webpack_rules/babel.rs index 81dff937ad0d44..2a09cc93483142 100644 --- a/crates/next-core/src/next_shared/webpack_rules/babel.rs +++ b/crates/next-core/src/next_shared/webpack_rules/babel.rs @@ -142,8 +142,7 @@ pub async fn get_babel_loader_rules( // Create extended react compiler options with environment.enableNameAnonymousFunctions if in // development - let react_compiler_options = if let Some(ref opts) = configured_react_compiler_options.as_ref() - { + let react_compiler_options = if let Some(opts) = configured_react_compiler_options.as_ref() { let environment = if builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Development) { Some(ReactCompilerEnvironmentConfig { @@ -154,7 +153,7 @@ pub async fn get_babel_loader_rules( }; Some(ResolvedReactCompilerOptions { - inner: (**opts).clone(), + inner: (*opts), environment, }) } else { From 4d342b84598c1ac27f908b0cd32f330ead0f8861 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Tue, 23 Sep 2025 13:32:19 -0700 Subject: [PATCH 5/7] attempt to clean up / simplify the rust implementation --- .../src/next_shared/webpack_rules/babel.rs | 75 ++++++++----------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/crates/next-core/src/next_shared/webpack_rules/babel.rs b/crates/next-core/src/next_shared/webpack_rules/babel.rs index 2a09cc93483142..5280d5e5ba7a2e 100644 --- a/crates/next-core/src/next_shared/webpack_rules/babel.rs +++ b/crates/next-core/src/next_shared/webpack_rules/babel.rs @@ -2,6 +2,7 @@ use std::{collections::BTreeSet, sync::LazyLock}; use anyhow::{Context, Result}; use regex::Regex; +use serde::Serialize; use turbo_esregex::EsRegex; use turbo_rcstr::{RcStr, rcstr}; use turbo_tasks::{ResolvedVc, Vc}; @@ -16,30 +17,13 @@ use turbopack_core::{ use turbopack_node::transforms::webpack::WebpackLoaderItem; use crate::{ - next_config::{NextConfig, ReactCompilerCompilationMode}, + next_config::{NextConfig, ReactCompilerCompilationMode, ReactCompilerOptions}, next_import_map::try_get_next_package, next_shared::webpack_rules::{ ManuallyConfiguredBuiltinLoaderIssue, WebpackLoaderBuiltinCondition, }, }; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -struct ReactCompilerEnvironmentConfig { - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "enableNameAnonymousFunctions")] - enable_name_anonymous_functions: Option, -} - -// Wrapper struct to augment the configured React Compiler options with defaults determined by -// Next.js -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -struct ResolvedReactCompilerOptions { - #[serde(flatten)] - inner: T, - #[serde(skip_serializing_if = "Option::is_none")] - environment: Option, -} - // https://babeljs.io/docs/config-files // TODO: Also support a `babel` key in a package.json file const BABEL_CONFIG_FILES: &[&str] = &[ @@ -130,36 +114,16 @@ pub async fn get_babel_loader_rules( } } - let configured_react_compiler_options = next_config.react_compiler_options().await?; + let react_compiler_options = next_config.react_compiler_options().await?; // if there's no babel config and react-compiler shouldn't be enabled, bail out early if babel_config_path.is_none() - && (configured_react_compiler_options.is_none() + && (react_compiler_options.is_none() || !builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Browser)) { return Ok(Vec::new()); } - // Create extended react compiler options with environment.enableNameAnonymousFunctions if in - // development - let react_compiler_options = if let Some(opts) = configured_react_compiler_options.as_ref() { - let environment = - if builtin_conditions.contains(&WebpackLoaderBuiltinCondition::Development) { - Some(ReactCompilerEnvironmentConfig { - enable_name_anonymous_functions: Some(true), - }) - } else { - None - }; - - Some(ResolvedReactCompilerOptions { - inner: (*opts), - environment, - }) - } else { - None - }; - // - See `packages/next/src/build/babel/loader/types.d.ts` for all the configuration options. // - See `packages/next/src/build/get-babel-loader-config.ts` for how we use this in webpack. let serde_json::Value::Object(mut loader_options) = serde_json::json!({ @@ -182,14 +146,38 @@ pub async fn get_babel_loader_rules( } let mut loader_conditions = Vec::new(); - if let Some(react_compiler_plugin_options) = react_compiler_options.as_ref() + if let Some(react_compiler_options) = react_compiler_options.as_ref() && let Some(babel_plugin_path) = resolve_babel_plugin_react_compiler(next_config, project_path).await? { + let react_compiler_options = react_compiler_options.await?; + + // we don't want to accept user-supplied `environment` options, but we do want to pass + // `enableNameAnonymousFunctions` down to the babel plugin based on dev/prod. + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct EnvironmentOptions { + enable_name_anonymous_functions: bool, + } + + #[derive(Serialize)] + struct ResolvedOptions<'a> { + #[serde(flatten)] + base: &'a ReactCompilerOptions, + environment: EnvironmentOptions, + } + + let resolved_options = ResolvedOptions { + base: &react_compiler_options, + environment: EnvironmentOptions { + enable_name_anonymous_functions: builtin_conditions + .contains(&WebpackLoaderBuiltinCondition::Development), + }, + }; let react_compiler_plugins = serde_json::Value::Array(vec![serde_json::Value::Array(vec![ serde_json::Value::String(babel_plugin_path.into_owned()), - serde_json::to_value(react_compiler_plugin_options) + serde_json::to_value(resolved_options) .expect("react compiler options JSON serialization should never fail"), ])]); @@ -201,8 +189,7 @@ pub async fn get_babel_loader_rules( // // NOTE: we already bail out at the earlier if `foreign` condition is set or if // `browser` is not set. - let inner_opts = react_compiler_plugin_options.inner.await?; - match inner_opts.compilation_mode { + match react_compiler_options.compilation_mode { ReactCompilerCompilationMode::Annotation => { loader_conditions.push(ConditionItem::Base { path: None, From af3c151507ee1d3445e6ddf14ba6c87815639286 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Wed, 24 Sep 2025 08:51:11 +0200 Subject: [PATCH 6/7] Consistently overwrite `environment` --- .../next/src/build/get-babel-loader-config.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/next/src/build/get-babel-loader-config.ts b/packages/next/src/build/get-babel-loader-config.ts index 2535e974bb59f5..64c1c6b7b670c8 100644 --- a/packages/next/src/build/get-babel-loader-config.ts +++ b/packages/next/src/build/get-babel-loader-config.ts @@ -24,22 +24,14 @@ const getReactCompilerPlugins = ( return undefined } - const defaultOptions: ReactCompilerOptions & { - environment?: { - enableNameAnonymousFunctions?: EnvironmentConfig['enableNameAnonymousFunctions'] - } - } = isDev - ? { - environment: { - enableNameAnonymousFunctions: true, - }, - } - : {} + const environment: Pick = { + enableNameAnonymousFunctions: isDev, + } const options: ReactCompilerOptions = typeof maybeOptions === 'boolean' ? {} : maybeOptions const compilerOptions: JSONValue = { - ...defaultOptions, ...options, + environment, } return [[getReactCompiler(), compilerOptions]] } From 592b4ddb17acc5c4b499a605565fb2e8d4b3ad36 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Wed, 24 Sep 2025 09:22:09 +0200 Subject: [PATCH 7/7] Poke