Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

[3.0.0-next.50 / next] SSR build: the $$moduleUrl export added to a Worker main module stops workerd from starting

オープン
#420 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@brenelz がすでに取り組んでいます。

2026年10月16日 から。

  • #423 @brenelz による — オープン

評価

難易度
3/5
見積もり時間
半日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
typescript

調査の方向性

The export is appended by injectSsrModuleId in src/index.ts, which currently adds it to every SSR module outside node_modules. Start there and check the condition that decides which modules get $$moduleUrl, then run vite build and inspect the last line of dist/server/server.js for the example worker. Done means a server entry keeps only its original exports while lazy()-reached modules still receive the export, and the worker still starts under vite preview.

索引モデルが issue の本文から書いたものです。

説明

Summary

In SSR builds, the plugin appends export const $$moduleUrl = "<path>" to project modules. Since #407 (3.0.0-next.49) it does so for plain .ts/.js modules too; .tsx modules already had it. When the module that gets it is a server entry, the export becomes part of the entry's public surface.

With @cloudflare/vite-plugin, which the README names as a host the plugin composes with, the Worker's main module is re-exported as a whole by the Worker entry (export * from "<main>"). workerd accepts only handlers and entrypoint classes as named exports of a Worker. So the production Worker can't start:

Uncaught TypeError: Incorrect type for map entry '$$moduleUrl': the provided value is not of type 'function or ExportedHandler'.

vite dev is not affected. Expected: a server entry keeps only the exports it was written with.

Reproduction

Versions:

  • @solidjs/vite-plugin 3.0.0-next.48 / 49 / 50, with start: true, ssr: true;
  • @cloudflare/vite-plugin 1.64.0 and wrangler 4.151.0;
  • Vite 8.3.4;
  • solid-js / @solidjs/web 2.0.0-rc.15;
  • Node 24.21.0, Linux.
// vite.config.ts
import { cloudflare } from "@cloudflare/vite-plugin";
import solid from "@solidjs/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [cloudflare({ viteEnvironment: { name: "ssr" } }), solid({ start: true, ssr: true })],
  server: { host: "127.0.0.1", port: 5391, strictPort: true },
});
// wrangler.jsonc
{
  "name": "module-url-probe",
  "main": "src/worker.ts",
  "compatibility_date": "2026-09-19",
  "compatibility_flags": ["nodejs_compat"]
}
// src/worker.ts
export { default } from "virtual:solid-ssr-handler";
// src/App.tsx
export default function App() {
  return <p>Hello</p>;
}

Run vite build, then vite preview --port 5391, then curl http://127.0.0.1:5391/. The second column is the last line of dist/server/server.js.

@solidjs/vite-plugin main dist/server/server.js exports vite preview
3.0.0-next.48 src/worker.ts export { worker_entry_default as default } starts, HTTP 200
3.0.0-next.48 src/worker.tsx (same content) export { $$moduleUrl, worker_entry_default as default } workerd fails to start (error above)
3.0.0-next.49 src/worker.ts export { $$moduleUrl, worker_entry_default as default } workerd fails to start
3.0.0-next.49 src/worker.tsx same workerd fails to start
3.0.0-next.50 src/worker.ts same workerd fails to start
3.0.0-next.50 src/worker.tsx same workerd fails to start

With 3.0.0-next.50, vite dev renders the page (<p _hk=10>Hello</p>).

Cause

injectSsrModuleId in src/index.ts; the code is unchanged on next. It appends the export to every SSR module outside node_modules:

function injectSsrModuleId(code, id, isSsr) {
  if (!isSsr || /node_modules/.test(id) || code.includes('$$moduleUrl')) return code;
  // ...
  return code + `\nexport const $$moduleUrl = ${JSON.stringify(relativeId)};\n`;
}

#407 also runs it on non-JSX modules, which is why a .ts Worker main breaks from next.49 on.

@cloudflare/vite-plugin's Worker entry module (virtual:cloudflare/worker-entry) re-exports the user's main:

import * as mod from "virtual:cloudflare/user-entry";
export * from "virtual:cloudflare/user-entry";
export default mod.default ?? {};

That way, Durable Object classes and other entrypoints written in main reach the Worker. In the build output, the added $$moduleUrl comes along as a named export of the Worker. workerd rejects it, because it is neither a handler nor an entrypoint class.

The only reader of $$moduleUrl is server-side lazy(). It reads it off a module that it resolved through import(), when its callsite has no static module URL (cur.mod?.$$moduleUrl in solid-js's server build). A Worker's main module is never such a target. So the export there serves no purpose, and it changes the entry's public surface.

Expected

A server entry module, and anything a host re-exports from it, keeps only the exports it was written with. $$moduleUrl is only needed on modules that lazy() reaches through import(). Related: #406 / #407 (where .ts/.js modules started getting the export).

(submitted by Claude Opus 5.5 on behalf of rvlzzr)

主要言語
TypeScript
スター
522
フォーク
73
平均マージ
1日 4時間
マージ済み PR(30日)
35

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

solidjs/solid-vite-plugin のほかの issue

solidjs/solid-vite-plugin の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。