preact-render-to-string
Version:
Render JSX to an HTML string, with support for Preact components.
182 lines (165 loc) • 4.61 kB
JavaScript
export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
export const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
// Boolean DOM properties that translate to enumerated ('true'/'false') attributes
export const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']);
// DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
export function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
let last = 0,
i = 0,
out = '',
ch = '';
// Seek forward in str until the next entity char:
for (; i < str.length; i++) {
switch (str.charCodeAt(i)) {
case 34:
ch = '"';
break;
case 38:
ch = '&';
break;
case 60:
ch = '<';
break;
default:
continue;
}
// Append skipped/buffered characters and the encoded entity:
if (i !== last) out = out + str.slice(last, i);
out = out + ch;
// Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out = out + str.slice(last, i);
return out;
}
export let indent = (s, char) =>
String(s).replace(/(\n+)/g, '$1' + (char || '\t'));
export let isLargeString = (s, length, ignoreLines) =>
String(s).length > (length || 40) ||
(!ignoreLines && String(s).indexOf('\n') !== -1) ||
String(s).indexOf('<') !== -1;
const JS_TO_CSS = {};
const IS_NON_DIMENSIONAL = new Set([
'animation-iteration-count',
'border-image-outset',
'border-image-slice',
'border-image-width',
'box-flex',
'box-flex-group',
'box-ordinal-group',
'column-count',
'fill-opacity',
'flex',
'flex-grow',
'flex-negative',
'flex-order',
'flex-positive',
'flex-shrink',
'flood-opacity',
'font-weight',
'grid-column',
'grid-row',
'line-clamp',
'line-height',
'opacity',
'order',
'orphans',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'tab-size',
'widows',
'z-index',
'zoom'
]);
const CSS_REGEX = /[A-Z]/g;
// Convert an Object style to a CSSText string
export function styleObjToCss(s) {
let str = '';
for (let prop in s) {
let val = s[prop];
if (val != null && val !== '') {
const name =
prop[0] == '-'
? prop
: JS_TO_CSS[prop] ||
(JS_TO_CSS[prop] = prop.replace(CSS_REGEX, '-$&').toLowerCase());
let suffix = ';';
if (
typeof val === 'number' &&
// Exclude custom-attributes
!name.startsWith('--') &&
!IS_NON_DIMENSIONAL.has(name)
) {
suffix = 'px;';
}
str = str + name + ':' + val + suffix;
}
}
return str || undefined;
}
/**
* Get flattened children from the children prop
* @param {Array} accumulator
* @param {any} children A `props.children` opaque object.
* @returns {Array} accumulator
* @private
*/
export function getChildren(accumulator, children) {
if (Array.isArray(children)) {
children.reduce(getChildren, accumulator);
} else if (children != null && children !== false) {
accumulator.push(children);
}
return accumulator;
}
function markAsDirty() {
this.__d = true;
}
export function createComponent(vnode, context) {
return {
__v: vnode,
context,
props: vnode.props,
// silently drop state updates
setState: markAsDirty,
forceUpdate: markAsDirty,
__d: true,
// hooks
__h: new Array(0)
};
}
// Necessary for createContext api. Setting this property will pass
// the context value as `this.context` just for this component.
export function getContext(nodeName, context) {
let cxType = nodeName.contextType;
let provider = cxType && context[cxType.__c];
return cxType != null
? provider
? provider.props.value
: cxType.__
: context;
}
/**
* @template T
*/
export class Deferred {
constructor() {
// eslint-disable-next-line lines-around-comment
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}