From 56d0ddae18993eb696ab41d0fc5041948b88024a Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Tue, 15 Jul 2025 16:55:31 +0200 Subject: [PATCH 1/3] [Flight] Switch to `__turbopack_load_by_url__` (#33791) --- .eslintrc.js | 2 +- .../client/ReactFlightClientConfigBundlerTurbopackBrowser.js | 2 +- .../src/client/ReactFlightClientConfigBundlerTurbopackServer.js | 2 +- scripts/flow/environment.js | 2 +- scripts/rollup/validate/eslintrc.cjs.js | 2 +- scripts/rollup/validate/eslintrc.cjs2015.js | 2 +- scripts/rollup/validate/eslintrc.esm.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f7f748516d9ab..4e023cd9d333b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -474,7 +474,7 @@ module.exports = { { files: ['packages/react-server-dom-turbopack/**/*.js'], globals: { - __turbopack_load__: 'readonly', + __turbopack_load_by_url__: 'readonly', __turbopack_require__: 'readonly', }, }, diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js index c418f51fa80ee..59aab436cbc2a 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js @@ -8,5 +8,5 @@ */ export function loadChunk(filename: string): Promise { - return __turbopack_load__(filename); + return __turbopack_load_by_url__(filename); } diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer.js b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer.js index c418f51fa80ee..59aab436cbc2a 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer.js @@ -8,5 +8,5 @@ */ export function loadChunk(filename: string): Promise { - return __turbopack_load__(filename); + return __turbopack_load_by_url__(filename); } diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 39c792b449277..f52e6fd428e20 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -150,7 +150,7 @@ declare const __webpack_require__: ((id: string) => any) & { u: string => string, }; -declare function __turbopack_load__(id: string): Promise; +declare function __turbopack_load_by_url__(id: string): Promise; declare const __turbopack_require__: ((id: string) => any) & { u: string => string, }; diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index e7e172599d0e6..e4cf904e5974d 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -65,7 +65,7 @@ module.exports = { __webpack_require__: 'readonly', // Flight Turbopack - __turbopack_load__: 'readonly', + __turbopack_load_by_url__: 'readonly', __turbopack_require__: 'readonly', // Flight Parcel diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 5e4feb64da8e8..4cda9b06a636e 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -62,7 +62,7 @@ module.exports = { __webpack_require__: 'readonly', // Flight Turbopack - __turbopack_load__: 'readonly', + __turbopack_load_by_url__: 'readonly', __turbopack_require__: 'readonly', // Flight Parcel diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index bef26bbd67efb..98fdc56c48c5f 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -65,7 +65,7 @@ module.exports = { __webpack_require__: 'readonly', // Flight Turbopack - __turbopack_load__: 'readonly', + __turbopack_load_by_url__: 'readonly', __turbopack_require__: 'readonly', // Flight Parcel From 2f0e7e570d3a99adc2a18e7575d1d2bb69660e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 15 Jul 2025 11:45:34 -0400 Subject: [PATCH 2/3] [Flight] Don't block on debug channel if it's not wired up (#33757) React Elements reference debug data (their stack and owner) in the debug channel. If the debug channel isn't wired up this can block the client from resolving. We can infer that if there's no debug channel wired up and the reference wasn't emitted before the element, then it's probably because it's in the debug channel. So we can skip it. This should also apply to debug chunks but they're not yet blocking until #33665 lands. --- .../react-client/src/ReactFlightClient.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index acc6e41cd4603..41dd877bb4fb3 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -1353,6 +1353,26 @@ function waitForReference( map: (response: Response, model: any, parentObject: Object, key: string) => T, path: Array, ): T { + if ( + __DEV__ && + // TODO: This should check for the existence of the "readable" side, not the "writable". + response._debugChannel === undefined + ) { + if ( + referencedChunk.status === PENDING && + parentObject[0] === REACT_ELEMENT_TYPE && + (key === '4' || key === '5') + ) { + // If the parent object is an unparsed React element tuple, and this is a reference + // to the owner or debug stack. Then we expect the chunk to have been emitted earlier + // in the stream. It might be blocked on other things but chunk should no longer be pending. + // If it's still pending that suggests that it was referencing an object in the debug + // channel, but no debug channel was wired up so it's missing. In this case we can just + // drop the debug info instead of halting the whole stream. + return (null: any); + } + } + let handler: InitializationHandler; if (initializingHandler) { handler = initializingHandler; From fe813143e23675963f92d25fa1fb35b39db40272 Mon Sep 17 00:00:00 2001 From: "Henry Q. Dineen" Date: Tue, 15 Jul 2025 14:50:20 -0400 Subject: [PATCH 3/3] [compiler] Check TSAsExpression and TSNonNullExpression reorderability (#33788) ## Summary The `TSAsExpression` and `TSNonNullExpression` nodes are supported by `lowerExpression()` but `isReorderableExpression()` does not check if they can be reordered. This PR updates `isReorderableExpression()` to handle these two node types by adding cases that fall through to the existing `TypeCastExpression` case. We ran `react-compiler-healthcheck` at scale on several of our repos and found dozens of `` (BuildHIR::node.lowerReorderableExpression) Expression type `TSAsExpression` cannot be safely reordered`` errors and a handful for `TSNonNullExpression`. ## How did you test this change? In this case I added two fixture tests --- .../src/HIR/BuildHIR.ts | 2 + .../ts-as-expression-default-value.expect.md | 67 +++++++++++++++++++ .../ts-as-expression-default-value.tsx | 14 ++++ ...on-null-expression-default-value.expect.md | 54 +++++++++++++++ .../ts-non-null-expression-default-value.tsx | 13 ++++ 5 files changed, 150 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index d0335fb3a435d..cd7d6a7a35b35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3001,6 +3001,8 @@ function isReorderableExpression( } } } + case 'TSAsExpression': + case 'TSNonNullExpression': case 'TypeCastExpression': { return isReorderableExpression( builder, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.expect.md new file mode 100644 index 0000000000000..505224cf9ed92 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.expect.md @@ -0,0 +1,67 @@ + +## Input + +```javascript +type Status = 'pending' | 'success' | 'error'; + +const StatusIndicator = ({status}: {status: Status}) => { + return
Status: {status}
; +}; + +const Component = ({status = 'pending' as Status}) => { + return ; +}; + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{status: 'success'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +type Status = "pending" | "success" | "error"; + +const StatusIndicator = (t0) => { + const $ = _c(3); + const { status } = t0; + const t1 = `status-${status}`; + let t2; + if ($[0] !== status || $[1] !== t1) { + t2 =
Status: {status}
; + $[0] = status; + $[1] = t1; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; +}; + +const Component = (t0) => { + const $ = _c(2); + const { status: t1 } = t0; + const status = t1 === undefined ? ("pending" as Status) : t1; + let t2; + if ($[0] !== status) { + t2 = ; + $[0] = status; + $[1] = t2; + } else { + t2 = $[1]; + } + return t2; +}; + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ status: "success" }], +}; + +``` + +### Eval output +(kind: ok)
Status: success
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.tsx new file mode 100644 index 0000000000000..715efd5bdc8d2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-as-expression-default-value.tsx @@ -0,0 +1,14 @@ +type Status = 'pending' | 'success' | 'error'; + +const StatusIndicator = ({status}: {status: Status}) => { + return
Status: {status}
; +}; + +const Component = ({status = 'pending' as Status}) => { + return ; +}; + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{status: 'success'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md new file mode 100644 index 0000000000000..7af6bc996a99a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md @@ -0,0 +1,54 @@ + +## Input + +```javascript +const THEME_MAP: ReadonlyMap = new Map([ + ['default', 'light'], + ['dark', 'dark'], +]); + +export const Component = ({theme = THEME_MAP.get('default')!}) => { + return
User preferences
; +}; + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{status: 'success'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +const THEME_MAP: ReadonlyMap = new Map([ + ["default", "light"], + ["dark", "dark"], +]); + +export const Component = (t0) => { + const $ = _c(2); + const { theme: t1 } = t0; + const theme = t1 === undefined ? THEME_MAP.get("default") : t1; + const t2 = `theme-${theme}`; + let t3; + if ($[0] !== t2) { + t3 =
User preferences
; + $[0] = t2; + $[1] = t3; + } else { + t3 = $[1]; + } + return t3; +}; + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ status: "success" }], +}; + +``` + +### Eval output +(kind: ok)
User preferences
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx new file mode 100644 index 0000000000000..c1d835d6f0f06 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx @@ -0,0 +1,13 @@ +const THEME_MAP: ReadonlyMap = new Map([ + ['default', 'light'], + ['dark', 'dark'], +]); + +export const Component = ({theme = THEME_MAP.get('default')!}) => { + return
User preferences
; +}; + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{status: 'success'}], +};