From fa7b7fd525aecf8b9bed5d3707644a368bcb5244 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Sun, 16 Aug 2026 18:53:32 +0000 Subject: [PATCH] perf(history): intern parseHref and skip sanitize on simple paths Repeated pushes of the same href reuse the last split. Paths that start with a single slash and have no query, hash, or control characters skip the sanitize regex. Query-only and hash-only hrefs keep those markers out of the pathname. --- packages/history/src/index.ts | 103 ++++++++++++++++++----- packages/history/tests/parseHref.test.ts | 21 +++++ 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/packages/history/src/index.ts b/packages/history/src/index.ts index 063174c7328..774a1a07b27 100644 --- a/packages/history/src/index.ts +++ b/packages/history/src/index.ts @@ -641,37 +641,100 @@ function sanitizePath(path: string): string { return sanitized } +let lastHref: string | undefined +let lastSanitizedHref = '' +let lastPathname = '' +let lastSearch = '' +let lastHash = '' + +function historyState( + state: ParsedHistoryState | undefined, +): ParsedHistoryState { + if (state) { + return state + } + const addedKey = createRandomKey() + return { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey } +} + export function parseHref( href: string, state: ParsedHistoryState | undefined, ): HistoryLocation { + if (href === lastHref) { + return { + href: lastSanitizedHref, + pathname: lastPathname, + hash: lastHash, + search: lastSearch, + state: historyState(state), + } + } + + const hrefLen = href.length + if ( + hrefLen !== 0 && + href.charCodeAt(0) === 47 && + (hrefLen === 1 || href.charCodeAt(1) !== 47) + ) { + let simple = true + for (let i = 1; i < hrefLen; i++) { + const code = href.charCodeAt(i) + if (code <= 0x1f || code === 0x7f || code === 63 || code === 35) { + simple = false + break + } + } + if (simple) { + lastHref = href + lastSanitizedHref = href + lastPathname = href + lastSearch = '' + lastHash = '' + return { + href, + pathname: href, + hash: '', + search: '', + state: historyState(state), + } + } + } + const sanitizedHref = sanitizePath(href) const hashIndex = sanitizedHref.indexOf('#') const searchIndex = sanitizedHref.indexOf('?') - const addedKey = createRandomKey() + let pathEnd = sanitizedHref.length + if (hashIndex >= 0 && searchIndex >= 0) { + pathEnd = Math.min(hashIndex, searchIndex) + } else if (hashIndex >= 0) { + pathEnd = hashIndex + } else if (searchIndex >= 0) { + pathEnd = searchIndex + } + const pathname = sanitizedHref.substring(0, pathEnd) + const hash = hashIndex >= 0 ? sanitizedHref.substring(hashIndex) : '' + const search = + searchIndex >= 0 + ? sanitizedHref.slice( + searchIndex, + hashIndex === -1 ? undefined : hashIndex, + ) + : '' + + lastHref = href + lastSanitizedHref = sanitizedHref + lastPathname = pathname + lastSearch = search + lastHash = hash return { href: sanitizedHref, - pathname: sanitizedHref.substring( - 0, - hashIndex > 0 - ? searchIndex > 0 - ? Math.min(hashIndex, searchIndex) - : hashIndex - : searchIndex > 0 - ? searchIndex - : sanitizedHref.length, - ), - hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : '', - search: - searchIndex > -1 - ? sanitizedHref.slice( - searchIndex, - hashIndex === -1 ? undefined : hashIndex, - ) - : '', - state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey }, + pathname, + hash, + search, + state: historyState(state), } } diff --git a/packages/history/tests/parseHref.test.ts b/packages/history/tests/parseHref.test.ts index e1b8db9c1a3..9b40c90ea9a 100644 --- a/packages/history/tests/parseHref.test.ts +++ b/packages/history/tests/parseHref.test.ts @@ -53,4 +53,25 @@ describe('parseHref', () => { expect(parsed.hash).toBe('#section') }) }) + + test('keeps query-only hrefs out of the pathname', () => { + const parsed = parseHref('?tab=one', undefined) + expect(parsed.pathname).toBe('') + expect(parsed.search).toBe('?tab=one') + expect(parsed.hash).toBe('') + }) + + test('keeps hash-only hrefs out of the pathname', () => { + const parsed = parseHref('#section', undefined) + expect(parsed.pathname).toBe('') + expect(parsed.search).toBe('') + expect(parsed.hash).toBe('#section') + }) + + test('keeps a leading query before a hash out of the pathname', () => { + const parsed = parseHref('?#', undefined) + expect(parsed.pathname).toBe('') + expect(parsed.search).toBe('?') + expect(parsed.hash).toBe('#') + }) })