Skip to content

Commit a19d078

Browse files
committed
fix: correct CRLF handling, JSDoc, and add negative test
- Fix \r\n producing double space: normalize CRLF to LF first, then replace remaining \t\n\r with spaces - Fix JSDoc: the real cause is Babel's code generator silently replacing newlines during serialization, not browser attribute normalization - Add negative test: component props (non-builtin tags) must preserve \n unchanged
1 parent 8745eec commit a19d078

4 files changed

Lines changed: 60 additions & 11 deletions

File tree

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,14 +2126,23 @@ const STRING_REQUIRES_EXPR_CONTAINER_PATTERN =
21262126
/[\u{0000}-\u{001F}\u{007F}\u{0080}-\u{FFFF}\u{010000}-\u{10FFFF}]|"|\\/u;
21272127

21282128
/**
2129-
* Browsers normalize tab, newline, and carriage return characters to spaces
2130-
* when parsing HTML attribute values. Without this normalization, the compiled
2131-
* code preserves these characters while the browser normalizes the server-
2132-
* rendered HTML, causing a hydration mismatch.
2129+
* When the compiler creates new StringLiteral AST nodes (without Babel's
2130+
* extra.raw), strings containing \n are wrapped in JSXExpressionContainers
2131+
* by STRING_REQUIRES_EXPR_CONTAINER_PATTERN. Babel's code generator may then
2132+
* silently replace newline characters with spaces during serialization
2133+
* (depending on retainLines/compact options), changing the runtime value.
2134+
* This causes a hydration mismatch: the server renders the original value
2135+
* while the client gets the Babel-transformed one.
21332136
*
2134-
* See: https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state
2137+
* We normalize \t, \n, and \r to spaces in string attributes of intrinsic
2138+
* HTML elements before the expression container check, which both prevents
2139+
* the Babel serialization issue and avoids unnecessary expression containers.
2140+
*
2141+
* CRLF (\r\n) is collapsed to a single space first, matching the HTML input
2142+
* stream preprocessing step (https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream).
21352143
*/
2136-
const ATTRIBUTE_WHITESPACE_NORMALIZE_PATTERN = /[\t\n\r]/g;
2144+
const NORMALIZE_CRLF_PATTERN = /\r\n/g;
2145+
const NORMALIZE_WHITESPACE_PATTERN = /[\t\n\r]/g;
21372146

21382147
function codegenJsxAttribute(
21392148
cx: Context,
@@ -2162,10 +2171,9 @@ function codegenJsxAttribute(
21622171
isBuiltinTag &&
21632172
!cx.fbtOperands.has(attribute.place.identifier.id)
21642173
) {
2165-
const normalized = value.value.replace(
2166-
ATTRIBUTE_WHITESPACE_NORMALIZE_PATTERN,
2167-
' ',
2168-
);
2174+
const normalized = value.value
2175+
.replace(NORMALIZE_CRLF_PATTERN, '\n')
2176+
.replace(NORMALIZE_WHITESPACE_PATTERN, ' ');
21692177
if (normalized !== value.value) {
21702178
value = createStringLiteral(value.loc, normalized);
21712179
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-multiline-classname-tabs.expect.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function Component() {
2222
const $ = _c(1);
2323
let t0;
2424
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25-
t0 = <div className="foo bar baz">Hello</div>;
25+
t0 = <div className="foo bar baz">Hello</div>;
2626
$[0] = t0;
2727
} else {
2828
t0 = $[0];
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
## Input
3+
4+
```javascript
5+
function Component() {
6+
return (
7+
<MyComponent
8+
data={"foo\nbar"}
9+
/>
10+
);
11+
}
12+
13+
```
14+
15+
## Code
16+
17+
```javascript
18+
import { c as _c } from "react/compiler-runtime";
19+
function Component() {
20+
const $ = _c(1);
21+
let t0;
22+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23+
t0 = <MyComponent data={"foo\nbar"} />;
24+
$[0] = t0;
25+
} else {
26+
t0 = $[0];
27+
}
28+
return t0;
29+
}
30+
31+
```
32+
33+
### Eval output
34+
(kind: exception) Fixture not implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function Component() {
2+
return (
3+
<MyComponent
4+
data={"foo\nbar"}
5+
/>
6+
);
7+
}

0 commit comments

Comments
 (0)