Skip to content

Commit 1ac4fc4

Browse files
committed
Add async benchmark and perf iterations
1 parent 45b8e8b commit 1ac4fc4

File tree

5 files changed

+98
-49
lines changed

5 files changed

+98
-49
lines changed

.changeset/angry-sheep-design.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'preact-render-to-string': patch
3+
---
4+
5+
Add async benchmarks and iterate on perf improvements

benchmarks/async.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { h } from 'preact';
2+
import { lazy } from 'preact/compat';
3+
4+
function Leaf() {
5+
return (
6+
<div>
7+
<span class="foo" data-testid="stack">
8+
deep stack
9+
</span>
10+
</div>
11+
);
12+
}
13+
14+
const lazies = new Array(600)
15+
.fill(600)
16+
.map(() =>
17+
lazy(() =>
18+
Promise.resolve().then(() => ({
19+
default: (props) => <div>{props.children}</div>
20+
}))
21+
)
22+
);
23+
function PassThrough(props) {
24+
const Lazy = lazies(props.id);
25+
return <Lazy {...props} />;
26+
}
27+
28+
function recursive(n, m) {
29+
if (n <= 0) {
30+
return <Leaf />;
31+
}
32+
return <PassThrough id={n * m}>{recursive(n - 1)}</PassThrough>;
33+
}
34+
35+
const content = [];
36+
for (let i = 0; i < 5; i++) {
37+
content.push(recursive(10, i));
38+
}
39+
40+
export default function App() {
41+
return <div>{content}</div>;
42+
}

benchmarks/index.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
import { h } from 'preact';
22
import Suite from 'benchmarkjs-pretty';
3-
import renderToStringBaseline from 'baseline-rts';
3+
import renderToStringBaseline, {
4+
renderToStringAsync as renderToStringAsyncBaseline
5+
} from 'baseline-rts';
46
// import renderToString from '../src/index';
5-
import renderToString from '../dist/index.module.js';
7+
import renderToString, { renderToStringAsync } from '../dist/index.module.js';
68
import TextApp from './text';
79
import StackApp from './stack';
810
import { App as IsomorphicSearchResults } from './isomorphic-ui/search-results/index';
911
import { App as ColorPicker } from './isomorphic-ui/color-picker';
1012

1113
function suite(name, Root) {
14+
return new Suite(name)
15+
.add('baseline', () => renderToStringAsyncBaseline(<Root />))
16+
.add('current', () => renderToStringAsync(<Root />))
17+
.run();
18+
}
19+
20+
function asyncSuite(name, Root) {
1221
return new Suite(name)
1322
.add('baseline', () => renderToStringBaseline(<Root />))
1423
.add('current', () => renderToString(<Root />))
1524
.run();
1625
}
1726

1827
(async () => {
19-
await suite('Text', TextApp);
20-
await suite('SearchResults', IsomorphicSearchResults);
21-
await suite('ColorPicker', ColorPicker);
22-
await suite('Stack Depth', StackApp);
28+
// await suite('Text', TextApp);
29+
// await suite('SearchResults', IsomorphicSearchResults);
30+
// await suite('ColorPicker', ColorPicker);
31+
// await suite('Stack Depth', StackApp);
32+
33+
const { App: Async } = await import('./async.js');
34+
await asyncSuite('async', Async);
2335
})();

