From 3e4222388117bf0576891ce195e05672d4d2b96f Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:13:35 +0000 Subject: [PATCH] Bun.FileSystemRouter: expose the assetPrefix getter on the prototype getAssetPrefix has existed since the API was introduced but was never listed in the class definition, so the property was missing at runtime and reads returned undefined despite bun-types declaring it. Report the empty string when no prefix is configured: an omitted assetPrefix and assetPrefix: "" collapse to the same internal state, and the empty string is the prefix the router applies to MatchedRoute.src. --- src/runtime/api/filesystem_router.classes.ts | 4 +++ src/runtime/api/filesystem_router.rs | 11 ++++--- test/js/bun/util/filesystem_router.test.ts | 34 ++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/runtime/api/filesystem_router.classes.ts b/src/runtime/api/filesystem_router.classes.ts index 99ec5c56c467..ac367fecf57f 100644 --- a/src/runtime/api/filesystem_router.classes.ts +++ b/src/runtime/api/filesystem_router.classes.ts @@ -28,6 +28,10 @@ export default [ getter: "getStyle", cache: true, }, + assetPrefix: { + getter: "getAssetPrefix", + cache: true, + }, }, klass: {}, }), diff --git a/src/runtime/api/filesystem_router.rs b/src/runtime/api/filesystem_router.rs index d39d6d12d922..fabda9c5d892 100644 --- a/src/runtime/api/filesystem_router.rs +++ b/src/runtime/api/filesystem_router.rs @@ -698,11 +698,14 @@ impl FileSystemRouter { #[bun_jsc::host_fn(getter)] pub fn get_asset_prefix(this: &Self, global_this: &JSGlobalObject) -> JsResult { - if let Some(ref asset_prefix) = this.asset_prefix { - return Ok(zs_to_js(asset_prefix.leak(), global_this)); - } + // An omitted `assetPrefix` and `assetPrefix: ""` both store no prefix, so report + // the empty string the router actually applies rather than null. + let prefix = match this.asset_prefix { + Some(ref asset_prefix) => asset_prefix.leak(), + None => b"", + }; - Ok(JSValue::NULL) + Ok(zs_to_js(prefix, global_this)) } // Codegen's `host_fn_finalize` calls this via `|b| FileSystemRouter::finalize(b)` diff --git a/test/js/bun/util/filesystem_router.test.ts b/test/js/bun/util/filesystem_router.test.ts index 51248c37db71..851f48754a21 100644 --- a/test/js/bun/util/filesystem_router.test.ts +++ b/test/js/bun/util/filesystem_router.test.ts @@ -314,6 +314,40 @@ it("assetPrefix, src, and origin", async () => { } }); +it("assetPrefix is readable from the router", () => { + const { dir } = make(["index.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + assetPrefix: "/_next/static/", + origin: "https://nextjs.org", + }); + + const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(router), "assetPrefix"); + expect(typeof descriptor?.get).toBe("function"); + expect(descriptor?.set).toBeUndefined(); + expect(Object.hasOwn(router, "assetPrefix")).toBe(false); + + expect(router.assetPrefix).toBe("/_next/static/"); + + // the prefix the router reports is the one it applies to `src` + expect(router.match("/")!.src).toBe(`${router.origin}${router.assetPrefix}index.tsx`); + + router.reload(); + expect(router.assetPrefix).toBe("/_next/static/"); +}); + +it("assetPrefix is an empty string when unset", () => { + const { dir } = make(["index.tsx"]); + + for (const assetPrefix of [undefined, ""]) { + const router = new Bun.FileSystemRouter({ dir, style: "nextjs", assetPrefix }); + expect(router.assetPrefix).toBe(""); + expect(router.match("/")!.src).toBe("index.tsx"); + } +}); + it(".query works", () => { // set up the test const { dir } = make(["posts.tsx"]);