Skip to content

fix: keep the SSR runner on fresh code after the style walk re-transforms - #538

Open
brenelz wants to merge 1 commit into
nextfrom
fix/537-style-walk-stale-runner
Open

brenelz wants to merge 1 commit into
nextfrom
fix/537-style-walk-stale-runner

Conversation

@brenelz

@brenelz brenelz commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #537.

In start mode dev, after an edit to a module that a lazy route imports, the server render kept serving the old module until the dev server restarted. This happened with runners that rely on fetchModule's cached handshake instead of a full reload, such as workerd under @cloudflare/vite-plugin.

Root cause

The dev asset resolver's style walk transforms SSR modules to read their imports, which stores transformResult on the module graph. Vite's fetchModule answers a runner's cached: true request with { cache: true } whenever a transformResult exists. When the walk re-transformed an invalidated module before the runner imported it again, the runner was told to keep its old copy.

Change

src/dev-manifest.ts now wraps fetchModule on each server environment when the resolver is created. For every runner fetch that returns code, the wrapper records the module's invalidation timestamp. When a later cached request gets { cache: true } but the module has been invalidated since the runner received its code, the wrapper sends fresh code with invalidate: true. The walk calls the unwrapped fetchModule, so its transforms no longer change what the runner is told.

I did not use the approaches discussed in the issue:

  • Clearing the walk's transformResult after the walk races the runner's own imports and evaluates a module twice. The new race test fails with that change in all three runs I made.
  • Comparing the transform result object the runner received with the current one also double-evaluates, because the walk requests a module by absolute path and the runner by /page.js. Vite does not dedupe those two requests, so the second result replaces the first. The wrapper compares invalidation timestamps instead, and treats code equal to the stored result as current.

Every runner fetch now does one module-graph lookup first. For a URL not yet in the graph, that includes one extra resolveId call. A module the runner fetched before the wrapper was installed keeps Vite's normal answer.

Verification

  • src/dev-manifest.test.ts uses createServerModuleRunner(env, { hmr: false }) to stand in for workerd, with a slow transform to widen the race window. It covers CSS inlining, the stale module after an edit, and single evaluation when the walk races the first render and a render after an edit. The stale test fails on next.
  • pnpm test:unit: 152 tests pass. tsc --noEmit and the build are clean.
  • examples/start-ssr: node test/run.mjs (783 assertions, with CHROME_BIN set to Playwright's Chrome for Testing) and the http-bridge, dedupe, style-patch and ssr-inline scripts pass.

Open

  • I have not run the issue's Cloudflare repro. CloudflareDevEnvironment overrides fetchModule on its prototype and uses DevEnvironment's invoke handler, so the instance wrapper sits in front of it, but this has not been run in workerd.
  • The walk still writes transformResult to the shared server graph. A walk that does not touch the runner's state, such as one using a separate non-runnable environment, would remove the hazard without wrapping fetchModule.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Nov 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e003085

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

This PR includes changesets to release 1 package
Name Type
@solidjs/vite-plugin 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

@pkg-pr-new

pkg-pr-new Bot commented Nov 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/vite-plugin@538

commit: e003085

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.

1 participant