diff --git a/.changeset/fifty-ways-think.md b/.changeset/fifty-ways-think.md new file mode 100644 index 000000000..ddd84e0c3 --- /dev/null +++ b/.changeset/fifty-ways-think.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Reintroduce flat routing diff --git a/packages/start/config/fs-router.js b/packages/start/config/fs-router.js index efefd87a4..c6e48b76e 100644 --- a/packages/start/config/fs-router.js +++ b/packages/start/config/fs-router.js @@ -1,22 +1,31 @@ import { analyzeModule, BaseFileSystemRouter, cleanPath } from "vinxi/fs-router"; +function toPathBase(src, config) { + const routePath = cleanPath(src, config) + // remove the initial slash + .slice(1) + .replace(/index$/, "") + // replace . with / for flat routes - e.g. foo.bar -> foo/bar + .replace(/\./g, "/") + // converts any splat route ... that got replaced back from /// + // this could be avoided with a lookbehind regex but safar has only supported them since mid 2023 + .replace(/\/\/\//g, "...") + .replace(/\[([^\/]+)\]/g, (_, m) => { + if (m.length > 3 && m.startsWith("...")) { + return `*${m.slice(3)}`; + } + if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { + return `:${m.slice(1, -1)}?`; + } + return `:${m}`; + }); + + return routePath?.length > 0 ? `/${routePath}` : "/"; +} + export class SolidStartClientFileRouter extends BaseFileSystemRouter { toPath(src) { - const routePath = cleanPath(src, this.config) - // remove the initial slash - .slice(1) - .replace(/index$/, "") - .replace(/\[([^\/]+)\]/g, (_, m) => { - if (m.length > 3 && m.startsWith("...")) { - return `*${m.slice(3)}`; - } - if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { - return `:${m.slice(1, -1)}?`; - } - return `:${m}`; - }); - - return routePath?.length > 0 ? `/${routePath}` : "/"; + return toPathBase(src, this.config); } toRoute(src) { @@ -72,21 +81,7 @@ function createHTTPHandlers(src, exports) { export class SolidStartServerFileRouter extends BaseFileSystemRouter { toPath(src) { - const routePath = cleanPath(src, this.config) - // remove the initial slash - .slice(1) - .replace(/index$/, "") - .replace(/\[([^\/]+)\]/g, (_, m) => { - if (m.length > 3 && m.startsWith("...")) { - return `*${m.slice(3)}`; - } - if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { - return `:${m.slice(1, -1)}?`; - } - return `:${m}`; - }); - - return routePath?.length > 0 ? `/${routePath}` : "/"; + return toPathBase(src, this.config); } toRoute(src) { diff --git a/packages/start/src/router/routes.ts b/packages/start/src/router/routes.ts index 0a907dcc9..b010a5a7a 100644 --- a/packages/start/src/router/routes.ts +++ b/packages/start/src/router/routes.ts @@ -33,7 +33,12 @@ function defineRoutes(fileRoutes: Route[]) { }); if (!parentRoute) { - routes.push({ ...route, id, path: id.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "") }); + const path = id + // strip out escape group for escaping nested routes - e.g. foo(bar) -> foo + .replace(/\/\([^)/]+\)/g, "") + .replace(/\([^)/]+\)/g, ""); + + routes.push({ ...route, id, path }); return routes; } processRoute( @@ -70,18 +75,24 @@ function containsHTTP(route: Route) { } const router = createRouter({ - routes: (fileRoutes as unknown as Route[]).reduce((memo, route) => { - if (!containsHTTP(route)) return memo; - let path = route.path.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`); - if (/:[^/]*\?/g.test(path)) { - throw new Error(`Optional parameters are not supported in API routes: ${path}`); - } - if (memo[path]) { - throw new Error( - `Duplicate API routes for "${path}" found at "${memo[path]!.route.path}" and "${route.path}"` - ); - } - memo[path] = { route }; - return memo; - }, {} as Record) + routes: (fileRoutes as unknown as Route[]).reduce( + (memo, route) => { + if (!containsHTTP(route)) return memo; + let path = route.path + .replace(/\/\([^)/]+\)/g, "") + .replace(/\([^)/]+\)/g, "") + .replace(/\*([^/]*)/g, (_, m) => `**:${m}`); + if (/:[^/]*\?/g.test(path)) { + throw new Error(`Optional parameters are not supported in API routes: ${path}`); + } + if (memo[path]) { + throw new Error( + `Duplicate API routes for "${path}" found at "${memo[path]!.route.path}" and "${route.path}"` + ); + } + memo[path] = { route }; + return memo; + }, + {} as Record + ) });