diff --git a/.changeset/spicy-camels-love.md b/.changeset/spicy-camels-love.md new file mode 100644 index 0000000000..46bf072730 --- /dev/null +++ b/.changeset/spicy-camels-love.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +Optimize href() to avoid backtracking regex on splat diff --git a/packages/react-router/lib/href.ts b/packages/react-router/lib/href.ts index 0fce5f763e..3d230e9d80 100644 --- a/packages/react-router/lib/href.ts +++ b/packages/react-router/lib/href.ts @@ -27,8 +27,7 @@ export function href( ...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) => { @@ -54,3 +53,24 @@ export function href( 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); +}