v0.10.0
⚠️ 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/**/bused to behave like/a/**. Now the path must end in/b. If you want the old behavior, register/a/**./a/:x+/bnow 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/**:pdidn'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
regexpand Rustregex. - Captures: an empty required segment at the end (
/a/:xon/a//) leaves the group unset (undefined), while the router returns"". - Duplicate param names (
/files/:path/**:path,/a/:_/**) now throw arou3:error instead of a rawSyntaxError. - 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. regExpToRoutestill 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: truenow drops a..that would go above the root (/x/../../foo→/foo) instead of keeping a literal..segment.
🚀 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
findRoutematches (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
- Pooya Parsa (@pi0)
- Taesu (@bytaesu)
- SeaStar Deng (@DSeaStar)
- Daniel Roe (@danielroe)