Skip to content

fix(ssr): drop $$moduleUrl from server entry chunk exports - #423

Open
brenelz wants to merge 1 commit into
nextfrom
fix/420-module-url-entry
Open

brenelz wants to merge 1 commit into
nextfrom
fix/420-module-url-entry

Conversation

@brenelz

@brenelz brenelz commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #420.

In SSR builds, injectSsrModuleId appends export const $$moduleUrl to every project module, and since #407 that includes .ts/.js modules. A server entry therefore gains a $$moduleUrl named export. @cloudflare/vite-plugin's worker entry does export * from main, so the export reaches the Worker, and workerd refuses to start with Incorrect type for map entry '$$moduleUrl'.

The SSR build now removes $$moduleUrl from the export clause of entry chunks in renderChunk. It only does this when the chunk's facade is a configured build input (build.lib.entry, a string build.ssr, or rollupOptions/rolldownOptions input, in Vite's order) and no module imports the facade, statically or through import(). Server lazy() reads $$moduleUrl only from modules it reaches through import(), so lazy targets keep the export. Entry chunks that other plugins emit with emitFile also keep it, because a framework may load them at runtime by path. The removed specifier is padded with spaces so the chunk's source map stays valid. Removing the export at render time leaves the transform unchanged, which matters because the transform cannot know whether a module will end up as an entry. With Cloudflare, the user's main is reached only through the plugin's virtual worker entry.

Verified with the issue's setup (vite 8.3.4, @cloudflare/vite-plugin 1.64.0, wrangler 4.151.0, solid-js 2.0.0-rc.15) against a packed tarball of this branch. I ran vite build and vite preview, then curl http://127.0.0.1:5391/:

  • Without the fix, dist/server/server.js ends with export { $$moduleUrl, worker_entry_default as default } and workerd fails to start.
  • With the fix, it ends with export { worker_entry_default as default } and the request returns HTTP 200. This holds for a .ts and a .tsx main, minified and unminified, with an exported Durable Object class, and with output.footer: '// end'.
  • An import.meta.glob lazy target's chunk still ends with export { i as $$moduleUrl, r as default }.

tsc --noEmit passes. There is no unit test harness on next, so this has no automated test.

Still open:

  • Only ESM output is handled. CJS server entries still export $$moduleUrl.
  • Only the chunk's trailing export clause is checked, with comments allowed after it. A footer that contains code would skip the removal.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 56e2b85

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 Oct 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 56e2b85

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