Skip to content

Commit bd26c31

Browse files
EdamAme-xautofix-ci[bot]claude
authored
perf(trie-router): improve performance (1.5x ~ 2.0x) (#4724)
* Update node.ts * ci: apply automated fixes * ci: apply automated fixes (attempt 2/3) * test(trie-router): add test for Node constructor with method and handler Covers the constructor branch where method and handler are provided, improving patch coverage to 100% line/statement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b85c1e0 commit bd26c31

File tree

2 files changed

+48
-17
lines changed

2 files changed

+48
-17
lines changed

src/router/trie-router/node.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,3 +804,13 @@ describe('The same name is used for path params', () => {
804804
})
805805
})
806806
})
807+
808+
describe('Node with initial method and handler', () => {
809+
it('should create a node with method and handler via constructor', () => {
810+
const node = new Node('get', 'initial handler')
811+
node.insert('get', '/hello', 'hello handler')
812+
const [res] = node.search('get', '/hello')
813+
expect(res.length).toBe(1)
814+
expect(res[0][0]).toEqual('hello handler')
815+
})
816+
})

src/router/trie-router/node.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ type HandlerParamsSet<T> = HandlerSet<T> & {
1515

1616
const emptyParams = Object.create(null)
1717

18+
const hasChildren = (children: Record<string, unknown>): boolean => {
19+
for (const _ in children) {
20+
return true
21+
}
22+
return false
23+
}
24+
1825
export class Node<T> {
1926
#methods: Record<string, HandlerSet<T>>[]
2027

@@ -77,13 +84,13 @@ export class Node<T> {
7784
return curNode
7885
}
7986

80-
#getHandlerSets(
87+
#pushHandlerSets(
88+
handlerSets: HandlerParamsSet<T>[],
8189
node: Node<T>,
8290
method: string,
8391
nodeParams: Record<string, string>,
8492
params?: Record<string, string>
85-
): HandlerParamsSet<T>[] {
86-
const handlerSets: HandlerParamsSet<T>[] = []
93+
): void {
8794
for (let i = 0, len = node.#methods.length; i < len; i++) {
8895
const m = node.#methods[i]
8996
const handlerSet = (m[method] || m[METHOD_NAME_ALL]) as HandlerParamsSet<T>
@@ -102,7 +109,6 @@ export class Node<T> {
102109
}
103110
}
104111
}
105-
return handlerSets
106112
}
107113

108114
search(method: string, path: string): [[T, Params][]] {
@@ -115,7 +121,10 @@ export class Node<T> {
115121
const parts = splitPath(path)
116122
const curNodesQueue: Node<T>[][] = []
117123

118-
for (let i = 0, len = parts.length; i < len; i++) {
124+
const len = parts.length
125+
let partOffsets: number[] | null = null
126+
127+
for (let i = 0; i < len; i++) {
119128
const part: string = parts[i]
120129
const isLast = i === len - 1
121130
const tempNodes: Node<T>[] = []
@@ -129,11 +138,9 @@ export class Node<T> {
129138
if (isLast) {
130139
// '/hello/*' => match '/hello'
131140
if (nextNode.#children['*']) {
132-
handlerSets.push(
133-
...this.#getHandlerSets(nextNode.#children['*'], method, node.#params)
134-
)
141+
this.#pushHandlerSets(handlerSets, nextNode.#children['*'], method, node.#params)
135142
}
136-
handlerSets.push(...this.#getHandlerSets(nextNode, method, node.#params))
143+
this.#pushHandlerSets(handlerSets, nextNode, method, node.#params)
137144
} else {
138145
tempNodes.push(nextNode)
139146
}
@@ -148,7 +155,7 @@ export class Node<T> {
148155
if (pattern === '*') {
149156
const astNode = node.#children['*']
150157
if (astNode) {
151-
handlerSets.push(...this.#getHandlerSets(astNode, method, node.#params))
158+
this.#pushHandlerSets(handlerSets, astNode, method, node.#params)
152159
astNode.#params = params
153160
tempNodes.push(astNode)
154161
}
@@ -164,14 +171,23 @@ export class Node<T> {
164171
const child = node.#children[key]
165172

166173
// `/js/:filename{[a-z]+.js}` => match /js/chunk/123.js
167-
const restPathString = parts.slice(i).join('/')
168174
if (matcher instanceof RegExp) {
175+
if (partOffsets === null) {
176+
partOffsets = new Array(len)
177+
let offset = path[0] === '/' ? 1 : 0
178+
for (let p = 0; p < len; p++) {
179+
partOffsets[p] = offset
180+
offset += parts[p].length + 1
181+
}
182+
}
183+
const restPathString = path.substring(partOffsets[i])
184+
169185
const m = matcher.exec(restPathString)
170186
if (m) {
171187
params[name] = m[0]
172-
handlerSets.push(...this.#getHandlerSets(child, method, node.#params, params))
188+
this.#pushHandlerSets(handlerSets, child, method, node.#params, params)
173189

174-
if (Object.keys(child.#children).length) {
190+
if (hasChildren(child.#children)) {
175191
child.#params = params
176192
const componentCount = m[0].match(/\//)?.length ?? 0
177193
const targetCurNodes = (curNodesQueue[componentCount] ||= [])
@@ -185,10 +201,14 @@ export class Node<T> {
185201
if (matcher === true || matcher.test(part)) {
186202
params[name] = part
187203
if (isLast) {
188-
handlerSets.push(...this.#getHandlerSets(child, method, params, node.#params))
204+
this.#pushHandlerSets(handlerSets, child, method, params, node.#params)
189205
if (child.#children['*']) {
190-
handlerSets.push(
191-
...this.#getHandlerSets(child.#children['*'], method, params, node.#params)
206+
this.#pushHandlerSets(
207+
handlerSets,
208+
child.#children['*'],
209+
method,
210+
params,
211+
node.#params
192212
)
193213
}
194214
} else {
@@ -199,7 +219,8 @@ export class Node<T> {
199219
}
200220
}
201221

202-
curNodes = tempNodes.concat(curNodesQueue.shift() ?? [])
222+
const shifted = curNodesQueue.shift()
223+
curNodes = shifted ? tempNodes.concat(shifted) : tempNodes
203224
}
204225

205226
if (handlerSets.length > 1) {

0 commit comments

Comments
 (0)