Skip to content

fix(rsc): resolve facade client entry with strictExecutionOrder - #1480

Merged
hi-ogawa merged 10 commits into
vitejs:mainfrom
james-elicx:fix/rsc-facade-client-entry
Oct 5, 2026
Merged

hi-ogawa merged 10 commits into
vitejs:mainfrom
james-elicx:fix/rsc-facade-client-entry

Conversation

@james-elicx

@james-elicx james-elicx commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

With output.strictExecutionOrder: true on the client environment, which codeSplitting.experimentalInlineCommonChunks also turns on by default, every build with at least one client component fails:

[vite-rsc] Client build must have an entry chunk named "index". Use 'customClientEntry' option to disable this requirement.

Rolldown keeps the browser entry's modules out of the index chunk when they're shared with client reference chunks (React alone is enough). index becomes an empty facade that only imports and runs the shared chunk, e.g. import{t as e}from"./entry.browser-….js";e();. The assets-manifest hook finds the entry through collectAssetDeps, which maps module id to chunk. The facade has no moduleIds, so it's never found.

The fix looks up the index entry chunk directly in the bundle and walks its deps with collectAssetDepsInner. For non-facade entries the result is the same as before. For a facade entry, clientEntryDeps now starts at the facade and includes the shared chunk it imports, so modulepreload still covers the entry's code.

I checked the other module-id lookups in this hook. Client reference groups are their own dynamic entries and keep their modules, so they're unaffected.

Tests

New strict-execution-order e2e block in e2e/starter.test.ts: an inline examples/starter fixture with strictExecutionOrder: true on the client environment. It's skipped on Rollup-based Vite (the vite-7 job) the same way e2e/rolldown.test.ts is, since Rollup has no strictExecutionOrder.

  • Runs the full defineStarterTest suite in build mode: hydration, client component, server action with and without JS, CSS, and assets.
  • Checks that the fixture really emits an empty-facade index entry (no moduleIds, at least one import). The fixture records the chunk shape from generateBundle. If a future rolldown stops producing this shape, the test fails instead of silently passing.
  • Checks that clientEntryUrl points at the facade and that clientEntryDeps.js includes the facade and the chunks it imports.

It lives in starter.test.ts rather than its own spec file on purpose. As a separate file it ran on the other worker and could copy examples/starter while dev-default > client hmr had it mid-edit, building a fixture with Client [edit] Counter.

The commits are split to show red then green:

  • test(rsc): … adds only the test. The fixture build fails with the error above.
  • fix(rsc): … makes the test pass: 8 passed, 3 HMR cases skipped in build mode.

e2e/client-entry-url.test.ts still passes.

E2E race fixes

CI on this PR kept failing on existing dev tests that are also flaky on main nightlies, so I tracked two of them down (last commit):

  • useCreateEditor writes are spaced at least 100ms apart per file. Vite's chokidar watcher drops a change event that arrives within 50ms of the previous one for the same path. When the HMR round trip is fast, editor.reset() lands inside that window and Vite never sees it. A CI trace of dev-no-ssr > client hmr showed the reset written about 55ms after the edit, with only one hot updated message. All three attempts got stuck on Client [edit] Counter: 1. A standalone probe against Vite's watcher reproduces the drop with a 20ms gap.
  • use-cache-persistent waits for the rsc:update refetch before page.reload(). Previously the reload could abort the in-flight _.rsc GET, which WebKit reports as an unhandled TypeError: Load failed. The late update could also reach the reloaded page before its effect assigned setPayload, giving setPayload is not a function. Delaying that GET by 300ms reproduces the WebKit failure locally; with the change it passes.
  • setupIsolatedFixture gets 60s instead of the default 30s hook timeout. It runs pnpm i over the network, which took 16-34s on macOS runners. The react-server-dom-webpack fixture installs twice, so its beforeAll regularly timed out, including on main nightlies.

Context

Found while trying rolldown's codeSplitting.experimentalInlineCommonChunks (rolldown/rolldown#11040) in an RSC framework (vinext). The repo's rolldown 1.2.9 reproduces it with strictExecutionOrder alone, so the test doesn't need the experimental option.

@james-elicx
james-elicx force-pushed the fix/rsc-facade-client-entry branch from 1d5d337 to 97ba487 Compare October 2, 2026 12:57
Copying `examples/starter` from a separate spec file could race with the
`starter.test.ts` HMR tests editing it on the other worker, building a
fixture with `Client [edit] Counter`. Colocate it so the copy runs
serially after those edits are reset.
- Space out editor writes to the same file. Vite's chokidar watcher drops
  a `change` event within 50ms of the previous one, so a fast HMR round
  trip could swallow `editor.reset()` (e.g. `dev-no-ssr > client hmr`).
- Wait for the `rsc:update` refetch before reloading in
  `use-cache-persistent`, so the reload neither aborts it (unhandled
  `Load failed` in WebKit) nor receives the late update before hydration
  (`setPayload is not a function`).
@james-elicx
james-elicx force-pushed the fix/rsc-facade-client-entry branch from 21d6ebb to 62fb48a Compare October 2, 2026 15:08
`setupIsolatedFixture` runs `pnpm i` over the network inside a
`beforeAll` hook. On macOS runners installs take 16-34s, and the
`react-server-dom-webpack` fixture installs twice, so the hook kept
hitting the default 30s timeout.
@james-elicx
james-elicx marked this pull request as ready for review October 2, 2026 16:07
@hyfdev

hyfdev commented Oct 3, 2026

Copy link
Copy Markdown

I'm checking if it's possible to support inlineCommonChunks with preserveEntryExportSignature

Return the chunk-level map from `collectAssetDeps` alongside the
module-id map, and find the `index` entry there. This reuses the deps
already computed for every chunk instead of walking the entry again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
hi-ogawa and others added 2 commits October 5, 2026 16:47
The starter suite already fails on the client build without the fix, so
it covers the regression. The removed test asserted rolldown's chunk
shape and the internal assets manifest, which would break on bundler
changes unrelated to the plugin.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Base the fixture on `examples/starter-extra` like other inline
fixtures, so it no longer copies `examples/starter` while starter's dev
tests edit it in place. `rolldown.test.ts` already has the same
rolldown-only skip.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@hi-ogawa hi-ogawa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks good! Thanks for e2e fix too. I'm splitting changes for e2e to just sort out some polish.

…-entry

Take main's e2e/fixture.ts and e2e/use-cache-persistent.test.ts. The e2e
deflake fixes from this PR landed separately in vitejs#1487, with the write
spacing moved to vitejs#1489.
@hi-ogawa
hi-ogawa merged commit 0982f02 into vitejs:main Oct 5, 2026
19 checks passed
@james-elicx
james-elicx deleted the fix/rsc-facade-client-entry branch October 5, 2026 15:05
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.

3 participants