diff --git a/packages/router-core/src/new-process-route-tree.ts b/packages/router-core/src/new-process-route-tree.ts index 2640961815..f49d097e5f 100644 --- a/packages/router-core/src/new-process-route-tree.ts +++ b/packages/router-core/src/new-process-route-tree.ts @@ -80,42 +80,44 @@ export function parseSegment( ): ParsedSegment { const next = path.indexOf('/', start) const end = next === -1 ? path.length : next - const part = path.substring(start, end) + const firstChar = path.charCodeAt(start) + + if (firstChar === 36) { + // $ (wildcard) + if (end === start + 1) { + const total = path.length + output[0] = SEGMENT_TYPE_WILDCARD + output[1] = start + output[2] = start + output[3] = total + output[4] = total + output[5] = total + return output as ParsedSegment + } - if (!part || !part.includes('$')) { - // early escape for static pathname - output[0] = SEGMENT_TYPE_PATHNAME + // $paramName + output[0] = SEGMENT_TYPE_PARAM output[1] = start - output[2] = start + output[2] = start + 1 // skip '$' output[3] = end output[4] = end output[5] = end return output as ParsedSegment } - // $ (wildcard) - if (part === '$') { - const total = path.length - output[0] = SEGMENT_TYPE_WILDCARD + const dollar = path.indexOf('$', start) + if (start === end || dollar === -1 || dollar >= end) { + // early escape for static pathname + output[0] = SEGMENT_TYPE_PATHNAME output[1] = start output[2] = start - output[3] = total - output[4] = total - output[5] = total - return output as ParsedSegment - } - - // $paramName - if (part.charCodeAt(0) === 36) { - output[0] = SEGMENT_TYPE_PARAM - output[1] = start - output[2] = start + 1 // skip '$' output[3] = end output[4] = end output[5] = end return output as ParsedSegment } + const part = path.substring(start, end) const braces = getOpenAndCloseBraces(part) if (braces) { const [openBrace, closeBrace] = braces diff --git a/packages/router-core/tests/parseSegment.bench.ts b/packages/router-core/tests/parseSegment.bench.ts new file mode 100644 index 0000000000..53eb31d7d6 --- /dev/null +++ b/packages/router-core/tests/parseSegment.bench.ts @@ -0,0 +1,94 @@ +import { bench, describe, expect } from 'vitest' +import { + SEGMENT_TYPE_OPTIONAL_PARAM, + SEGMENT_TYPE_PARAM, + SEGMENT_TYPE_PATHNAME, + parseSegment, +} from '../src/new-process-route-tree' + +const iterations = 10_000 +const mixedPath = + '/organizations/$organizationId/projects/$projectId/settings' +const staticPath = '/organizations/projects/settings/members/activity' +const deepStaticPath = `/${Array.from( + { length: 32 }, + (_, index) => `segment-${index}`, +).join('/')}` +const bracedPath = + '/organizations/prefix{$organizationId}/projects/{-$projectId}/settings' +let benchmarkSink = 0 + +function segmentTypes(path: string) { + const types: Array = [] + const output = new Uint16Array(6) + let cursor = 0 + while (cursor < path.length) { + const segment = parseSegment(path, cursor, output) + types.push(segment[0]) + cursor = segment[5] + 1 + } + return types +} + +expect(segmentTypes(mixedPath)).toEqual([ + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PARAM, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PARAM, + SEGMENT_TYPE_PATHNAME, +]) +expect(segmentTypes(staticPath)).toEqual([ + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, +]) +expect(segmentTypes(deepStaticPath)).toHaveLength(33) +expect(segmentTypes(deepStaticPath)).toEqual( + Array(33).fill(SEGMENT_TYPE_PATHNAME), +) +expect(segmentTypes(bracedPath)).toEqual([ + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_PARAM, + SEGMENT_TYPE_PATHNAME, + SEGMENT_TYPE_OPTIONAL_PARAM, + SEGMENT_TYPE_PATHNAME, +]) + +function parsePathBatch(path: string) { + const output = new Uint16Array(6) + let checksum = 0 + for (let index = 0; index < iterations; index++) { + let cursor = 0 + while (cursor < path.length) { + const segment = parseSegment(path, cursor, output) + checksum += segment[0] + segment[2] + segment[3] + cursor = segment[5] + 1 + } + } + benchmarkSink = checksum +} + +describe('parseSegment', () => { + bench('mixed static and parameter segments', () => { + parsePathBatch(mixedPath) + }) + + bench('all-static segments', () => { + parsePathBatch(staticPath) + }) + + bench('deep all-static segments', () => { + parsePathBatch(deepStaticPath) + }) + + bench('braced parameter segments', () => { + parsePathBatch(bracedPath) + }) +}) + +void benchmarkSink