Skip to content

Commit 5df4915

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

File tree

5 files changed

+96
-51
lines changed

5 files changed

+96
-51
lines changed

.changeset/angry-sheep-design.md

+5
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

+42
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

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
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 />))
@@ -20,4 +29,7 @@ function suite(name, Root) {
2029
await suite('SearchResults', IsomorphicSearchResults);
2130
await suite('ColorPicker', ColorPicker);
2231
await suite('Stack Depth', StackApp);
32+
33+
const { App: Async } = await import('./async.js');
34+
await asyncSuite('async', Async);
2335
})();

src/index.js

+34-48
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,6 +23,7 @@ import {
2223
CATCH_ERROR
2324
} from './lib/constants.js';
2425

26+
const EMPTY_OBJ = {};
2527
const EMPTY_ARR = [];
2628
const isArray = Array.isArray;
2729
const assign = Object.assign;
@@ -147,13 +149,6 @@ export async function renderToStringAsync(vnode, context) {
147149
}
148150
}
149151

150-
// Installed as setState/forceUpdate for function components
151-
function markAsDirty() {
152-
this.__d = true;
153-
}
154-
155-
const EMPTY_OBJ = {};
156-
157152
/**
158153
* @param {VNode} vnode
159154
* @param {Record<string, unknown>} context
@@ -238,9 +233,9 @@ function _renderToString(
238233

239234
let vnodeType = typeof vnode;
240235
// 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;
236+
if (vnodeType != 'object') {
237+
if (vnodeType == 'function') return EMPTY_STR;
238+
return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
244239
}
245240

246241
// Recurse into children / Arrays
@@ -250,7 +245,7 @@ function _renderToString(
250245
parent[CHILDREN] = vnode;
251246
for (let i = 0; i < vnode.length; i++) {
252247
let child = vnode[i];
253-
if (child == null || typeof child === 'boolean') continue;
248+
if (child == null || typeof child == 'boolean') continue;
254249

255250
const childRender = _renderToString(
256251
child,
@@ -262,10 +257,12 @@ function _renderToString(
262257
renderer
263258
);
264259

265-
if (typeof childRender === 'string') {
260+
if (typeof childRender == 'string') {
266261
rendered = rendered + childRender;
267262
} else {
268-
renderArray = renderArray || [];
263+
if (!renderArray) {
264+
renderArray = [];
265+
}
269266

270267
if (rendered) renderArray.push(rendered);
271268

@@ -294,14 +291,14 @@ function _renderToString(
294291
if (beforeDiff) beforeDiff(vnode);
295292

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

303296
// Invoke rendering on Components
304-
if (typeof type === 'function') {
297+
if (typeof type == 'function') {
298+
let cctx = context,
299+
contextType,
300+
rendered,
301+
component;
305302
if (type === Fragment) {
306303
// Serialized precompiled JSX.
307304
if ('tpl' in props) {
@@ -315,7 +312,7 @@ function _renderToString(
315312

316313
// Check if we're dealing with a vnode or an array of nodes
317314
if (
318-
typeof value === 'object' &&
315+
typeof value == 'object' &&
319316
(value.constructor === undefined || isArray(value))
320317
) {
321318
out =
@@ -340,9 +337,7 @@ function _renderToString(
340337
} else if ('UNSTABLE_comment' in props) {
341338
// Fragments are the least used components of core that's why
342339
// branching here for comments has the least effect on perf.
343-
return (
344-
'<!--' + encodeEntities(props.UNSTABLE_comment || EMPTY_STR) + '-->'
345-
);
340+
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
346341
}
347342

348343
rendered = props.children;
@@ -354,22 +349,15 @@ function _renderToString(
354349
}
355350

356351
let isClassComponent =
357-
type.prototype && typeof type.prototype.render === 'function';
352+
type.prototype && typeof type.prototype.render == 'function';
358353
if (isClassComponent) {
359354
rendered = /**#__NOINLINE__**/ renderClassComponent(vnode, cctx);
360355
component = vnode[COMPONENT];
361356
} 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-
};
357+
vnode[COMPONENT] = component = /**#__NOINLINE__**/ createComponent(
358+
vnode,
359+
cctx
360+
);
373361

374362
// If a hook invokes setState() to invalidate the component during rendering,
375363
// re-render it up to 25 times to allow "settling" of memoized states.
@@ -402,7 +390,7 @@ function _renderToString(
402390
rendered != null &&
403391
rendered.type === Fragment &&
404392
rendered.key == null &&
405-
!('tpl' in rendered.props);
393+
rendered.props.tpl == null;
406394
rendered = isTopLevelFragment ? rendered.props.children : rendered;
407395

408396
try {
@@ -416,8 +404,6 @@ function _renderToString(
416404
renderer
417405
);
418406
} catch (err) {
419-
let str = EMPTY_STR;
420-
421407
if (type.getDerivedStateFromError) {
422408
component[NEXT_STATE] = type.getDerivedStateFromError(err);
423409
}
@@ -438,10 +424,10 @@ function _renderToString(
438424
rendered != null &&
439425
rendered.type === Fragment &&
440426
rendered.key == null &&
441-
!('tpl' in rendered.props);
427+
rendered.props.tpl == null;
442428
rendered = isTopLevelFragment ? rendered.props.children : rendered;
443429

444-
str = _renderToString(
430+
return _renderToString(
445431
rendered,
446432
context,
447433
isSvgMode,
@@ -452,7 +438,7 @@ function _renderToString(
452438
);
453439
}
454440

455-
return str;
441+
return EMPTY_STR;
456442
} finally {
457443
if (afterDiff) afterDiff(vnode);
458444
vnode[PARENT] = null;
@@ -468,7 +454,7 @@ function _renderToString(
468454
rendered != null &&
469455
rendered.type === Fragment &&
470456
rendered.key == null &&
471-
!('tpl' in rendered.props);
457+
rendered.props.tpl == null;
472458
rendered = isTopLevelFragment ? rendered.props.children : rendered;
473459

474460
try {
@@ -513,7 +499,7 @@ function _renderToString(
513499

514500
if (!asyncMode) throw error;
515501

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

518504
const renderNestedChildren = () => {
519505
try {
@@ -527,7 +513,7 @@ function _renderToString(
527513
renderer
528514
);
529515
} catch (e) {
530-
if (!e || typeof e.then !== 'function') throw e;
516+
if (!e || typeof e.then != 'function') throw e;
531517

532518
return e.then(
533519
() =>
@@ -557,7 +543,7 @@ function _renderToString(
557543
for (let name in props) {
558544
let v = props[name];
559545

560-
if (typeof v === 'function') continue;
546+
if (typeof v == 'function') continue;
561547

562548
switch (name) {
563549
case 'children':
@@ -663,7 +649,7 @@ function _renderToString(
663649
' ' +
664650
name +
665651
'="' +
666-
(typeof v === 'string' ? encodeEntities(v) : v + EMPTY_STR) +
652+
(typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) +
667653
'"';
668654
}
669655
}
@@ -711,7 +697,7 @@ function _renderToString(
711697
const startTag = s + '>';
712698

713699
if (isArray(html)) return [startTag, ...html, endTag];
714-
else if (typeof html !== 'string') return [startTag, html, endTag];
700+
else if (typeof html != 'string') return [startTag, html, endTag];
715701
return startTag + html + endTag;
716702
}
717703

src/lib/util.js

+1-1
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)