Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@
- nowells
- Nurai1
- Obi-Dann
- okalil
- OlegDev1
- omahs
- omar-moquete
Expand Down
104 changes: 104 additions & 0 deletions packages/react-router-fs-routes/__tests__/nestedRoutes-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import path from "node:path";
import { nestedRoutes, createRoutePath } from "../nestedRoutes";

describe("nestedRoutes", () => {
describe("creates proper route paths", () => {
let tests: [string, string | undefined][] = [
["routes/$", "routes/*"],
["routes/sub/$", "routes/sub/*"],
["routes.sub/$", "routes/sub/*"],
["routes/$slug", "routes/:slug"],
["routes/sub/$slug", "routes/sub/:slug"],
["routes.sub/$slug", "routes/sub/:slug"],
["$", "*"],
["nested/$", "nested/*"],
["flat.$", "flat/*"],
["$slug", ":slug"],
["nested/$slug", "nested/:slug"],
["flat.$slug", "flat/:slug"],
["flat.sub", "flat/sub"],
["nested/index", "nested"],
["flat.index", "flat"],
["index", undefined],
["__layout/index", undefined],
["__layout/test", "test"],
["__layout.test", "test"],
["__layout/$slug", ":slug"],
["nested/__layout/$slug", "nested/:slug"],
["$slug[.]json", ":slug.json"],
["sub/[sitemap.xml]", "sub/sitemap.xml"],
["posts/$slug/[image.jpg]", "posts/:slug/image.jpg"],
["$[$dollabills].[.]lol[/]what/[$].$", ":$dollabills/.lol/what/$/*"],
["sub.[[]", "sub/["],
["sub.]", "sub/]"],
["sub.[[]]", "sub/[]"],
["sub.[[]", "sub/["],
["beef]", "beef]"],
["[index]", "index"],
["test/inde[x]", "test/index"],
["[i]ndex/[[].[[]]", "index/[/[]"],

// Optional segment routes
["(routes)/$", "routes?/*"],
["(routes)/(sub)/$", "routes?/sub?/*"],
["(routes).(sub)/$", "routes?/sub?/*"],
["(routes)/($slug)", "routes?/:slug?"],
["(routes)/sub/($slug)", "routes?/sub/:slug?"],
["(routes).sub/($slug)", "routes?/sub/:slug?"],
["(nested)/$", "nested?/*"],
["(flat).$", "flat?/*"],
["($slug)", ":slug?"],
["(nested)/($slug)", "nested?/:slug?"],
["(flat).($slug)", "flat?/:slug?"],
["flat.(sub)", "flat/sub?"],
["__layout/(test)", "test?"],
["__layout.(test)", "test?"],
["__layout/($slug)", ":slug?"],
["(nested)/__layout/($slug)", "nested?/:slug?"],
["($slug[.]json)", ":slug.json?"],
["(sub)/([sitemap.xml])", "sub?/sitemap.xml?"],
["(sub)/[(sitemap.xml)]", "sub?/(sitemap.xml)"],
["(posts)/($slug)/([image.jpg])", "posts?/:slug?/image.jpg?"],
[
"($[$dollabills]).([.]lol)[/](what)/([$]).$",
":$dollabills?/.lol)/(what?/$?/*",
],
[
"($[$dollabills]).([.]lol)/(what)/([$]).($up)",
":$dollabills?/.lol?/what?/$?/:up?",
],
["(sub).([[])", "sub?/[?"],
["(sub).(])", "sub?/]?"],
["(sub).([[]])", "sub?/[]?"],
["(sub).([[])", "sub?/[?"],
["(beef])", "beef]?"],
["([index])", "index?"],
["(test)/(inde[x])", "test?/index?"],
["([i]ndex)/([[]).([[]])", "index?/[?/[]?"],
];

for (let [input, expected] of tests) {
it(`"${input}" -> "${expected}"`, () => {
expect(createRoutePath(input)).toBe(expected);
});
}

describe("optional segments", () => {
it("will only work when starting and ending a segment with parenthesis", () => {
let [input, expected] = ["(routes.sub)/$", "(routes/sub)/*"];
expect(createRoutePath(input)).toBe(expected);
});

it("throws error on optional to splat routes", () => {
expect(() => createRoutePath("(routes)/($)")).toThrow("Splat");
expect(() => createRoutePath("($)")).toThrow("Splat");
});

it("throws errors on optional index without brackets routes", () => {
expect(() => createRoutePath("(nested)/(index)")).toThrow("index");
expect(() => createRoutePath("(flat).(index)")).toThrow("index");
expect(() => createRoutePath("(index)")).toThrow("index");
});
});
});
});
45 changes: 32 additions & 13 deletions packages/react-router-fs-routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,31 @@ import {

import { routeManifestToRouteConfig } from "./manifest";
import { flatRoutes as flatRoutesImpl } from "./flatRoutes";
import { nestedRoutes as nestedRoutesImpl } from "./nestedRoutes";
import { normalizeSlashes } from "./normalizeSlashes";

interface FileSystemRoutesOptions {
/**
* An array of [minimatch](https://www.npmjs.com/package/minimatch) globs that match files to ignore.
* Defaults to `[]`.
*/
ignoredRouteFiles?: string[];

/**
* The directory containing file system routes, relative to the app directory.
* Defaults to `"./routes"`.
*/
rootDirectory?: string;
}

/**
* Creates route config from the file system using a convention that matches
* [Remix v2's route file
* naming](https://remix.run/docs/en/v2/file-conventions/routes-files), for use
* within `routes.ts`.
*/
export async function flatRoutes(
options: {
/**
* An array of [minimatch](https://www.npmjs.com/package/minimatch) globs that match files to ignore.
* Defaults to `[]`.
*/
ignoredRouteFiles?: string[];

/**
* The directory containing file system routes, relative to the app directory.
* Defaults to `"./routes"`.
*/
rootDirectory?: string;
} = {}
options: FileSystemRoutesOptions = {}
): Promise<RouteConfigEntry[]> {
let { ignoredRouteFiles = [], rootDirectory: userRootDirectory = "routes" } =
options;
Expand All @@ -43,3 +46,19 @@ export async function flatRoutes(

return routeManifestToRouteConfig(routes);
}

/**
* Creates route config from the file system using a convention that matches
* [Remix v1's route file
* naming](https://remix.run/docs/en/v1/file-conventions/routes-files), for use
* within `routes.ts`.
*/
export async function nestedRoutes(
options: FileSystemRoutesOptions = {}
): Promise<RouteConfigEntry[]> {
let { ignoredRouteFiles = [], rootDirectory: userRootDirectory = "routes" } =
options;
let appDirectory = getAppDirectory();
let rootDirectory = path.resolve(appDirectory, userRootDirectory);
return nestedRoutesImpl(appDirectory, ignoredRouteFiles, rootDirectory);
}
Loading