Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions .changeset/spicy-camels-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

Optimize href() to avoid backtracking regex on splat
24 changes: 22 additions & 2 deletions packages/react-router/lib/href.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ export function href<Path extends keyof Args>(
...args: Args[Path]
): string {
let params = args[0];
let result = path
.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
let result = trimEndSplat(path) // Ignore trailing / and /*, we'll handle it below
.replace(
/\/:([\w-]+)(\?)?/g, // same regex as in .\router\utils.ts: compilePath().
(_: string, param: string, questionMark: string | undefined) => {
Expand All @@ -54,3 +53,24 @@ export function href<Path extends keyof Args>(

return result || "/";
}

/**
Removes a trailing splat and any number of slashes from the end of the path.

Benchmarks as running faster than `path.replace(/\/*\*?$/, "")`, which backtracks.
*/
function trimEndSplat(path: string): string {
let i = path.length - 1;
let char = path[i];
if (char !== "*" && char !== "/") {
return path;
}
i--;
for (; i >= 0; i--) {
// for/break benchmarks faster than do/while
if (path[i] !== "/") {
break;
}
}
return path.slice(0, i + 1);
}