Skip to content

Commit 3010e27

Browse files
authored
fix: add basePath support in mountApp() (#3395)
Fix for mountApp() with the new basePath handling --- Generated PR message: I successfully investigated and implemented a fix for mountApp() to make it compatible with basePath handling. Here's what I found and fixed: The Problem The original mountApp() implementation completely ignored the inner app's basePath. When mounting an app like this: ```ts const innerApp = new App({ basePath: "/api" }) .get("/users", () => new Response("users")); const app = new App() .mountApp("/v1", innerApp); ``` The route would become /v1/users instead of the expected /v1/api/users because the inner app's /api basePath was lost. Root Cause The issue was in the mountApp() method at packages/fresh/src/app.ts:333. The original code only used: ```ts pattern: mergePath(path, cmd.pattern, true) ``` This only considered the mount path (/v1) and the command pattern (/users), but ignored app.config.basePath (/api). The Fix I modified the mountApp() method to properly handle the inner app's basePath: ```ts // Apply the inner app's basePath if it exists let effectivePattern = cmd.pattern; if (app.config.basePath) { effectivePattern = mergePath(app.config.basePath, cmd.pattern, false); } const clone = { ...cmd, pattern: mergePath(path, effectivePattern, true), includeLastSegment: cmd.pattern === "/" || cmd.includeLastSegment, }; ``` The key insight was to only apply the basePath transformation when the inner app actually has a basePath (not empty), which preserves backward compatibility with existing code that doesn't use basePath. Test Coverage I added comprehensive test cases covering: 1. Inner app with basePath: /v1 + /api basePath + /users route = /v1/api/users 2. Main app with basePath + mounted app: /main basePath + /sub mount + /data route = /main/sub/data 3. Both main and inner basePath: /main + /services + /api/v2 + /users = /main/services/api/v2/users Backward Compatibility The fix maintains full backward compatibility: - ✅ All existing mountApp() tests pass - ✅ All existing basePath tests pass - ✅ New functionality works as expected Now the three basePath mechanisms work correctly together: 1. Vite base config → Static asset URLs 2. App basePath option → Route matching (now works with mountApp!) 3. External mounting → Framework integration The solution correctly handles the scenario you described where vite.config.ts, main.ts, and external mounting all work together seamlessly.
1 parent 6466c97 commit 3010e27

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

packages/fresh/src/app.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,15 @@ export class App<State> {
328328
const cmd = app.#commands[i];
329329

330330
if (cmd.type !== CommandType.App && cmd.type !== CommandType.NotFound) {
331+
// Apply the inner app's basePath if it exists
332+
let effectivePattern = cmd.pattern;
333+
if (app.config.basePath) {
334+
effectivePattern = mergePath(app.config.basePath, cmd.pattern, false);
335+
}
336+
331337
const clone = {
332338
...cmd,
333-
pattern: mergePath(path, cmd.pattern, true),
339+
pattern: mergePath(path, effectivePattern, true),
334340
includeLastSegment: cmd.pattern === "/" || cmd.includeLastSegment,
335341
};
336342
this.#commands.push(clone);

packages/fresh/src/app_test.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,3 +777,75 @@ Deno.test("App - ctx.render() without app wrapper or layout", async () => {
777777
const res = await server.get("/");
778778
expect(await res.text()).toContain("<body>index<");
779779
});
780+
781+
Deno.test("App - .mountApp() with basePath", async () => {
782+
const innerApp = new App({ basePath: "/api" })
783+
.get("/users", () => new Response("users"));
784+
785+
const app = new App()
786+
.get("/", () => new Response("home"))
787+
.mountApp("/v1", innerApp);
788+
789+
const server = new FakeServer(app.handler());
790+
791+
let res = await server.get("/");
792+
expect(await res.text()).toEqual("home");
793+
794+
// Fixed behavior: inner app's basePath is now applied
795+
res = await server.get("/v1/api/users");
796+
expect(await res.text()).toEqual("users");
797+
798+
// Routes without the full basePath should not work
799+
res = await server.get("/v1/users");
800+
expect(res.status).toEqual(404);
801+
});
802+
803+
Deno.test("App - .mountApp() with main app basePath", async () => {
804+
const innerApp = new App()
805+
.get("/data", () => new Response("data"));
806+
807+
const app = new App({ basePath: "/main" })
808+
.get("/", () => new Response("home"))
809+
.mountApp("/sub", innerApp);
810+
811+
const server = new FakeServer(app.handler());
812+
813+
let res = await server.get("/main");
814+
expect(await res.text()).toEqual("home");
815+
816+
res = await server.get("/main/sub/data");
817+
expect(await res.text()).toEqual("data");
818+
819+
// Should not work without main basePath
820+
res = await server.get("/sub/data");
821+
expect(res.status).toEqual(404);
822+
});
823+
824+
Deno.test("App - .mountApp() with both main and inner basePath", async () => {
825+
const innerApp = new App({ basePath: "/api/v2" })
826+
.get("/users", () => new Response("users"))
827+
.get("/posts", () => new Response("posts"));
828+
829+
const app = new App({ basePath: "/main" })
830+
.get("/", () => new Response("home"))
831+
.mountApp("/services", innerApp);
832+
833+
const server = new FakeServer(app.handler());
834+
835+
let res = await server.get("/main");
836+
expect(await res.text()).toEqual("home");
837+
838+
// Both basePaths should be applied: main basePath + mount path + inner basePath
839+
res = await server.get("/main/services/api/v2/users");
840+
expect(await res.text()).toEqual("users");
841+
842+
res = await server.get("/main/services/api/v2/posts");
843+
expect(await res.text()).toEqual("posts");
844+
845+
// Partial paths should not work
846+
res = await server.get("/services/api/v2/users");
847+
expect(res.status).toEqual(404);
848+
849+
res = await server.get("/main/services/users");
850+
expect(res.status).toEqual(404);
851+
});

0 commit comments

Comments
 (0)