From 7b6b61719c07941eaf1524fcd49b7e721f63ffa3 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 16 May 2023 14:12:59 -0400 Subject: [PATCH 1/3] Allow sibling pathless layout routes --- .changeset/sibling-pathless-routes.md | 5 +++ packages/v1-route-convention/src/lib.ts | 53 +++++++++++++++++++++---- 2 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 .changeset/sibling-pathless-routes.md diff --git a/.changeset/sibling-pathless-routes.md b/.changeset/sibling-pathless-routes.md new file mode 100644 index 0000000..2cec5eb --- /dev/null +++ b/.changeset/sibling-pathless-routes.md @@ -0,0 +1,5 @@ +--- +"@remix-run/v1-route-convention": patch +--- + +Allow sibling pathless layout routes (from https://github.com/remix-run/remix/pull/4421) diff --git a/packages/v1-route-convention/src/lib.ts b/packages/v1-route-convention/src/lib.ts index f935f65..49e367a 100644 --- a/packages/v1-route-convention/src/lib.ts +++ b/packages/v1-route-convention/src/lib.ts @@ -105,15 +105,52 @@ export function createRoutesFromFolders( let isIndexRoute = routeId.endsWith("/index"); let fullPath = createRoutePath(routeId.slice(routesDirectory.length + 1)); let uniqueRouteId = (fullPath || "") + (isIndexRoute ? "?index" : ""); - - if (uniqueRouteId) { + let isPathlessLayoutRoute = + routeId.split("/").pop()?.startsWith("__") === true; + + /** + * We do not try to detect path collisions for pathless layout route + * files because, by definition, they create the potential for route + * collisions _at that level in the tree_. + * + * Consider example where a user may want multiple pathless layout routes + * for different subfolders + * + * routes/ + * account.tsx + * account/ + * __public/ + * login.tsx + * perks.tsx + * __private/ + * orders.tsx + * profile.tsx + * __public.tsx + * __private.tsx + * + * In order to support both a public and private layout for `/account/*` + * URLs, we are creating a mutually exclusive set of URLs beneath 2 + * separate pathless layout routes. In this case, the route paths for + * both account/__public.tsx and account/__private.tsx is the same + * (/account), but we're again not expecting to match at that level. + * + * By only ignoring this check when the final portion of the filename is + * pathless, we will still detect path collisions such as: + * + * routes/parent/__pathless/foo.tsx + * routes/parent/__pathless2/foo.tsx + * + * and + * + * routes/parent/__pathless/index.tsx + * routes/parent/__pathless2/index.tsx + */ + if (uniqueRouteId && !isPathlessLayoutRoute) { if (uniqueRoutes.has(uniqueRouteId)) { throw new Error( - `Path ${JSON.stringify(fullPath)} defined by route ${JSON.stringify( - routeId - )} conflicts with route ${JSON.stringify( - uniqueRoutes.get(uniqueRouteId) - )}` + `Path ${JSON.stringify(fullPath || "/")} defined by route ` + + `${JSON.stringify(routeId)} conflicts with route ` + + `${JSON.stringify(uniqueRoutes.get(uniqueRouteId))}` ); } else { uniqueRoutes.set(uniqueRouteId, routeId); @@ -268,6 +305,8 @@ export function createRoutePath(partialRouteId: string): string | undefined { if (rawSegmentBuffer === "index" && result.endsWith("index")) { result = result.replace(/\/?index$/, ""); + } else { + result = result.replace(/\/$/, ""); } if (rawSegmentBuffer === "index" && result.endsWith("index?")) { From 9cc80868e66cc009f8841273cf6eb7fbec82bf9e Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 22 May 2023 14:48:52 -0400 Subject: [PATCH 2/3] Add tests --- .../blog-collision/routes/blog/__nested.tsx | 0 .../routes/blog/__nested/new.tsx | 0 .../blog-collision/routes/blog/index.tsx | 0 .../fixtures/blog-collision/routes/index.tsx | 0 .../fixtures/route-collisions/root.tsx | 0 .../fixtures/route-collisions/routes/__a.tsx | 0 .../route-collisions/routes/__a/a.tsx | 0 .../route-collisions/routes/__a/index.tsx | 0 .../fixtures/route-collisions/routes/__b.tsx | 0 .../route-collisions/routes/__b/a.tsx | 0 .../sibling-pathless-layout-routes/root.tsx | 0 .../routes/__a.tsx | 0 .../routes/__a/a.tsx | 0 .../routes/__a/index.tsx | 0 .../routes/__b.tsx | 0 .../routes/__b/b.tsx | 0 .../__tests__/index-test.ts | 88 +++++++++++++++++++ 17 files changed, 88 insertions(+) create mode 100644 packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/__nested.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/__nested/new.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/index.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/index.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/route-collisions/root.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a/a.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a/index.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__b.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__b/a.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/root.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a/a.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a/index.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__b.tsx create mode 100644 packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__b/b.tsx diff --git a/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/__nested.tsx b/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/__nested.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/__nested/new.tsx b/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/__nested/new.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/index.tsx b/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/blog/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/index.tsx b/packages/v1-route-convention/__tests__/fixtures/blog-collision/routes/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/route-collisions/root.tsx b/packages/v1-route-convention/__tests__/fixtures/route-collisions/root.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a.tsx b/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a/a.tsx b/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a/a.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a/index.tsx b/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__a/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__b.tsx b/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__b.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__b/a.tsx b/packages/v1-route-convention/__tests__/fixtures/route-collisions/routes/__b/a.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/root.tsx b/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/root.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a.tsx b/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a/a.tsx b/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a/a.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a/index.tsx b/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__a/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__b.tsx b/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__b.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__b/b.tsx b/packages/v1-route-convention/__tests__/fixtures/sibling-pathless-layout-routes/routes/__b/b.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/v1-route-convention/__tests__/index-test.ts b/packages/v1-route-convention/__tests__/index-test.ts index 3d821de..d43bae7 100644 --- a/packages/v1-route-convention/__tests__/index-test.ts +++ b/packages/v1-route-convention/__tests__/index-test.ts @@ -150,4 +150,92 @@ describe("defineConventionalRoutes", () => { expect(routes).toMatchObject(expected); }); + + it("supports sibling pathless layout routes", () => { + let routes = createRoutesFromFolders(defineRoutes, { + appDirectory: path.join( + __dirname, + "fixtures", + "sibling-pathless-layout-routes" + ), + }); + + expect(routes).toEqual({ + "routes/__a": { + file: "routes/__a.tsx", + id: "routes/__a", + parentId: "root", + }, + "routes/__a/a": { + file: "routes/__a/a.tsx", + id: "routes/__a/a", + parentId: "routes/__a", + path: "a", + }, + "routes/__a/index": { + file: "routes/__a/index.tsx", + id: "routes/__a/index", + index: true, + parentId: "routes/__a", + }, + "routes/__b": { + file: "routes/__b.tsx", + id: "routes/__b", + parentId: "root", + }, + "routes/__b/b": { + caseSensitive: undefined, + file: "routes/__b/b.tsx", + id: "routes/__b/b", + index: undefined, + parentId: "routes/__b", + path: "b", + }, + }); + }); + + // See: https://github.com/remix-run/remix/discussions/3014 + it("handles sibling pathless and index routes", () => { + let routes = createRoutesFromFolders(defineRoutes, { + appDirectory: path.join(__dirname, "fixtures", "blog-collision"), + }); + + expect(routes).toEqual({ + "routes/blog/__nested": { + file: "routes/blog/__nested.tsx", + id: "routes/blog/__nested", + parentId: "root", + path: "blog", + }, + "routes/blog/__nested/new": { + file: "routes/blog/__nested/new.tsx", + id: "routes/blog/__nested/new", + parentId: "routes/blog/__nested", + path: "new", + }, + "routes/blog/index": { + file: "routes/blog/index.tsx", + id: "routes/blog/index", + index: true, + parentId: "root", + path: "blog", + }, + "routes/index": { + file: "routes/index.tsx", + id: "routes/index", + index: true, + parentId: "root", + }, + }); + }); + + it("warns on route collisions", () => { + expect(() => + createRoutesFromFolders(defineRoutes, { + appDirectory: path.join(__dirname, "fixtures", "route-collisions"), + }) + ).toThrowErrorMatchingInlineSnapshot( + '"Path \\"a\\" defined by route \\"routes/__b/a\\" conflicts with route \\"routes/__a/a\\""' + ); + }); }); From 3e2e13415b64dabef77bab3ea0166da35cf692eb Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 22 May 2023 15:00:35 -0400 Subject: [PATCH 3/3] WINDOWWWWWWWS --- .../__tests__/index-test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/v1-route-convention/__tests__/index-test.ts b/packages/v1-route-convention/__tests__/index-test.ts index d43bae7..6b4334b 100644 --- a/packages/v1-route-convention/__tests__/index-test.ts +++ b/packages/v1-route-convention/__tests__/index-test.ts @@ -162,30 +162,30 @@ describe("defineConventionalRoutes", () => { expect(routes).toEqual({ "routes/__a": { - file: "routes/__a.tsx", + file: path.join("routes", "__a.tsx"), id: "routes/__a", parentId: "root", }, "routes/__a/a": { - file: "routes/__a/a.tsx", + file: path.join("routes", "__a", "a.tsx"), id: "routes/__a/a", parentId: "routes/__a", path: "a", }, "routes/__a/index": { - file: "routes/__a/index.tsx", + file: path.join("routes", "__a", "index.tsx"), id: "routes/__a/index", index: true, parentId: "routes/__a", }, "routes/__b": { - file: "routes/__b.tsx", + file: path.join("routes", "__b.tsx"), id: "routes/__b", parentId: "root", }, "routes/__b/b": { caseSensitive: undefined, - file: "routes/__b/b.tsx", + file: path.join("routes", "__b", "b.tsx"), id: "routes/__b/b", index: undefined, parentId: "routes/__b", @@ -202,26 +202,26 @@ describe("defineConventionalRoutes", () => { expect(routes).toEqual({ "routes/blog/__nested": { - file: "routes/blog/__nested.tsx", + file: path.join("routes", "blog", "__nested.tsx"), id: "routes/blog/__nested", parentId: "root", path: "blog", }, "routes/blog/__nested/new": { - file: "routes/blog/__nested/new.tsx", + file: path.join("routes", "blog", "__nested", "new.tsx"), id: "routes/blog/__nested/new", parentId: "routes/blog/__nested", path: "new", }, "routes/blog/index": { - file: "routes/blog/index.tsx", + file: path.join("routes", "blog", "index.tsx"), id: "routes/blog/index", index: true, parentId: "root", path: "blog", }, "routes/index": { - file: "routes/index.tsx", + file: path.join("routes", "index.tsx"), id: "routes/index", index: true, parentId: "root",