Skip to content

feat(compiler,babel-plugin,solid): componentNames for SSR output; ssrScope swaps the id-bearing owner - #3441

Merged
ryansolid merged 1 commit into
nextfrom
ssr-component-names
Sep 14, 2026
Merged

ryansolid merged 1 commit into
nextfrom
ssr-component-names

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Follow-up to #3433, closing the "known gap" it left: compiled SSR JSX now gets the component label, so a server finding's ownerPath reads <App> › <Page> like the client's.

Compilers

componentNames applies to the ssr generate the way it does to dom. Under the option both compilers keep the createComponent call they otherwise inline to Comp(props) and pass the source tag text:

// componentNames: true, generate: "ssr"
_$createComponent(Child, { name: "Jane" }, "Child")
_$createComponent(Ui.Button, { variant: "primary" }, "Ui.Button")
_$createComponent(_self$.Row, {}, "this.Row")

Without the option SSR still inlines — prod output is byte-identical. Universal and dynamic stay unlabelled (user-owned two-argument createComponent). @solidjs/vite-plugin already passes the option for its dev and observe postures for every generate, so app server builds (Solid Start, TanStack Start) pick this up with no config change.

  • Babel: shared/component.ts — the label gate is dom || ssr; the SSR inline is skipped when labelled.
  • Oxc: AstSsrTransform gains component_names / uses_create_component; jsx_tag_name becomes a shared closure-based helper so the SSR lowering reuses the same this-via-_self$ and member-tag handling as DOM.
  • Fixtures: __dom_component_names_fixtures__/ssrInertssr, widened to the DOM fixture's shapes; the Oxc suite additionally pins that SSR without the option imports no createComponent. Parity harness green.

Runtime: ssrScope under transparent owners

Compiling the web server suite with the option (vite.config.server.mjs, i.e. the dev posture) surfaced a latent bug. The virtual hole scope swapped the current owner's id/_childCount, but content inside a hole resolves its ids by walking up past transparent owners — so with a transparent owner between the hole and its id-bearing ancestor, the reserved slot was invisible and the hole's content took fresh ids from the enclosing counter (_hk=3 where the client expects _hk=10).

This is reachable in prod today through runInServerComponentScope (its owner is transparent); with labels it would have been every component body. The scope now swaps the nearest id-bearing owner. solid/test/server/ssr-scope.spec.ts covers a plain transparent owner, the server-component scope, the labelled component owner, and nesting — all four failed before the fix.

Tests

  • server-diagnostics.spec.tsx drops its mount() workaround for plain JSX. Boundaries are compiled components too, so a boundary's finding now locates as ["<App>", "<Errored>"] / ["<App>", "<Loading>"] — matching the client.
  • Web server (91 files), hydrate (29), client (83) suites pass under the native compiler and JSX_COMPILER=babel; compiler vitest 5779 + cargo; babel-plugin 257; solid 618.

Out of repo

vite-plugin-solid's comment near componentNames still says "DOM-only by construction (the ssr generate ignores the flag)". Behavior there is already correct; only the comment is stale.

…Scope swaps the id-bearing owner

The SSR generate honours `componentNames` the way `dom` does. Under the
option both compilers keep the `createComponent` call they otherwise inline
to `Comp(props)` and pass the source tag text —
`_$createComponent(Comp, props, "Comp")` — so the server runtime's
observe/dev `createComponent` (a transparent, labelled owner around the
body, #3433) labels every compiled component and a server finding's
`ownerPath` reads `<App> › <Page>` like the client's. Without the option
SSR still inlines: prod output is byte-identical. Universal and dynamic
stay unlabelled. `@solidjs/vite-plugin` already passes the option for its
dev and observe postures for every generate, so app server builds pick this
up with no config change. Oxc's `jsx_tag_name` is now a shared,
closure-based helper so the SSR lowering reuses it (`this.Row` through the
`_self$` pre-pass, member tags). The `ssrInert` fixture becomes `ssr`,
widened to the DOM fixture's shapes; the Oxc suite pins that SSR without
the option imports no `createComponent`.

Compiling the web server suite with the option (vite.config.server.mjs —
the dev posture) surfaced a latent `ssrScope` bug: the virtual hole scope
swapped the CURRENT owner's `id`/`_childCount`, but content inside a hole
resolves ids by walking up past transparent owners, so with one in between
the reserved slot was invisible and the hole's content took fresh ids from
the enclosing counter (`_hk=3` where the client expects `_hk=10`). Reachable
in prod today through `runInServerComponentScope` (transparent), ubiquitous
once every component body runs under a labelled owner. The scope now swaps
the nearest id-bearing owner (solid/test/server/ssr-scope.spec.ts covers a
plain transparent owner, the server-component scope, the labelled
component owner, and nesting).

server-diagnostics.spec.tsx drops its `mount()` workaround for plain JSX;
boundaries are compiled components too, so a boundary's finding now locates
as `<App> › <Errored>` / `<App> › <Loading>` — as on the client. Server,
hydrate and client suites pass under the native compiler and
JSX_COMPILER=babel.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a36b9f6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
solid-js Patch
test-integration Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
@solidjs/universal Patch
@solidjs/web Patch
@solidjs/diagnostics Patch
@solidjs/signals Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34903669436

Coverage remained the same at 71.842%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1007
Covered Lines: 772
Line Coverage: 76.66%
Relevant Branches: 790
Covered Branches: 519
Branch Coverage: 65.7%
Branches in Coverage %: Yes
Coverage Strength: 15.09 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Sep 14, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 10.43%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 159 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
projection derive: write one NESTED field (reference) 2.5 ms 2.8 ms -10.43%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing ssr-component-names (a36b9f6) with next (c452850)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants