[compiler] Normalize whitespace in JSX string attributes for builtin tags#35978
Open
Felipeness wants to merge 3 commits intofacebook:mainfrom
Open
[compiler] Normalize whitespace in JSX string attributes for builtin tags#35978Felipeness wants to merge 3 commits intofacebook:mainfrom
Felipeness wants to merge 3 commits intofacebook:mainfrom
Conversation
…tags Browsers normalize tab, newline, and carriage return characters to spaces when parsing HTML attribute values. Without this normalization, the compiled code preserves these characters while the browser normalizes the server- rendered HTML, causing a hydration mismatch. This change normalizes \t, \n, and \r to spaces in JSX string attribute values for builtin HTML elements (div, span, etc.) during codegen, while preserving the original values for component props and fbt operands. Fixes facebook#35481
- 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
The actual root cause is @babel/plugin-transform-react-jsx normalizing /\n\s+/g → " " for plain JSX string attributes but skipping this for JSXExpressionContainer values. The compiler wraps strings in expression containers, bypassing the normalization and creating a mismatch. Use the same regex /\n\s+/g as the JSX transform to produce identical output. This replaces the incorrect /[\t\n\r]/g which over-normalized \t and \r (untouched by JSX transform) and diverged in output for all whitespace patterns. Rewrite fixtures to test the reporter's actual scenario (newline + indentation in Tailwind className).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #35481
Root cause
The Babel JSX transform (
@babel/plugin-transform-react-jsx) normalizes newlines followed by whitespace in plain JSX string attributes:This normalization is skipped for
JSXExpressionContainervalues. When the React Compiler wraps strings in expression containers (because\nmatchesSTRING_REQUIRES_EXPR_CONTAINER_PATTERNin theU+0000-U+001Frange), the JSX transform bypasses normalization. The server code (uncompiled) goes through the standard normalization, creating a hydration mismatch.Fix
Apply the same
/\n\s+/g→" "normalization that the JSX transform uses, before the expression container check. This ensures compiled output matches the behavior of non-compiled code. After normalization, strings that no longer contain control characters avoid unnecessary expression container wrapping entirely.Scope
div,span, etc.) — component props are preserved unchanged@babel/plugin-transform-react-jsxBefore (compiled output)
After (compiled output)
How did you test this change?
5 fixture tests covering positive and negative scenarios:
repro-multiline-classname-hydration\n+ indentation → single spacerepro-multiline-classname-newline-indent{"foo\n bar\n baz"}\n\s+normalized →"foo bar baz"repro-multiline-title-attributetitlewith\n+ indentationrepro-multiline-classname-expression{"foo\nbar"}—\nwithout trailing whitespace\s+after\n, matches JSX transform)repro-multiline-component-prop-preserved<MyComponent data={"\n"}>