@@ -1584,17 +1584,18 @@ function matchPathImpl<Path extends string>(
15841584 if ( ! match ) return null ;
15851585
15861586 let matchedPathname = match [ 0 ] ;
1587- let pathnameBase = matchedPathname . replace ( / ( . ) \/ + $ / , "$1" ) ;
1587+ let pathnameBase = removeTrailingSlash ( matchedPathname , 1 ) ;
15881588 let captureGroups = match . slice ( 1 ) ;
15891589 let params : Params = compiledParams . reduce < Mutable < Params > > (
15901590 ( memo , { paramName, isOptional } , index ) => {
15911591 // We need to compute the pathnameBase here using the raw splat value
15921592 // instead of using params["*"] later because it will be decoded then
15931593 if ( paramName === "*" ) {
15941594 let splatValue = captureGroups [ index ] || "" ;
1595- pathnameBase = matchedPathname
1596- . slice ( 0 , matchedPathname . length - splatValue . length )
1597- . replace ( / ( .) \/ + $ / , "$1" ) ;
1595+ pathnameBase = removeTrailingSlash (
1596+ matchedPathname . slice ( 0 , matchedPathname . length - splatValue . length ) ,
1597+ 1 ,
1598+ ) ;
15981599 }
15991600
16001601 const value = captureGroups [ index ] ;
@@ -1944,8 +1945,14 @@ export const removeDoubleSlashes = (path: string): string =>
19441945export const joinPaths = ( paths : string [ ] ) : string =>
19451946 removeDoubleSlashes ( paths . join ( "/" ) ) ;
19461947
1947- export const removeTrailingSlash = ( path : string ) : string =>
1948- path . replace ( / \/ + $ / , "" ) ;
1948+ // Scan from the end to avoid repeated RegExp work on long paths.
1949+ export function removeTrailingSlash ( path : string , minLength = 0 ) : string {
1950+ let end = path . length ;
1951+ while ( end > minLength && path . charCodeAt ( end - 1 ) === 47 ) {
1952+ end -- ;
1953+ }
1954+ return end === path . length ? path : path . slice ( 0 , end ) ;
1955+ }
19491956
19501957export const normalizePathname = ( pathname : string ) : string =>
19511958 removeTrailingSlash ( pathname ) . replace ( / ^ \/ * / , "/" ) ;
0 commit comments