diff --git a/.changeset/angry-sheep-design.md b/.changeset/angry-sheep-design.md
new file mode 100644
index 0000000..d6b34b9
--- /dev/null
+++ b/.changeset/angry-sheep-design.md
@@ -0,0 +1,5 @@
+---
+'preact-render-to-string': patch
+---
+
+Add async benchmarks and iterate on perf improvements
diff --git a/benchmarks/async.js b/benchmarks/async.js
new file mode 100644
index 0000000..8ac8b88
--- /dev/null
+++ b/benchmarks/async.js
@@ -0,0 +1,42 @@
+import { h } from 'preact';
+import { lazy } from 'preact/compat';
+
+function Leaf() {
+ return (
+
+
+ deep stack
+
+
+ );
+}
+
+const lazies = new Array(600)
+ .fill(600)
+ .map(() =>
+ lazy(() =>
+ Promise.resolve().then(() => ({
+ default: (props) => {props.children}
+ }))
+ )
+ );
+function PassThrough(props) {
+ const Lazy = lazies(props.id);
+ return ;
+}
+
+function recursive(n, m) {
+ if (n <= 0) {
+ return ;
+ }
+ return {recursive(n - 1)};
+}
+
+const content = [];
+for (let i = 0; i < 5; i++) {
+ content.push(recursive(10, i));
+}
+
+export default function App() {
+ return {content}
;
+}
diff --git a/benchmarks/index.js b/benchmarks/index.js
index c46a7db..55c8ba7 100644
--- a/benchmarks/index.js
+++ b/benchmarks/index.js
@@ -1,14 +1,23 @@
import { h } from 'preact';
import Suite from 'benchmarkjs-pretty';
-import renderToStringBaseline from 'baseline-rts';
+import renderToStringBaseline, {
+ renderToStringAsync as renderToStringAsyncBaseline
+} from 'baseline-rts';
// import renderToString from '../src/index';
-import renderToString from '../dist/index.module.js';
+import renderToString, { renderToStringAsync } from '../dist/index.module.js';
import TextApp from './text';
import StackApp from './stack';
import { App as IsomorphicSearchResults } from './isomorphic-ui/search-results/index';
import { App as ColorPicker } from './isomorphic-ui/color-picker';
function suite(name, Root) {
+ return new Suite(name)
+ .add('baseline', () => renderToStringAsyncBaseline())
+ .add('current', () => renderToStringAsync())
+ .run();
+}
+
+function asyncSuite(name, Root) {
return new Suite(name)
.add('baseline', () => renderToStringBaseline())
.add('current', () => renderToString())
@@ -16,8 +25,11 @@ function suite(name, Root) {
}
(async () => {
- await suite('Text', TextApp);
- await suite('SearchResults', IsomorphicSearchResults);
- await suite('ColorPicker', ColorPicker);
- await suite('Stack Depth', StackApp);
+ // await suite('Text', TextApp);
+ // await suite('SearchResults', IsomorphicSearchResults);
+ // await suite('ColorPicker', ColorPicker);
+ // await suite('Stack Depth', StackApp);
+
+ const { App: Async } = await import('./async.js');
+ await asyncSuite('async', Async);
})();
diff --git a/src/index.js b/src/index.js
index c2efbdf..b29230e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,7 +4,8 @@ import {
UNSAFE_NAME,
NAMESPACE_REPLACE_REGEX,
HTML_LOWER_CASE,
- SVG_CAMEL_CASE
+ SVG_CAMEL_CASE,
+ createComponent
} from './lib/util.js';
import { options, h, Fragment } from 'preact';
import {
@@ -22,7 +23,7 @@ import {
CATCH_ERROR
} from './lib/constants.js';
-const EMPTY_ARR = [];
+const EMPTY_ARR = new Array(0);
const isArray = Array.isArray;
const assign = Object.assign;
const EMPTY_STR = '';
@@ -238,9 +239,9 @@ function _renderToString(
let vnodeType = typeof vnode;
// Text VNodes: escape as HTML
- if (vnodeType !== 'object') {
- if (vnodeType === 'function') return EMPTY_STR;
- return vnodeType === 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
+ if (vnodeType != 'object') {
+ if (vnodeType == 'function') return EMPTY_STR;
+ return vnodeType == 'string' ? encodeEntities(vnode) : vnode + EMPTY_STR;
}
// Recurse into children / Arrays
@@ -250,7 +251,7 @@ function _renderToString(
parent[CHILDREN] = vnode;
for (let i = 0; i < vnode.length; i++) {
let child = vnode[i];
- if (child == null || typeof child === 'boolean') continue;
+ if (child == null || typeof child == 'boolean') continue;
const childRender = _renderToString(
child,
@@ -262,10 +263,10 @@ function _renderToString(
renderer
);
- if (typeof childRender === 'string') {
+ if (typeof childRender == 'string') {
rendered = rendered + childRender;
} else {
- renderArray = renderArray || [];
+ renderArray = renderArray || new Array(vnode.length);
if (rendered) renderArray.push(rendered);
@@ -294,14 +295,14 @@ function _renderToString(
if (beforeDiff) beforeDiff(vnode);
let type = vnode.type,
- props = vnode.props,
- cctx = context,
- contextType,
- rendered,
- component;
+ props = vnode.props;
// Invoke rendering on Components
- if (typeof type === 'function') {
+ if (typeof type == 'function') {
+ let cctx = context,
+ contextType,
+ rendered,
+ component;
if (type === Fragment) {
// Serialized precompiled JSX.
if ('tpl' in props) {
@@ -315,7 +316,7 @@ function _renderToString(
// Check if we're dealing with a vnode or an array of nodes
if (
- typeof value === 'object' &&
+ typeof value == 'object' &&
(value.constructor === undefined || isArray(value))
) {
out =
@@ -340,9 +341,7 @@ function _renderToString(
} else if ('UNSTABLE_comment' in props) {
// Fragments are the least used components of core that's why
// branching here for comments has the least effect on perf.
- return (
- ''
- );
+ return '';
}
rendered = props.children;
@@ -354,22 +353,15 @@ function _renderToString(
}
let isClassComponent =
- type.prototype && typeof type.prototype.render === 'function';
+ type.prototype && typeof type.prototype.render == 'function';
if (isClassComponent) {
rendered = /**#__NOINLINE__**/ renderClassComponent(vnode, cctx);
component = vnode[COMPONENT];
} else {
- vnode[COMPONENT] = component = {
- __v: vnode,
- props,
- context: cctx,
- // silently drop state updates
- setState: markAsDirty,
- forceUpdate: markAsDirty,
- __d: true,
- // hooks
- __h: []
- };
+ vnode[COMPONENT] = component = /**#__NOINLINE__**/ createComponent(
+ vnode,
+ cctx
+ );
// If a hook invokes setState() to invalidate the component during rendering,
// re-render it up to 25 times to allow "settling" of memoized states.
@@ -402,7 +394,7 @@ function _renderToString(
rendered != null &&
rendered.type === Fragment &&
rendered.key == null &&
- !('tpl' in rendered.props);
+ rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
@@ -416,8 +408,6 @@ function _renderToString(
renderer
);
} catch (err) {
- let str = EMPTY_STR;
-
if (type.getDerivedStateFromError) {
component[NEXT_STATE] = type.getDerivedStateFromError(err);
}
@@ -438,10 +428,10 @@ function _renderToString(
rendered != null &&
rendered.type === Fragment &&
rendered.key == null &&
- !('tpl' in rendered.props);
+ rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
- str = _renderToString(
+ return _renderToString(
rendered,
context,
isSvgMode,
@@ -452,7 +442,7 @@ function _renderToString(
);
}
- return str;
+ return EMPTY_STR;
} finally {
if (afterDiff) afterDiff(vnode);
vnode[PARENT] = null;
@@ -468,7 +458,7 @@ function _renderToString(
rendered != null &&
rendered.type === Fragment &&
rendered.key == null &&
- !('tpl' in rendered.props);
+ rendered.props.tpl == null;
rendered = isTopLevelFragment ? rendered.props.children : rendered;
try {
@@ -513,7 +503,7 @@ function _renderToString(
if (!asyncMode) throw error;
- if (!error || typeof error.then !== 'function') throw error;
+ if (!error || typeof error.then != 'function') throw error;
const renderNestedChildren = () => {
try {
@@ -527,7 +517,7 @@ function _renderToString(
renderer
);
} catch (e) {
- if (!e || typeof e.then !== 'function') throw e;
+ if (!e || typeof e.then != 'function') throw e;
return e.then(
() =>
@@ -557,7 +547,7 @@ function _renderToString(
for (let name in props) {
let v = props[name];
- if (typeof v === 'function') continue;
+ if (typeof v == 'function') continue;
switch (name) {
case 'children':
@@ -663,7 +653,7 @@ function _renderToString(
' ' +
name +
'="' +
- (typeof v === 'string' ? encodeEntities(v) : v + EMPTY_STR) +
+ (typeof v == 'string' ? encodeEntities(v) : v + EMPTY_STR) +
'"';
}
}
@@ -711,7 +701,7 @@ function _renderToString(
const startTag = s + '>';
if (isArray(html)) return [startTag, ...html, endTag];
- else if (typeof html !== 'string') return [startTag, html, endTag];
+ else if (typeof html != 'string') return [startTag, html, endTag];
return startTag + html + endTag;
}
diff --git a/src/lib/util.js b/src/lib/util.js
index 691373c..e0002a8 100644
--- a/src/lib/util.js
+++ b/src/lib/util.js
@@ -147,7 +147,7 @@ export function createComponent(vnode, context) {
forceUpdate: markAsDirty,
__d: true,
// hooks
- __h: []
+ __h: new Array(0)
};
}