Skip to content

v0.10.0

Choose a tag to compare

@pi0 pi0 released this 28 Sep 22:13
· 49 commits to main since this release

⚠️ Migration guide

Most apps can upgrade with no changes. Check the sections below that apply to how you use rou3.

Only one trailing slash is ignored (#210)

Lookup now ignores at most one trailing slash, the same as the non-strict defaults of Express, find-my-way and Hono. Registering routes works as before.

Path v0.9 v0.10
/users/123/ ✅ { id: "123" } ✅ { id: "123" }
/users/123//, /about// ✅ ❌ no match
// (root /) ✅ ❌ no match
/users// on /users/:id ❌ ✅ { id: "" }

Migrate: if you need to accept repeated trailing slashes, collapse them before lookup, e.g. path.replace(/\/{2,}$/, "/").

** is no longer terminal (#216)

Segments after ** used to be silently dropped. They are now matched from the end of the path:

addRoute(router, "GET", "/**/_payload.json", payload);
findRoute(router, "GET", "/blog/post/_payload.json"); // params: { _: "blog/post" }

addRoute(router, "GET", "/**.md", doc); // shorthand for `/**/*.md`
  • /a/**/b used to behave like /a/**. Now the path must end in /b. If you want the old behavior, register /a/**.
  • /a/:x+/b now requires the trailing /b (URLPattern semantics).
  • A route can have only one **. /**/** and /a/:x+/b/:y+ now throw.
  • removeRoute(router, "GET", "/a/**/b") no longer removes /a/**.
  • On paths that a route with segments after ** matches, matches are ranked from the last segment backwards (literal > regex param > param > **). Routers without such routes behave as before.

removeRoute removes only the route you pass (#202)

Previously, removing a route also removed every other route on the same node and method. For example, removing /a/:id also removed /a/:userId, and removing /a/** also removed /a/**:rest. Now only the given pattern is removed.

Migrate: if your code relied on this, call removeRoute once for each pattern you registered.

Types: optional params may be undefined (#198)

InferRouteParams now types optional params (:id?, :id*) and a trailing bare * (which can match zero segments) as string | undefined. It also strips constraints and modifiers from param names (:id(\d+) → id).

Migrate: handle undefined where you assumed a string, e.g. params.id ?? "".

routeToRegExp output changed (#211, #204, #217)

The regex now matches exactly the paths findRoute matches. If you use it as a guard or for scope checks, you should upgrade:

  • /api/** no longer matches /apifoo.
  • Catch-alls now match line terminators the way the router does. In v0.9, /admin/**:p didn't match /admin/x\ry, even though the router routed that path.
  • Segments after ** are kept (see above).

Other changes:

  • Regex text changed for almost every route. Update any snapshots or string comparisons. Most routes no longer use look-behind, so the regexes now also compile in RE2, Go regexp and Rust regex.
  • Captures: an empty required segment at the end (/a/:x on /a//) leaves the group unset (undefined), while the router returns "".
  • Duplicate param names (/files/:path/**:path, /a/:_/**) now throw a rou3: error instead of a raw SyntaxError.
  • Node 22: an optional group followed by more of the route (/files/:name{.:ext}?, /users{/:id}?/posts/:post) no longer emits duplicate group names, so it compiles on Node 22.
  • regExpToRoute still reads regexes from older versions. Some routes come back in an equivalent form, e.g. /a/:x?/:y? → /a{/:x/:y?}?.

No action needed

  • normalize: true now drops a .. that would go above the root (/x/../../foo → /foo) instead of keeping a literal .. segment.

compare changes

🚀 Enhancements

  • regexp: ⚠️ Emit RC2-compatible trailing-slash suffix where possible (#211)
  • router: ⚠️ Support segments after wildcards (#216)

🩹 Fixes

  • Drop excess .. segments in path normalization (6ebebb6)
  • regexp: Stop emitting segments after a terminal catch-all (#204)
  • regexp: Anchor catch-all separator to its prefix (eea9453)
  • remove: Splice one same-node sibling instead of the whole method bucket (#202)
  • regexp: Match exactly the paths findRoute matches (6368bf5)
  • ⚠️ Ignore at most one trailing slash in lookup (#210)
  • regexp: Inline an optional group before more of the route (#217)

🌊 Types

  • Allow undefined for trailing wildcard params (#198)

❤️ Contributors