src/index.js

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
UNSAFE_NAME,
55
NAMESPACE_REPLACE_REGEX,
66
HTML_LOWER_CASE,
7-
SVG_CAMEL_CASE
7+
SVG_CAMEL_CASE,
8+
createComponent
89
} from './lib/util.js';
910
import { options, h, Fragment } from 'preact';
1011
import {
@@ -22,7 +23,7 @@ import {
2223
CATCH_ERROR
2324
} from './lib/constants.js';
2425

25-
const EMPTY_ARR = [];
26+
const EMPTY_ARR = new Array(0);
2627
const isArray = Array.isArray;
2728
const assign = Object.assign;
2829
const EMPTY_STR = '';
@@ -238,9 +239,9 @@ function _renderToString(
238239

239240
let vnodeType = typeof vnode;
240241
// Text VNodes: escape as HTML
241-
if (vnodeType !== 'object') {
242-
if (vnodeType === 'function') return EMPTY_STR;
243-
return vnodeType === 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
242+
if (vnodeType != 'object') {
243+
if (vnodeType == 'function') return EMPTY_STR;
244+
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
244245
}
245246

246247
// Recurse into children / Arrays
@@ -250,7 +251,7 @@ function _renderToString(
250251
parent[CHILDREN] = vnode;
251252
for (let i = 0; i < vnode.length; i++) {
252253
let child = vnode[i];
253-
if (child == null || typeof child === 'boolean') continue;
254+
if (child == null || typeof child == 'boolean') continue;
254255

255256
const childRender = _renderToString(
256257
child,
@@ -262,10 +263,10 @@ function _renderToString(
262263
renderer
263264
);
264265

265-
if (typeof childRender === 'string') {
266+
if (typeof childRender == 'string') {
266267
rendered = rendered + childRender;
267268
} else {
268-
renderArray = renderArray || [];
269+
renderArray = renderArray || new Array(vnode.length);
269270

270271
if (rendered) renderArray.push(rendered);
271272

@@ -294,14 +295,14 @@ function _renderToString(
294295
if (beforeDiff) beforeDiff(vnode);
295296

296297
let type = vnode.type,
297-
props = vnode.props,
298-
cctx = context,
299-
contextType,
300-
rendered,
301-
component;
298+
props = vnode.props;
302299

303300
// Invoke rendering on Components
304-
if (typeof type === 'function') {
301+
if (typeof type == 'function') {
302+
let cctx = context,
303+
contextType,
304+
rendered,
305+
component;
305306
if (type === Fragment) {
306307
// Serialized precompiled JSX.
307308
if ('tpl' in props) {
@@ -315,7 +316,7 @@ function _renderToString(
315316

316317
// Check if we're dealing with a vnode or an array of nodes
317318
if (
318-
typeof value === 'object' &&
319+
typeof value == 'object' &&
319320
(value.constructor === undefined || isArray(value))
320321
) {
321322
out =
@@ -340,9 +341,7 @@ function _renderToString(
340341
} else if ('UNSTABLE_comment' in props) {
341342
// Fragments are the least used components of core that's why
342343
// branching here for comments has the least effect on perf.
343-
return (
344-
'<!--' + encodeEntities(props.UNSTABLE_comment || EMPTY_STR) + '-->'
345-
);
344+
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
346345
}
347346

348347
rendered = props.children;
@@ -354,22 +353,15 @@ function _renderToString(
354353
}
355354

356355
let isClassComponent =
357-
type.prototype && typeof type.prototype.render === 'function';
356+
type.prototype && typeof type.prototype.render == 'function';
358357
if (isClassComponent) {
359358
rendered = /**#__NOINLINE__**/ renderClassComponent(vnode, cctx);
360359
component = vnode[COMPONENT];
361360
} else {
362-
vnode[COMPONENT] = component = {
363-
__v: vnode,
364-
props,
365-
context: cctx,
366-
// silently drop state updates
367-
setState: markAsDirty,
368-
forceUpdate: markAsDirty,
369-
__d: true,
370-
// hooks
371-
__h: []
372-
};
361+
vnode[COMPONENT] = component = /**#__NOINLINE__**/ createComponent(
362+
vnode,
363+
cctx
364+
);
373365

374366
// If a hook invokes setState() to invalidate the component during rendering,
375367
// re-render it up to 25 times to allow "settling" of memoized states.
@@ -402,7 +394,7 @@ function _renderToString(
402394
rendered != null &&
403395
rendered.type === Fragment &&
404396
rendered.key == null &&
405-
!('tpl' in rendered.props);
397+
rendered.props.tpl == null;
406398
rendered = isTopLevelFragment ? rendered.props.children : rendered;
407399

408400
try {
@@ -416,8 +408,6 @@ function _renderToString(
416408
renderer
417409
);
418410
} catch (err) {
419-
let str = EMPTY_STR;
420-
421411
if (type.getDerivedStateFromError) {
422412
component[NEXT_STATE] = type.getDerivedStateFromError(err);
423413
}
@@ -438,10 +428,10 @@ function _renderToString(
438428
rendered != null &&
439429
rendered.type === Fragment &&
440430
rendered.key == null &&
441-
!('tpl' in rendered.props);
431+
rendered.props.tpl == null;
442432
rendered = isTopLevelFragment ? rendered.props.children : rendered;
443433

444-
str = _renderToString(
434+
return _renderToString(
445435
rendered,
446436
context,
447437
isSvgMode,
@@ -452,7 +442,7 @@ function _renderToString(
452442
);
453443
}
454444

455-
return str;
445+
return EMPTY_STR;
456446
} finally {
457447
if (afterDiff) afterDiff(vnode);
458448
vnode[PARENT] = null;
@@ -468,7 +458,7 @@ function _renderToString(
468458
rendered != null &&
469459
rendered.type === Fragment &&
470460
rendered.key == null &&
471-
!('tpl' in rendered.props);
461+
rendered.props.tpl == null;
472462
rendered = isTopLevelFragment ? rendered.props.children : rendered;
473463

474464
try {
@@ -513,7 +503,7 @@ function _renderToString(
513503

514504
if (!asyncMode) throw error;
515505

516-
if (!error || typeof error.then !== 'function') throw error;
506+
if (!error || typeof error.then != 'function') throw error;
517507

518508
const renderNestedChildren = () => {
519509
try {
@@ -527,7 +517,7 @@ function _renderToString(
527517
renderer
528518
);
529519
} catch (e) {
530-
if (!e || typeof e.then !== 'function') throw e;
520+
if (!e || typeof e.then != 'function') throw e;
531521

532522
return e.then(
533523
() =>
@@ -557,7 +547,7 @@ function _renderToString(
557547
for (let name in props) {
558548
let v = props[name];
559549

560-
if (typeof v === 'function') continue;
550+
if (typeof v == 'function') continue;
561551

562552
switch (name) {
563553
case 'children':
@@ -663,7 +653,7 @@ function _renderToString(
663653
' ' +
664654
name +
665655
'="' +
666-
(typeof v === 'string' ? encodeEntities(v) : v + EMPTY_STR) +
656+
(typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) +
667657
'"';
668658
}
669659
}
@@ -711,7 +701,7 @@ function _renderToString(
711701
const startTag = s + '>';
712702

713703
if (isArray(html)) return [startTag, ...html, endTag];
714-
else if (typeof html !== 'string') return [startTag, html, endTag];
704+
else if (typeof html != 'string') return [startTag, html, endTag];
715705
return startTag + html + endTag;
716706
}
717707

src/lib/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export function createComponent(vnode, context) {
147147
forceUpdate: markAsDirty,
148148
__d: true,
149149
// hooks
150-
__h: []
150+
__h: new Array(0)
151151
};
152152
}
153153

0 commit comments

Comments
 (0)