From 02173178195d9947a5ba28a37b0ffd2c55a3c495 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:13:43 +0000 Subject: [PATCH 1/4] bake: provide every runtime helper from the dev server's bun:wrap module The dev server does not bundle runtime.js. Modules it serves import their lowering helpers from "bun:wrap", which the HMR runtime registers as a synthetic module built from a hand-written list of eight helpers. The list was never updated for standard decorator lowering, so a module containing a TC39 decorator or an accessor field fails to evaluate with "import_bun_wrap.__decoratorStart is not a function" in dev mode while the same source works in bun build and bun run. Register the runtime.bun.js module namespace as "bun:wrap" instead, which is the same module bun run serves for that specifier, so the synthetic module exposes every helper the transpiler can emit without a list to keep in sync. The unused ambient "bun:wrap" declaration that mirrored the old list is removed. --- src/runtime/bake/bake.private.d.ts | 11 ----- src/runtime/bake/hmr-module.ts | 29 ++++--------- test/bake/dev-and-prod.test.ts | 67 +++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/src/runtime/bake/bake.private.d.ts b/src/runtime/bake/bake.private.d.ts index 1c8649f07bbb..ae3d2e1676bf 100644 --- a/src/runtime/bake/bake.private.d.ts +++ b/src/runtime/bake/bake.private.d.ts @@ -144,14 +144,3 @@ declare module "react-dom/server.node" { options: RenderToPipeableStreamOptions, ): PipeableStream; } - -declare module "bun:wrap" { - export const __name: unique symbol; - export const __legacyDecorateClassTS: unique symbol; - export const __legacyDecorateParamTS: unique symbol; - export const __legacyMetadataTS: unique symbol; - export const __using: unique symbol; - export const __callDispose: unique symbol; - export const __MEMO_CACHE_SENTINEL: unique symbol; - export const __EARLY_RETURN_SENTINEL: unique symbol; -} diff --git a/src/runtime/bake/hmr-module.ts b/src/runtime/bake/hmr-module.ts index b61c2c12380e..cb5b01c4efe1 100644 --- a/src/runtime/bake/hmr-module.ts +++ b/src/runtime/bake/hmr-module.ts @@ -6,16 +6,7 @@ // Some build failures from the bundler surface as runtime errors here, such as // `require` on a module with transitive top-level await, or a missing export. // This was done to make incremental updates as isolated as possible. -import { - __callDispose, - __EARLY_RETURN_SENTINEL, - __legacyDecorateClassTS, - __legacyDecorateParamTS, - __legacyMetadataTS, - __MEMO_CACHE_SENTINEL, - __name, - __using, -} from "../../runtime.bun"; +import * as runtimeHelpers from "../../runtime.bun"; // This import is different based on client vs server side. // On the server, remapping is done automatically. import { type SourceMapURL, derefMapping } from "#stack-trace"; @@ -945,17 +936,13 @@ declare global { } // bun:bake/server, bun:bake/client, and bun:wrap are -// provided by this file instead of the bundler -registerSynthetic("bun:wrap", { - __name, - __legacyDecorateClassTS, - __legacyDecorateParamTS, - __legacyMetadataTS, - __using, - __callDispose, - __MEMO_CACHE_SENTINEL, - __EARLY_RETURN_SENTINEL, -}); +// provided by this file instead of the bundler. +// +// Transpiled modules import the helpers their lowered syntax needs (legacy and +// standard decorators, `using`, React Compiler sentinels, ...) from "bun:wrap", +// which `bun build` resolves to runtime.js. Exposing that module as a whole +// keeps this copy complete when helpers are added, instead of listing them here. +registerSynthetic("bun:wrap", runtimeHelpers); if (side === "server") { registerSynthetic("bun:bake/server", { diff --git a/test/bake/dev-and-prod.test.ts b/test/bake/dev-and-prod.test.ts index 26c769669420..162fdf53dc45 100644 --- a/test/bake/dev-and-prod.test.ts +++ b/test/bake/dev-and-prod.test.ts @@ -1,6 +1,6 @@ // Tests which apply to both dev and prod. They are run twice. import { writeFileSync } from "node:fs"; -import { devAndProductionTest, devTest, emptyHtmlFile, WAIT_MULTIPLIER } from "./bake-harness"; +import { devAndProductionTest, devTest, emptyHtmlFile, minimalFramework, WAIT_MULTIPLIER } from "./bake-harness"; const hmrSelfAcceptingModule = (label: string) => ` console.log(${JSON.stringify(label)}); @@ -202,6 +202,71 @@ devTest("using runtime import", { ); }, }); +// Standard (TC39) decorators, `accessor` fields, and the `#private` members of a +// decorated class are lowered into calls to helpers imported from "bun:wrap" +// (__decoratorStart, __decorateElement, __runInitializers, __decoratorMetadata, +// __privateAdd, __privateGet, __privateSet, __privateIn, __privateMethod). The +// dev server does not bundle runtime.js; its HMR runtime provides "bun:wrap" +// itself, so it has to export everything the production runtime does. This +// class imports all nine helpers. +const standardDecoratorsSource = ` + const applied: string[] = []; + function dec(_value: unknown, ctx: DecoratorContext) { + applied.push(ctx.kind + ":" + String(ctx.name)); + } + @dec + class Foo { + @dec static s = 1; + @dec #p = 2; + @dec accessor x = 3; + @dec m() {} + @dec get g() { return this.#p; } + @dec #pm() { return 5; } + accessor y = 4; + has(o: object) { return #p in o; } + pm() { return this.#pm(); } + } + const foo = new Foo(); + foo.x += 10; + foo.y += 10; + export const decorated = applied.sort().join(","); + export const values = [Foo.s, foo.x, foo.y, foo.g, foo.pm(), foo.has(foo), foo.has({})].join(","); +`; +const expectedDecorated = "accessor:x,class:Foo,field:#p,field:s,getter:g,method:#pm,method:m"; +const expectedValues = "1,13,14,2,5,true,false"; +devAndProductionTest("standard decorators runtime import", { + files: { + "index.html": emptyHtmlFile({ + styles: [], + scripts: ["index.ts"], + }), + "index.ts": ` + import { decorated, values } from "./decorated"; + console.log(decorated); + console.log(values); + `, + "decorated.ts": standardDecoratorsSource, + }, + async test(dev) { + await using c = await dev.client("/"); + await c.expectMessage(expectedDecorated, expectedValues); + }, +}); +devTest("standard decorators runtime import on the server", { + framework: minimalFramework, + files: { + "decorated.ts": standardDecoratorsSource, + "routes/index.ts": ` + import { decorated, values } from "../decorated"; + export default function (req, meta) { + return new Response(decorated + "\\n" + values); + } + `, + }, + async test(dev) { + await dev.fetch("/").equals(expectedDecorated + "\n" + expectedValues); + }, +}); devTest("hmr handles rapid consecutive edits", { files: { "index.html": emptyHtmlFile({ From e7629229af8c92c36c5d74fcf81e74108bc98cd0 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:24:28 +0000 Subject: [PATCH 2/4] bake: shorten the bun:wrap registration comment --- src/runtime/bake/hmr-module.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/runtime/bake/hmr-module.ts b/src/runtime/bake/hmr-module.ts index cb5b01c4efe1..fe1ee7a39342 100644 --- a/src/runtime/bake/hmr-module.ts +++ b/src/runtime/bake/hmr-module.ts @@ -935,13 +935,9 @@ declare global { } } -// bun:bake/server, bun:bake/client, and bun:wrap are -// provided by this file instead of the bundler. -// -// Transpiled modules import the helpers their lowered syntax needs (legacy and -// standard decorators, `using`, React Compiler sentinels, ...) from "bun:wrap", -// which `bun build` resolves to runtime.js. Exposing that module as a whole -// keeps this copy complete when helpers are added, instead of listing them here. +// bun:bake/server, bun:bake/client, and bun:wrap are provided by this file +// instead of the bundler. Transpiled output imports whichever lowering helpers +// it uses from bun:wrap, so it gets all of runtime.bun.js, like `bun run`. registerSynthetic("bun:wrap", runtimeHelpers); if (side === "server") { From d2d047c4cdd41e0114c939efe198a9b5583f906f Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:26:21 +0000 Subject: [PATCH 3/4] bake: keep the original comment on the bun:wrap registration --- src/runtime/bake/hmr-module.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/runtime/bake/hmr-module.ts b/src/runtime/bake/hmr-module.ts index fe1ee7a39342..4395bb1059b4 100644 --- a/src/runtime/bake/hmr-module.ts +++ b/src/runtime/bake/hmr-module.ts @@ -935,9 +935,8 @@ declare global { } } -// bun:bake/server, bun:bake/client, and bun:wrap are provided by this file -// instead of the bundler. Transpiled output imports whichever lowering helpers -// it uses from bun:wrap, so it gets all of runtime.bun.js, like `bun run`. +// bun:bake/server, bun:bake/client, and bun:wrap are +// provided by this file instead of the bundler registerSynthetic("bun:wrap", runtimeHelpers); if (side === "server") { From 8d5d46d5fffe6afd28b08ac3287a2a36f5ede07c Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 06:23:27 +0000 Subject: [PATCH 4/4] ci: retrigger