ITADN

next/swc drops the leading whitespace of a JSX text node that contains an HTML entity

#95881Openchrisgory 创建于 2026-07-16
C
chrisgorycommented
### Link to the code that reproduces this issue https://github.com/chrisgory/nextswc-jsx-entity-leading-space-repro ### To Reproduce `app/page.tsx` is the whole reproduction — four variants, one of which loses a space. 1. `npm install` 2. `npm run build` (Turbopack, the Next 16 default) 3. Inspect the emitted chunk: ```bash grep -rhoE '"(AAA|BBB|DDD)"\}\),("[^"]{0,22}|" ","[^"]{0,18})' .next/server/chunks/ssr/*.js ``` Also reproduces with `npm run build:webpack` (`next build --webpack`) and in `npm run dev`. ### Current vs. Expected behavior A JSX text node that (a) begins with a space and (b) contains an HTML entity loses its leading space. Without the entity — same shape, same position — the space is kept. ```jsx <p><b>AAA</b> alpha&rsquo;s tail.</p> {/* entity */} <p><b>BBB</b> bravo plain tail.</p> {/* no entity */} ``` Emitted by `next build` (identical under Turbopack and webpack): ```js "AAA"}),"alpha’s tail." // ← leading space GONE "BBB"})," bravo plain tail." // ← leading space kept ``` Rendered: **`AAAalpha’s tail.`** vs `BBB bravo plain tail.` Expected: both keep the space. Only whitespace containing a newline is stripped by JSX; this space has no newline in it, and its handling should not depend on whether the text node happens to contain an entity. `&rsquo;`, `&apos;`, `&nbsp;` and numeric (`&#8217;`) entities all trigger it. ### Scope Verified against build output: | case | result | | --- | --- | | `<b>AAA</b> alpha&rsquo;s tail.` — leading space + entity | **space dropped** | | `<b>BBB</b> bravo plain tail.` — leading space, no entity | kept | | `charlie&rsquo;s tail <b>CCC</b>` — **trailing** space + entity | kept | | `<b>DDD</b>{' '}delta&rsquo;s tail.` — explicit expression | kept | So it is specific to **leading** whitespace on an **entity-bearing** text node. ### Not bundler-specific, and not build-specific Both bundlers and both modes are affected, which points at the shared `next/swc` JSX transform rather than Turbopack: | | leading space | | --- | --- | | `next build` (Turbopack) | **dropped** | | `next build --webpack` | **dropped** | | `next dev` | **dropped** | ### Divergence from the reference implementations The same source through Babel and through standalone `@swc/core` keeps the space, so this looks specific to Next's copy/configuration of the transform: ```js // @babel/plugin-transform-react-jsx (runtime: automatic) }), " alpha’s tail."] // ← space kept // @swc/core (standalone, runtime: automatic, minified or not) _jsx(L,{children:"link"})," alpha’s tail." // ← space kept // next/swc (next dev + next build) "AAA"}),"alpha’s tail." // ← space dropped ``` Babel is the reference JSX implementation, and upstream `swc` agrees with it, which suggests `next/swc` is the outlier here rather than this being intended JSX semantics. ### Why it is easy to miss The source looks correct, and `react/no-unescaped-entities` actively pushes authors toward writing `&rsquo;`/`&apos;` — which is exactly what triggers this. The result is a silently missing space in shipped copy. We hit it in production on a real sentence: ```jsx <Link href="/pipeline">open the pipeline</Link> to see what&rsquo;s next. ``` rendering as **"open the pipelineto see what's next"**. A sweep of our build output found 28 occurrences across 20 files. ### Workaround Write the space as its own expression, so it is no longer part of the entity-bearing text node: ```jsx <Link href="/pipeline">open the pipeline</Link>{' '} to see what&rsquo;s next. ``` Using the literal character (`’`) instead of the entity also avoids it. ### Provide environment information ``` Operating System: Platform: darwin Arch: arm64 Binaries: Node: 22.16.0 Relevant Packages: next: 16.2.7 react: 19.2.0 react-dom: 19.2.0 ``` ### Which area(s) are affected? SWC transforms, Turbopack, Webpack ### Which stage(s) are affected? `next dev`, `next build`
2 条评论