react-simple-ssr/renderToHtml.js

128 lines
3.1 KiB
JavaScript

// A function that converts react-test-renderer's toJSON() result
// to HTML usable for React.hydrate()
//
// (c) Vitaliy Filippov 2021+
// License: Dual-license MPL 1.1+ or GNU LGPL 3.0+
const enclosedTags = {
br: true,
hr: true,
input: true,
img: true,
link: true,
source: true,
col: true,
area: true,
base: true,
meta: true,
embed: true,
param: true,
track: true,
wbr: true,
keygen: true,
};
const boolAttrs = {
checked: true,
selected: true,
readonly: true,
defer: true,
deferred: true,
disabled: true,
hidden: true,
multiple: true,
required: true,
reversed: true,
};
function renderToHtml(tree)
{
if (tree instanceof Array)
{
return tree.map(renderToHtml).join('');
}
else if (tree instanceof Object)
{
if (typeof(tree.type) == 'string')
{
let tag = tree.type.toLowerCase();
let children = tree.children;
let html = '<'+tag;
let k, v;
let esc = true;
for (k in tree.props)
{
v = tree.props[k];
k = k.toLowerCase();
if (k == 'classname')
{
k = 'class';
}
else if (k == 'htmlfor')
{
k = 'for';
}
else if (k == 'xlinkhref')
{
k = 'xlink:href';
}
else if (boolAttrs[k])
{
if (v)
html += ' '+k;
continue;
}
else if (k == 'style' && v instanceof Object)
{
v = Object.keys(v).map(sk => sk.replace(/[A-Z]/g, m => '-'+m.toLowerCase())+':'+v[sk]).join(';');
}
else if (k == 'value' && tag == 'textarea')
{
children = v;
continue;
}
else if (k == 'dangerouslysetinnerhtml')
{
children = v == null ? '' : v;
esc = false;
continue;
}
if (v == null || typeof v == 'function')
{
continue;
}
html += ' '+k+'="'+htmlspecialchars(''+v)+'"';
}
if (!enclosedTags[tag])
{
html += '>'+(esc ? renderToHtml(children) : children)+'</'+tag+'>';
}
else
{
html += ' />';
}
return html;
}
else
{
return renderToHtml(tree.children);
}
}
else if (tree != null)
{
return htmlspecialchars(tree);
}
return '';
}
function htmlspecialchars(text)
{
return (''+text).replace(/&/g, '&amp;')
.replace(/'/g, '&apos;') // '
.replace(/"/g, '&quot;') // "
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
module.exports = renderToHtml;