Skip to content

Commit b1d412c

Browse files
eps1lonbgw
andauthored
Enable anonymous function naming in React Compiler (#84070)
Co-authored-by: Benjamin Woodruff <[email protected]>
1 parent 55f83e0 commit b1d412c

File tree

9 files changed

+248
-128
lines changed

9 files changed

+248
-128
lines changed

crates/next-core/src/next_shared/webpack_rules/babel.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{collections::BTreeSet, sync::LazyLock};
22

33
use anyhow::{Context, Result};
44
use regex::Regex;
5+
use serde::Serialize;
56
use turbo_esregex::EsRegex;
67
use turbo_rcstr::{RcStr, rcstr};
78
use turbo_tasks::{ResolvedVc, Vc};
@@ -16,7 +17,7 @@ use turbopack_core::{
1617
use turbopack_node::transforms::webpack::WebpackLoaderItem;
1718

1819
use crate::{
19-
next_config::{NextConfig, ReactCompilerCompilationMode},
20+
next_config::{NextConfig, ReactCompilerCompilationMode, ReactCompilerOptions},
2021
next_import_map::try_get_next_package,
2122
next_shared::webpack_rules::{
2223
ManuallyConfiguredBuiltinLoaderIssue, WebpackLoaderBuiltinCondition,
@@ -145,15 +146,38 @@ pub async fn get_babel_loader_rules(
145146
}
146147

147148
let mut loader_conditions = Vec::new();
148-
if let Some(react_compiler_options) = &*react_compiler_options
149+
if let Some(react_compiler_options) = react_compiler_options.as_ref()
149150
&& let Some(babel_plugin_path) =
150151
resolve_babel_plugin_react_compiler(next_config, project_path).await?
151152
{
152153
let react_compiler_options = react_compiler_options.await?;
154+
155+
// we don't want to accept user-supplied `environment` options, but we do want to pass
156+
// `enableNameAnonymousFunctions` down to the babel plugin based on dev/prod.
157+
#[derive(Serialize)]
158+
#[serde(rename_all = "camelCase")]
159+
struct EnvironmentOptions {
160+
enable_name_anonymous_functions: bool,
161+
}
162+
163+
#[derive(Serialize)]
164+
struct ResolvedOptions<'a> {
165+
#[serde(flatten)]
166+
base: &'a ReactCompilerOptions,
167+
environment: EnvironmentOptions,
168+
}
169+
170+
let resolved_options = ResolvedOptions {
171+
base: &react_compiler_options,
172+
environment: EnvironmentOptions {
173+
enable_name_anonymous_functions: builtin_conditions
174+
.contains(&WebpackLoaderBuiltinCondition::Development),
175+
},
176+
};
153177
let react_compiler_plugins =
154178
serde_json::Value::Array(vec![serde_json::Value::Array(vec![
155179
serde_json::Value::String(babel_plugin_path.into_owned()),
156-
serde_json::to_value(&*react_compiler_options)
180+
serde_json::to_value(resolved_options)
157181
.expect("react compiler options JSON serialization should never fail"),
158182
])]);
159183

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
"abort-controller": "3.0.0",
156156
"alex": "9.1.0",
157157
"async-sema": "3.0.1",
158-
"babel-plugin-react-compiler": "19.1.0-rc.2",
158+
"babel-plugin-react-compiler": "0.0.0-experimental-3fde738-20250918",
159159
"browserslist": "4.25.1",
160160
"buffer": "5.6.0",
161161
"cheerio": "0.22.0",

packages/next/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@
234234
"async-sema": "3.0.0",
235235
"axe-playwright": "2.0.3",
236236
"babel-loader": "10.0.0",
237-
"babel-plugin-react-compiler": "19.1.0-rc.2",
237+
"babel-plugin-react-compiler": "0.0.0-experimental-3fde738-20250918",
238238
"babel-plugin-transform-define": "2.0.0",
239239
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
240240
"browserify-zlib": "0.2.0",

packages/next/src/build/get-babel-loader-config.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { EnvironmentConfig } from 'babel-plugin-react-compiler'
12
import path from 'path'
23
import type { JSONValue, ReactCompilerOptions } from '../server/config-shared'
34
import type { NextBabelLoaderOptions } from './babel/loader/types'
@@ -23,16 +24,14 @@ const getReactCompilerPlugins = (
2324
return undefined
2425
}
2526

26-
const defaultOptions: ReactCompilerOptions = isDev
27-
? {
28-
// TODO: enable `environment.enableNameAnonymousFunctions`Ï
29-
}
30-
: {}
27+
const environment: Pick<EnvironmentConfig, 'enableNameAnonymousFunctions'> = {
28+
enableNameAnonymousFunctions: isDev,
29+
}
3130
const options: ReactCompilerOptions =
3231
typeof maybeOptions === 'boolean' ? {} : maybeOptions
3332
const compilerOptions: JSONValue = {
34-
...defaultOptions,
3533
...options,
34+
environment,
3635
}
3736
return [[getReactCompiler(), compilerOptions]]
3837
}

pnpm-lock.yaml

Lines changed: 21 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
5+
export default function Page() {
6+
const [callFrame, setCallFrame] = useState(null)
7+
useEffect(() => {
8+
const error = new Error('test-top-frame')
9+
console.error(error)
10+
11+
const callStack = new Error('test-top-frame').stack.split(
12+
'test-top-frame\n'
13+
)[1]
14+
// indices might change due to different compiler optimizations
15+
const callFrame = callStack.split('\n')[0]
16+
setCallFrame(callFrame)
17+
}, [])
18+
return (
19+
<pre data-testid="call-frame" aria-busy={callFrame === null}>
20+
{String(callFrame)}
21+
</pre>
22+
)
23+
}
Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
'use client'
22

3-
import { useEffect } from 'react'
3+
import { Profiler, useReducer } from 'react'
44

5-
export default function Page() {
6-
let $_: any
7-
if (typeof window !== 'undefined') {
8-
// eslint-disable-next-line no-eval
9-
$_ = eval('$')
10-
}
5+
if (typeof window !== 'undefined') {
6+
;(window as any).staticChildRenders = 0
7+
}
118

12-
useEffect(() => {
13-
if (Array.isArray($_)) {
14-
document.getElementById('react-compiler-enabled-message')!.textContent =
15-
`React compiler is enabled with ${$_!.length} memo slots`
16-
}
17-
})
9+
function StaticChild() {
10+
return (
11+
<Profiler
12+
onRender={(id, phase) => {
13+
;(window as any).staticChildRenders += 1
14+
}}
15+
id="test"
16+
>
17+
<div>static child</div>
18+
</Profiler>
19+
)
20+
}
1821

22+
export default function Page() {
23+
const [count, increment] = useReducer((n) => n + 1, 1)
1924
return (
2025
<>
21-
<div>
22-
<h1 id="react-compiler-enabled-message" />
23-
<p>hello world</p>
24-
</div>
26+
<div data-testid="parent-commits">Parent commits: {count}</div>
27+
<button onClick={increment}>Increment</button>
28+
<StaticChild />
2529
</>
2630
)
2731
}

test/e2e/react-compiler/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const nextConfig = {
55
experimental: {
66
reactCompiler: true,
77
},
8+
reactProductionProfiling: true,
89
}
910

1011
module.exports = nextConfig

0 commit comments

Comments
 (0)