@@ -1692,17 +1692,18 @@ function matchPathImpl<Path extends string>(
16921692 if ( ! match ) return null ;
16931693
16941694 let matchedPathname = match [ 0 ] ;
1695- let pathnameBase = matchedPathname . replace ( / ( . ) \/ + $ / , "$1" ) ;
1695+ let pathnameBase = removeTrailingSlash ( matchedPathname , 1 ) ;
16961696 let captureGroups = match . slice ( 1 ) ;
16971697 let params : Params = compiledParams . reduce < Mutable < Params > > (
16981698 ( memo , { paramName, isOptional } , index ) => {
16991699 // We need to compute the pathnameBase here using the raw splat value
17001700 // instead of using params["*"] later because it will be decoded then
17011701 if ( paramName === "*" ) {
17021702 let splatValue = captureGroups [ index ] || "" ;
1703- pathnameBase = matchedPathname
1704- . slice ( 0 , matchedPathname . length - splatValue . length )
1705- . replace ( / ( .) \/ + $ / , "$1" ) ;
1703+ pathnameBase = removeTrailingSlash (
1704+ matchedPathname . slice ( 0 , matchedPathname . length - splatValue . length ) ,
1705+ 1 ,
1706+ ) ;
17061707 }
17071708
17081709 const value = captureGroups [ index ] ;
@@ -2054,8 +2055,14 @@ export const removeDoubleSlashes = (path: string): string =>
20542055export const joinPaths = ( paths : string [ ] ) : string =>
20552056 removeDoubleSlashes ( paths . join ( "/" ) ) ;
20562057
2057- export const removeTrailingSlash = ( path : string ) : string =>
2058- path . replace ( / \/ + $ / , "" ) ;
2058+ // Scan from the end to avoid repeated RegExp work on long paths.
2059+ export function removeTrailingSlash ( path : string , minLength = 0 ) : string {
2060+ let end = path . length ;
2061+ while ( end > minLength && path . charCodeAt ( end - 1 ) === 47 ) {
2062+ end -- ;
2063+ }
2064+ return end === path . length ? path : path . slice ( 0 , end ) ;
2065+ }
20592066
20602067export const normalizePathname = ( pathname : string ) : string =>
20612068 removeTrailingSlash ( pathname ) . replace ( / ^ \/ * / , "/" ) ;
0 commit comments