From e5f7d6b40420995d068a10acdb27d03f8ed192d2 Mon Sep 17 00:00:00 2001 From: JamesbbBriz Date: Sat, 12 Sep 2026 01:09:17 +1000 Subject: [PATCH 1/2] fix(server): fall back to a passthrough when console.createTask is inert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit workerd's console exposes createTask but throws "not implemented" when it is called, while React's development builds call it at module initialization and during rendering (scheduler task tracing). The combination crashes every server environment on Workers: the RSC environment dies at module init and the SSR environment dies again during HTML rendering. installServerGlobals() — imported first by every generated server entry across environments — now probes the runtime implementation once and replaces it with a synchronous passthrough when it cannot execute. A working implementation (Node's task tracer) is left untouched, and runtimes without createTask are not modified. --- packages/vinext/src/server/server-globals.ts | 40 +++++++++++++ tests/edge-globals.test.ts | 59 +++++++++++++++++++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/packages/vinext/src/server/server-globals.ts b/packages/vinext/src/server/server-globals.ts index 6681f62d3..72f7a9b28 100644 --- a/packages/vinext/src/server/server-globals.ts +++ b/packages/vinext/src/server/server-globals.ts @@ -15,6 +15,12 @@ const PHASE_PRODUCTION_BUILD = "phase-production-build"; type BrowserGlobalName = "window" | "document"; +type ConsoleTaskLike = { name: string; run: (fn: () => T) => T }; + +type ConsoleWithCreateTask = typeof console & { + createTask?: unknown; +}; + function clearBrowserGlobal(name: BrowserGlobalName): void { const descriptor = Object.getOwnPropertyDescriptor(globalThis, name); @@ -40,6 +46,38 @@ function clearBrowserGlobal(name: BrowserGlobalName): void { } } +/** + * Fall back to a synchronous passthrough when `console.createTask` is inert. + * + * workerd's console exposes `createTask` but throws "not implemented" when it + * is called, and React's development builds call it at module initialization + * and during rendering (scheduler task tracing). The combination crashes every + * server environment on Workers, so probe the runtime implementation once and + * replace it with a passthrough when it cannot execute. A working + * implementation (Node's task tracer) is left untouched. + */ +function installCreateTaskFallback(): void { + const existing = (console as ConsoleWithCreateTask).createTask; + if (typeof existing !== "function") return; + + let usable = true; + try { + const probe = (existing as (name: string) => { run: (fn: () => void) => void })( + "vinext:createTask-probe", + ); + probe.run(() => {}); + } catch { + usable = false; + } + + if (!usable) { + (console as ConsoleWithCreateTask).createTask = (name: string): ConsoleTaskLike => ({ + name, + run: (fn) => fn(), + }); + } +} + export function installServerGlobals(): void { clearBrowserGlobal("window"); clearBrowserGlobal("document"); @@ -55,6 +93,8 @@ export function installServerGlobals(): void { }); } + installCreateTaskFallback(); + const nextPhaseDescriptor = Object.getOwnPropertyDescriptor(globalThis, "__VINEXT_NEXT_PHASE"); if (!nextPhaseDescriptor || nextPhaseDescriptor.configurable) { Object.defineProperty(globalThis, "__VINEXT_NEXT_PHASE", { diff --git a/tests/edge-globals.test.ts b/tests/edge-globals.test.ts index befd2c9dc..ff3444bfe 100644 --- a/tests/edge-globals.test.ts +++ b/tests/edge-globals.test.ts @@ -10,7 +10,7 @@ * that do `new AsyncLocalStorage()` without an import fail with * ReferenceError: AsyncLocalStorage is not defined. */ -import { describe, expect, it } from "vite-plus/test"; +import { afterEach, describe, expect, it } from "vite-plus/test"; import { AsyncLocalStorage as NodeAsyncLocalStorage } from "node:async_hooks"; import { installServerGlobals } from "../packages/vinext/src/server/server-globals.js"; @@ -165,3 +165,60 @@ describe("edge runtime globals", () => { } }); }); + +type TaskLike = { name: string; run: (fn: () => T) => T }; + +describe("console.createTask fallback", () => { + const originalDescriptor = Object.getOwnPropertyDescriptor(console, "createTask"); + + afterEach(() => { + if (originalDescriptor) { + Object.defineProperty(console, "createTask", originalDescriptor); + } + }); + + it("replaces a createTask implementation that throws when called", () => { + // workerd exposes console.createTask but throws "not implemented" on call. + Object.defineProperty(console, "createTask", { + configurable: true, + writable: true, + value: () => { + throw new Error("not implemented"); + }, + }); + + installServerGlobals(); + + const createTask = (console as unknown as { createTask: (name: string) => TaskLike }) + .createTask; + const task = createTask("render"); + expect(task.name).toBe("render"); + expect(task.run(() => 42)).toBe(42); + }); + + it("leaves a working createTask implementation untouched", () => { + const working = (name: string): TaskLike => ({ name, run: (fn) => fn() }); + Object.defineProperty(console, "createTask", { + configurable: true, + writable: true, + value: working, + }); + + installServerGlobals(); + + expect((console as { createTask: unknown }).createTask).toBe(working); + }); + + it("does not add createTask when the runtime does not expose it", () => { + const consoleWithoutCreateTask = console as unknown as Record; + const saved = consoleWithoutCreateTask["createTask"]; + delete consoleWithoutCreateTask["createTask"]; + + try { + installServerGlobals(); + expect("createTask" in (console as object)).toBe(false); + } finally { + consoleWithoutCreateTask["createTask"] = saved; + } + }); +}); From ae4c20a0a35e0a5bda2b1a759f0185002a84b9fc Mon Sep 17 00:00:00 2001 From: JamesbbBriz Date: Sat, 12 Sep 2026 02:43:31 +1000 Subject: [PATCH 2/2] refactor: install the createTask fallback directly in the catch The intermediate `usable` flag and the inline task-shape cast duplicate what the catch branch already knows. Probe, and install on failure. --- packages/vinext/src/server/server-globals.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/vinext/src/server/server-globals.ts b/packages/vinext/src/server/server-globals.ts index 72f7a9b28..7c5cbc5e7 100644 --- a/packages/vinext/src/server/server-globals.ts +++ b/packages/vinext/src/server/server-globals.ts @@ -60,17 +60,9 @@ function installCreateTaskFallback(): void { const existing = (console as ConsoleWithCreateTask).createTask; if (typeof existing !== "function") return; - let usable = true; try { - const probe = (existing as (name: string) => { run: (fn: () => void) => void })( - "vinext:createTask-probe", - ); - probe.run(() => {}); + (existing as (name: string) => ConsoleTaskLike)("vinext:createTask-probe").run(() => {}); } catch { - usable = false; - } - - if (!usable) { (console as ConsoleWithCreateTask).createTask = (name: string): ConsoleTaskLike => ({ name, run: (fn) => fn(),