import { objectKeys, encodeEntities, indent, isLargeString, styleObjToCss, assign, getNodeProps } from './util';
const SHALLOW = { shallow: true };
// components without names, kept as a hash for later comparison to return consistent UnnamedComponentXX names.
const UNNAMED = [];
const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
/** Render Preact JSX + Components to an HTML string.
* @name render
* @function
* @param {VNode} vnode JSX VNode to render.
* @param {Object} [context={}] Optionally pass an initial context object through the render path.
* @param {Object} [options={}] Rendering options
* @param {Boolean} [options.shallow=false] If `true`, renders nested Components as HTML elements (``).
* @param {Boolean} [options.xml=false] If `true`, uses self-closing tags for elements without children.
* @param {Boolean} [options.pretty=false] If `true`, adds whitespace for readability
*/
renderToString.render = renderToString;
/** Only render elements, leaving Components inline as ``.
* This method is just a convenience alias for `render(vnode, context, { shallow:true })`
* @name shallow
* @function
* @param {VNode} vnode JSX VNode to render.
* @param {Object} [context={}] Optionally pass an initial context object through the render path.
*/
let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW);
/** The default export is an alias of `render()`. */
function renderToString(vnode, context, opts, inner, isSvgMode) {
if (vnode==null || typeof vnode==='boolean') {
return '';
}
let nodeName = vnode.nodeName,
attributes = vnode.attributes,
isComponent = false;
context = context || {};
opts = opts || {};
let pretty = opts.pretty,
indentChar = typeof pretty==='string' ? pretty : '\t';
// #text nodes
if (typeof vnode!=='object' && !nodeName) {
return encodeEntities(vnode);
}
// components
if (typeof nodeName==='function') {
isComponent = true;
if (opts.shallow && (inner || opts.renderRootComponent===false)) {
nodeName = getComponentName(nodeName);
}
else {
let props = getNodeProps(vnode),
rendered;
if (!nodeName.prototype || typeof nodeName.prototype.render!=='function') {
// stateless functional components
rendered = nodeName(props, context);
}
else {
// class-based components
let c = new nodeName(props, context);
// turn off stateful re-rendering:
c._disable = c.__x = true;
c.props = props;
c.context = context;
if (c.componentWillMount) c.componentWillMount();
rendered = c.render(c.props, c.state, c.context);
if (c.getChildContext) {
context = assign(assign({}, context), c.getChildContext());
}
}
return renderToString(rendered, context, opts, opts.shallowHighOrder!==false);
}
}
// render JSX to HTML
let s = '', html;
if (attributes) {
let attrs = objectKeys(attributes);
// allow sorting lexicographically for more determinism (useful for tests, such as via preact-jsx-chai)
if (opts && opts.sortAttributes===true) attrs.sort();
for (let i=0; i]/)) continue;
if (!(opts && opts.allAttributes) && (name==='key' || name==='ref')) continue;
if (name==='className') {
if (attributes.class) continue;
name = 'class';
}
else if (isSvgMode && name.match(/^xlink:?./)) {
name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
}
if (name==='style' && v && typeof v==='object') {
v = styleObjToCss(v);
}
let hooked = opts.attributeHook && opts.attributeHook(name, v, context, opts, isComponent);
if (hooked || hooked==='') {
s += hooked;
continue;
}
if (name==='dangerouslySetInnerHTML') {
html = v && v.__html;
}
else if ((v || v===0 || v==='') && typeof v!=='function') {
if (v===true || v==='') {
v = name;
// in non-xml mode, allow boolean attributes
if (!opts || !opts.xml) {
s += ' ' + name;
continue;
}
}
s += ` ${name}="${encodeEntities(v)}"`;
}
}
}
// account for >1 multiline attribute
let sub = s.replace(/^\n\s*/, ' ');
if (sub!==s && !~sub.indexOf('\n')) s = sub;
else if (pretty && ~s.indexOf('\n')) s += '\n';
s = `<${nodeName}${s}>`;
if (String(nodeName).match(/[\s\n\\/='"\0<>]/)) throw s;
let isVoid = String(nodeName).match(VOID_ELEMENTS);
if (isVoid) s = s.replace(/>$/, ' />');
let pieces = [];
if (html) {
// if multiline, indent.
if (pretty && isLargeString(html)) {
html = '\n' + indentChar + indent(html, indentChar);
}
s += html;
}
else if (vnode.children) {
let hasLarge = ~s.indexOf('\n');
for (let i=0; i';
}
if (!isVoid) {
if (pretty && ~s.indexOf('\n')) s += '\n';
s += `${nodeName}>`;
}
return s;
}
function getComponentName(component) {
return component.displayName || component!==Function && component.name || getFallbackComponentName(component);
}
function getFallbackComponentName(component) {
let str = Function.prototype.toString.call(component),
name = (str.match(/^\s*function\s+([^( ]+)/) || '')[1];
if (!name) {
// search for an existing indexed name for the given component:
let index = -1;
for (let i=UNNAMED.length; i--; ) {
if (UNNAMED[i]===component) {
index = i;
break;
}
}
// not found, create a new indexed name:
if (index<0) {
index = UNNAMED.push(component) - 1;
}
name = `UnnamedComponent${index}`;
}
return name;
}
renderToString.shallowRender = shallowRender;
export default renderToString;
export {
renderToString as render,
renderToString,
shallowRender
};