From 5d305a669f7276c4ea6f6ee2d90f6fcd1f3db34f Mon Sep 17 00:00:00 2001 From: Zhi Date: Sat, 13 Jun 2026 02:08:11 +0800 Subject: [PATCH 1/3] fix: sort programmatic routes by specificity to prevent shadowing --- packages/fresh/src/router.ts | 16 +++++++++++ packages/fresh/src/router_test.ts | 44 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/packages/fresh/src/router.ts b/packages/fresh/src/router.ts index c0bbfa9293a..0b1ec442436 100644 --- a/packages/fresh/src/router.ts +++ b/packages/fresh/src/router.ts @@ -1,3 +1,5 @@ +import { sortRoutePaths } from "./fs_routes.ts"; + export type Method = | "HEAD" | "GET" @@ -89,6 +91,20 @@ export class UrlPatternRouter implements Router { }; this.#dynamics.set(pathname, def); this.#dynamicArr.push(def); + this.#dynamicArr.sort((a, b) => { + const aPath = a.pattern.pathname; + const bPath = b.pattern.pathname; + // Convert URLPattern format (:param, :param*) to + // filesystem route format ([param], [...param]) so that + // sortRoutePaths can rank by specificity. + const aFs = aPath + .replace(/:(\w+)\*/g, "[...$1]") + .replace(/:(\w+)/g, "[$1]"); + const bFs = bPath + .replace(/:(\w+)\*/g, "[...$1]") + .replace(/:(\w+)/g, "[$1]"); + return sortRoutePaths(aFs, bFs); + }); } byMethod = def.byMethod; diff --git a/packages/fresh/src/router_test.ts b/packages/fresh/src/router_test.ts index 479f2f8439c..4a679e558d1 100644 --- a/packages/fresh/src/router_test.ts +++ b/packages/fresh/src/router_test.ts @@ -319,3 +319,47 @@ Deno.test("UrlPatternRouter - non-standard method on dynamic route", () => { pattern: "/books/:id", }); }); + +Deno.test("UrlPatternRouter - specific route matches before catch-all regardless of registration order", () => { + const router = new UrlPatternRouter<() => string>(); + const catchAll = () => "catch-all"; + const specific = () => "specific"; + + // Register catch-all first, then specific route + router.add("GET", "/blog/:rest*", catchAll); + router.add("GET", "/blog/:id", specific); + + // Specific route should match /blog/123 + const res = router.match("GET", new URL("/blog/123", "http://localhost")); + expect(res.item).toBe(specific); + expect(res.params).toEqual({ id: "123" }); + + // Catch-all should still match paths the specific route doesn't + const res2 = router.match( + "GET", + new URL("/blog/a/b/c", "http://localhost"), + ); + expect(res2.item).toBe(catchAll); + expect(res2.params).toEqual({ rest: "a/b/c" }); +}); + +Deno.test("UrlPatternRouter - multiple dynamic routes sorted by specificity", () => { + const router = new UrlPatternRouter<() => string>(); + const catchAll = () => "catch-all"; + const byId = () => "by-id"; + const byName = () => "by-name"; + + // Register catch-all first, then specific routes + router.add("GET", "/api/:rest*", catchAll); + router.add("GET", "/api/:name", byName); + router.add("GET", "/api/:id", byId); + + // Registration order was: catchAll, byName, byId + // After sorting, more specific routes should match first. + // /api/:id and /api/:name are equally specific (both dynamic), + // so the sort order between them is stable/deterministic. + const res = router.match("GET", new URL("/api/hello", "http://localhost")); + // Should match one of the specific routes, not the catch-all + expect(res.item).not.toBe(catchAll); + expect(res.methodMatch).toBe(true); +}); From 7e5735ce9e46cd1c6954798ef6c6db6ea76e4fed Mon Sep 17 00:00:00 2001 From: Zhi Date: Sat, 13 Jun 2026 02:17:04 +0800 Subject: [PATCH 2/3] fix: correct import statements for @tailwindcss/postcss and @prefresh/vite --- packages/plugin-tailwindcss/src/mod.ts | 2 +- packages/plugin-vite/src/mod.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugin-tailwindcss/src/mod.ts b/packages/plugin-tailwindcss/src/mod.ts index 6db172a80b5..328df4fe714 100644 --- a/packages/plugin-tailwindcss/src/mod.ts +++ b/packages/plugin-tailwindcss/src/mod.ts @@ -1,5 +1,5 @@ import type { Builder } from "fresh/dev"; -import twPostcss from "@tailwindcss/postcss"; +import { postcss as twPostcss } from "@tailwindcss/postcss"; import postcss from "postcss"; import type { TailwindPluginOptions } from "./types.ts"; diff --git a/packages/plugin-vite/src/mod.ts b/packages/plugin-vite/src/mod.ts index c49c244526c..60c88e09f00 100644 --- a/packages/plugin-vite/src/mod.ts +++ b/packages/plugin-vite/src/mod.ts @@ -6,7 +6,7 @@ import { } from "./utils.ts"; import { deno } from "./plugins/deno.ts"; -import prefresh from "@prefresh/vite"; +import { prefresh } from "@prefresh/vite"; import { serverEntryPlugin } from "./plugins/server_entry.ts"; import { clientEntryPlugin } from "./plugins/client_entry.ts"; import { devServer } from "./plugins/dev_server.ts"; @@ -258,7 +258,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] { ...devServer(fConfig), prefresh({ include: [/\.[cm]?[tj]sx?$/], - exclude: [/node_modules/, /[\\/]+deno[\\/]+npm[\\/]+/], + exclude: [/node_modules/, /[\\\/]+deno[\\\/]+npm[\\\/]+/], parserPlugins: [ "importMeta", "explicitResourceManagement", From 3acaeb98faf39c665db613ccfec9f07696a58050 Mon Sep 17 00:00:00 2001 From: Zhi Date: Sat, 13 Jun 2026 02:45:23 +0800 Subject: [PATCH 3/3] fix: revert accidental changes to plugin-vite and plugin-tailwindcss A previous commit accidentally changed \import prefresh from\ to \import { prefresh } from\ and modified regex escaping in the exclude array. Revert both files to match main. --- packages/plugin-tailwindcss/src/mod.ts | 2 +- packages/plugin-vite/src/mod.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugin-tailwindcss/src/mod.ts b/packages/plugin-tailwindcss/src/mod.ts index 328df4fe714..6db172a80b5 100644 --- a/packages/plugin-tailwindcss/src/mod.ts +++ b/packages/plugin-tailwindcss/src/mod.ts @@ -1,5 +1,5 @@ import type { Builder } from "fresh/dev"; -import { postcss as twPostcss } from "@tailwindcss/postcss"; +import twPostcss from "@tailwindcss/postcss"; import postcss from "postcss"; import type { TailwindPluginOptions } from "./types.ts"; diff --git a/packages/plugin-vite/src/mod.ts b/packages/plugin-vite/src/mod.ts index 60c88e09f00..c49c244526c 100644 --- a/packages/plugin-vite/src/mod.ts +++ b/packages/plugin-vite/src/mod.ts @@ -6,7 +6,7 @@ import { } from "./utils.ts"; import { deno } from "./plugins/deno.ts"; -import { prefresh } from "@prefresh/vite"; +import prefresh from "@prefresh/vite"; import { serverEntryPlugin } from "./plugins/server_entry.ts"; import { clientEntryPlugin } from "./plugins/client_entry.ts"; import { devServer } from "./plugins/dev_server.ts"; @@ -258,7 +258,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] { ...devServer(fConfig), prefresh({ include: [/\.[cm]?[tj]sx?$/], - exclude: [/node_modules/, /[\\\/]+deno[\\\/]+npm[\\\/]+/], + exclude: [/node_modules/, /[\\/]+deno[\\/]+npm[\\/]+/], parserPlugins: [ "importMeta", "explicitResourceManagement",