Hacktoberfest 2026:维护者为十月标记出来的 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 个 reaction 已指派 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 小时
30 天内合并 PR
35

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

solidjs/solid-vite-plugin 的其他 Issue

查看 solidjs/solid-vite-plugin 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。