From: Eduardo San Martin Morote Date: Wed, 26 Jun 2024 08:49:57 +0000 (+0200) Subject: perf: parseURL minor improvements X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bf52c6e31c50c05c6ccd91a3f159fa7e5ea8c83a;p=thirdparty%2Fvuejs%2Frouter.git perf: parseURL minor improvements --- diff --git a/packages/router/__tests__/location.spec.ts b/packages/router/__tests__/location.spec.ts index 8ccd8a42..98dadf1e 100644 --- a/packages/router/__tests__/location.spec.ts +++ b/packages/router/__tests__/location.spec.ts @@ -134,7 +134,7 @@ describe('parseURL', () => { }) }) - it('parses ? after the hash', () => { + it('avoids ? after the hash', () => { expect(parseURL('/foo#?a=one')).toEqual({ fullPath: '/foo#?a=one', path: '/foo', @@ -149,11 +149,75 @@ describe('parseURL', () => { }) }) + it('works with empty query', () => { + expect(parseURL('/foo?#hash')).toEqual({ + fullPath: '/foo?#hash', + path: '/foo', + hash: '#hash', + query: {}, + }) + expect(parseURL('/foo?')).toEqual({ + fullPath: '/foo?', + path: '/foo', + hash: '', + query: {}, + }) + }) + + it('works with empty hash', () => { + expect(parseURL('/foo#')).toEqual({ + fullPath: '/foo#', + path: '/foo', + hash: '#', + query: {}, + }) + expect(parseURL('/foo?#')).toEqual({ + fullPath: '/foo?#', + path: '/foo', + hash: '#', + query: {}, + }) + }) + + it('works with a relative paths', () => { + expect(parseURL('foo', '/parent/bar')).toEqual({ + fullPath: '/parent/foo', + path: '/parent/foo', + hash: '', + query: {}, + }) + expect(parseURL('./foo', '/parent/bar')).toEqual({ + fullPath: '/parent/foo', + path: '/parent/foo', + hash: '', + query: {}, + }) + expect(parseURL('../foo', '/parent/bar')).toEqual({ + fullPath: '/foo', + path: '/foo', + hash: '', + query: {}, + }) + + expect(parseURL('#foo', '/parent/bar')).toEqual({ + fullPath: '/parent/bar#foo', + path: '/parent/bar', + hash: '#foo', + query: {}, + }) + expect(parseURL('?o=o', '/parent/bar')).toEqual({ + fullPath: '/parent/bar?o=o', + path: '/parent/bar', + hash: '', + query: { o: 'o' }, + }) + }) + it('calls parseQuery', () => { const parseQuery = vi.fn() originalParseURL(parseQuery, '/?é=é&é=a') expect(parseQuery).toHaveBeenCalledTimes(1) - expect(parseQuery).toHaveBeenCalledWith('é=é&é=a') + expect(parseQuery).toHaveBeenCalledWith('?é=é&é=a') }) }) diff --git a/packages/router/__tests__/router.spec.ts b/packages/router/__tests__/router.spec.ts index bf11f31b..f835f41e 100644 --- a/packages/router/__tests__/router.spec.ts +++ b/packages/router/__tests__/router.spec.ts @@ -14,8 +14,6 @@ import { START_LOCATION_NORMALIZED } from '../src/location' import { vi, describe, expect, it, beforeAll } from 'vitest' import { mockWarn } from './vitest-mock-warn' -declare var __DEV__: boolean - const routes: RouteRecordRaw[] = [ { path: '/', component: components.Home, name: 'home' }, { path: '/home', redirect: '/' }, @@ -173,7 +171,7 @@ describe('Router', () => { const parseQuery = vi.fn(_ => ({})) const { router } = await newRouter({ parseQuery }) const to = router.resolve('/foo?bar=baz') - expect(parseQuery).toHaveBeenCalledWith('bar=baz') + expect(parseQuery).toHaveBeenCalledWith('?bar=baz') expect(to.query).toEqual({}) }) diff --git a/packages/router/src/location.ts b/packages/router/src/location.ts index 08c2b744..e0aa5405 100644 --- a/packages/router/src/location.ts +++ b/packages/router/src/location.ts @@ -50,37 +50,43 @@ export function parseURL( searchString = '', hash = '' - // Could use URL and URLSearchParams but IE 11 doesn't support it - // TODO: move to new URL() + // NOTE: we could use URL and URLSearchParams but they are 2 to 5 times slower than this method const hashPos = location.indexOf('#') - let searchPos = location.indexOf('?') - // the hash appears before the search, so it's not part of the search string - if (hashPos < searchPos && hashPos >= 0) { - searchPos = -1 - } + // let searchPos = location.indexOf('?') + let searchPos = + hashPos >= 0 + ? // find the query string before the hash to avoid including a ? in the hash + // e.g. /foo#hash?query -> has no query + location.lastIndexOf('?', hashPos) + : location.indexOf('?') - if (searchPos > -1) { + if (searchPos >= 0) { path = location.slice(0, searchPos) - searchString = location.slice( - searchPos + 1, - hashPos > -1 ? hashPos : location.length - ) + searchString = + '?' + + location.slice(searchPos + 1, hashPos > 0 ? hashPos : location.length) query = parseQuery(searchString) } - if (hashPos > -1) { + if (hashPos >= 0) { + // TODO(major): path ||= path = path || location.slice(0, hashPos) // keep the # character hash = location.slice(hashPos, location.length) } - // no search and no query - path = resolveRelativePath(path != null ? path : location, currentLocation) - // empty path means a relative query or hash `?foo=f`, `#thing` + // TODO(major): path ?? location + path = resolveRelativePath( + path != null + ? path + : // empty path means a relative query or hash `?foo=f`, `#thing` + location, + currentLocation + ) return { - fullPath: path + (searchString && '?') + searchString + hash, + fullPath: path + searchString + hash, path, query, hash: decode(hash), @@ -207,11 +213,12 @@ export function resolveRelativePath(to: string, from: string): string { return to } + // resolve '' with '/anything' -> '/anything' if (!to) return from const fromSegments = from.split('/') const toSegments = to.split('/') - const lastToSegment = toSegments[toSegments.length - 1] + const lastToSegment: string | undefined = toSegments[toSegments.length - 1] // make . and ./ the same (../ === .., ../../ === ../..) // this is the same behavior as new URL() diff --git a/packages/router/src/query.ts b/packages/router/src/query.ts index 94d91461..75a8cc70 100644 --- a/packages/router/src/query.ts +++ b/packages/router/src/query.ts @@ -56,8 +56,7 @@ export function parseQuery(search: string): LocationQuery { // avoid creating an object with an empty key and empty value // because of split('&') if (search === '' || search === '?') return query - const hasLeadingIM = search[0] === '?' - const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&') + const searchParams = (search[0] === '?' ? search.slice(1) : search).split('&') for (let i = 0; i < searchParams.length; ++i) { // pre decode the + into space const searchParam = searchParams[i].replace(PLUS_RE, ' ')