Skip to content

Commit 05779a7

Browse files
authored
perf(server-renderer): optimize unrollBuffer by avoiding promises (#11340)
1 parent d76dd9c commit 05779a7

File tree

2 files changed

+112
-18
lines changed

2 files changed

+112
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { bench, describe } from 'vitest'
2+
3+
import { type SSRBuffer, createBuffer } from '../src/render'
4+
import { unrollBuffer } from '../src/renderToString'
5+
6+
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
7+
const buffer = createBuffer()
8+
9+
function addItems(buf: ReturnType<typeof createBuffer>, level: number) {
10+
for (let i = 1; i <= levels * itemsPerLevel; i++) {
11+
buf.push(`sync${level}.${i}`)
12+
}
13+
if (level < levels) {
14+
const subBuffer = createBuffer()
15+
addItems(subBuffer, level + 1)
16+
buf.push(subBuffer.getBuffer())
17+
}
18+
}
19+
20+
addItems(buffer, 1)
21+
return buffer.getBuffer()
22+
}
23+
24+
function createMixedBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
25+
const buffer = createBuffer()
26+
27+
function addItems(buf: ReturnType<typeof createBuffer>, level: number) {
28+
for (let i = 1; i <= levels * itemsPerLevel; i++) {
29+
if (i % 3 === 0) {
30+
// @ts-expect-error testing...
31+
buf.push(Promise.resolve(`async${level}.${i}`))
32+
} else {
33+
buf.push(`sync${level}.${i}`)
34+
}
35+
}
36+
if (level < levels) {
37+
const subBuffer = createBuffer()
38+
addItems(subBuffer, level + 1)
39+
buf.push(subBuffer.getBuffer())
40+
}
41+
}
42+
43+
addItems(buffer, 1)
44+
return buffer.getBuffer()
45+
}
46+
47+
describe('unrollBuffer', () => {
48+
let syncBuffer = createBuffer().getBuffer()
49+
let mixedBuffer = createBuffer().getBuffer()
50+
51+
bench(
52+
'sync',
53+
() => {
54+
return unrollBuffer(syncBuffer) as any
55+
},
56+
{
57+
setup() {
58+
syncBuffer = createSyncBuffer(5, 3)
59+
},
60+
},
61+
)
62+
63+
bench(
64+
'mixed',
65+
() => {
66+
return unrollBuffer(mixedBuffer) as any
67+
},
68+
{
69+
setup() {
70+
mixedBuffer = createMixedBuffer(5, 3)
71+
},
72+
},
73+
)
74+
})

packages/server-renderer/src/renderToString.ts

+38-18
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,46 @@ import { type SSRBuffer, type SSRContext, renderComponentVNode } from './render'
1111

1212
const { isVNode } = ssrUtils
1313

14-
async function unrollBuffer(buffer: SSRBuffer): Promise<string> {
15-
if (buffer.hasAsync) {
16-
let ret = ''
17-
for (let i = 0; i < buffer.length; i++) {
18-
let item = buffer[i]
19-
if (isPromise(item)) {
20-
item = await item
21-
}
22-
if (isString(item)) {
23-
ret += item
24-
} else {
25-
ret += await unrollBuffer(item)
26-
}
14+
function nestedUnrollBuffer(
15+
buffer: SSRBuffer,
16+
parentRet: string,
17+
startIndex: number,
18+
): Promise<string> | string {
19+
if (!buffer.hasAsync) {
20+
return parentRet + unrollBufferSync(buffer)
21+
}
22+
23+
let ret = parentRet
24+
for (let i = startIndex; i < buffer.length; i += 1) {
25+
const item = buffer[i]
26+
if (isString(item)) {
27+
ret += item
28+
continue
2729
}
28-
return ret
29-
} else {
30-
// sync buffer can be more efficiently unrolled without unnecessary await
31-
// ticks
32-
return unrollBufferSync(buffer)
30+
31+
if (isPromise(item)) {
32+
return item.then(nestedItem => {
33+
buffer[i] = nestedItem
34+
return nestedUnrollBuffer(buffer, ret, i)
35+
})
36+
}
37+
38+
const result = nestedUnrollBuffer(item, ret, 0)
39+
if (isPromise(result)) {
40+
return result.then(nestedItem => {
41+
buffer[i] = nestedItem
42+
return nestedUnrollBuffer(buffer, '', i)
43+
})
44+
}
45+
46+
ret = result
3347
}
48+
49+
return ret
50+
}
51+
52+
export function unrollBuffer(buffer: SSRBuffer): Promise<string> | string {
53+
return nestedUnrollBuffer(buffer, '', 0)
3454
}
3555

3656
function unrollBufferSync(buffer: SSRBuffer): string {

0 commit comments

Comments
 (0)