nano-jsx
Version:
SSR first, lightweight 1kB JSX library.
275 lines • 10.2 kB
JavaScript
import './types.js';
export const isSSR = () => typeof _nano !== 'undefined' && _nano.isSSR === true;
/** Creates a new Microtask using Promise() */
export const tick = Promise.prototype.then.bind(Promise.resolve());
export const removeAllChildNodes = (parent) => {
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
};
// https://stackoverflow.com/a/7616484/12656855
export const strToHash = (s) => {
let hash = 0;
for (let i = 0; i < s.length; i++) {
const chr = s.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return Math.abs(hash).toString(32);
};
export const appendChildren = (element, children, escape = true) => {
// if the child is an html element
if (!Array.isArray(children)) {
appendChildren(element, [children], escape);
return;
}
// htmlCollection to array
if (typeof children === 'object')
children = Array.prototype.slice.call(children);
children.forEach(child => {
// if child is an array of children, append them instead
if (Array.isArray(child))
appendChildren(element, child, escape);
else {
// render the component
const c = _render(child);
if (typeof c !== 'undefined') {
// if c is an array of children, append them instead
if (Array.isArray(c))
appendChildren(element, c, escape);
// apply the component to parent element
else {
if (isSSR() && !escape)
element.appendChild(c.nodeType == null ? c.toString() : c);
else
element.appendChild(c.nodeType == null ? document.createTextNode(c.toString()) : c);
}
}
}
});
};
/**
* A simple component for rendering SVGs
*/
const SVG = (props) => {
const child = props.children[0];
const attrs = child.attributes;
if (isSSR())
return child;
const svg = hNS('svg');
for (let i = attrs.length - 1; i >= 0; i--) {
svg.setAttribute(attrs[i].name, attrs[i].value);
}
svg.innerHTML = child.innerHTML;
return svg;
};
/** Returns the populated parent if available else one child or an array of children */
export const render = (component, parent = null, removeChildNodes = true) => {
let el = _render(component);
if (Array.isArray(el)) {
el = el.map(e => _render(e));
if (el.length === 1)
el = el[0];
}
if (parent) {
if (removeChildNodes)
removeAllChildNodes(parent);
// if parent and child are the same, we replace the parent instead of appending to it
if (el && parent.id && parent.id === el.id && parent.parentElement) {
parent.parentElement.replaceChild(el, parent);
}
else {
// append element(s) to the parent
if (Array.isArray(el))
el.forEach((e) => {
appendChildren(parent, _render(e));
//parent.appendChild(_render(e))
});
else
appendChildren(parent, _render(el));
}
return parent;
}
// returning one child or an array of children
else {
if (isSSR() && !Array.isArray(el))
return [el];
return el;
}
};
export const hydrate = render;
export const _render = (comp) => {
// null, false, undefined
if (comp === null || comp === false || typeof comp === 'undefined')
return [];
// string, number
if (typeof comp === 'string' || typeof comp === 'number')
return comp.toString();
// SVGElement
if (comp.tagName && comp.tagName.toLowerCase() === 'svg')
return SVG({ children: [comp] });
// HTMLElement
if (comp.tagName)
return comp;
// TEXTNode (Node.TEXT_NODE === 3)
if (comp && comp.nodeType === 3)
return comp;
// Class Component
if (comp && comp.component && comp.component.isClass)
return renderClassComponent(comp);
// Class Component (Uninitialized)
if (comp.isClass)
return renderClassComponent({ component: comp, props: {} });
// Functional Component
if (comp.component && typeof comp.component === 'function')
return renderFunctionalComponent(comp);
// Array (render each child and return the array) (is probably a fragment)
if (Array.isArray(comp))
return comp.map(c => _render(c)).flat();
// function
if (typeof comp === 'function' && !comp.isClass)
return _render(comp());
// if component is a HTMLElement (rare case)
if (comp.component && comp.component.tagName && typeof comp.component.tagName === 'string')
return _render(comp.component);
// (rare case)
if (Array.isArray(comp.component))
return _render(comp.component);
// (rare case)
if (comp.component)
return _render(comp.component);
// object
if (typeof comp === 'object')
return [];
console.warn('Something unexpected happened with:', comp);
};
const renderFunctionalComponent = (fncComp) => {
const { component, props } = fncComp;
return _render(component(props));
};
const renderClassComponent = (classComp) => {
const { component, props } = classComp;
// calc hash
const hash = strToHash(component.toString());
// make hash accessible in constructor, without passing it to it
component.prototype._getHash = () => hash;
const Component = new component(props);
if (!isSSR())
Component.willMount();
let el = Component.render();
el = _render(el);
Component.elements = el;
// pass the component instance as ref
if (props && props.ref)
props.ref(Component);
if (!isSSR())
tick(() => {
Component._didMount();
});
return el;
};
const hNS = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
// https://stackoverflow.com/a/42405694/12656855
export const h = (tagNameOrComponent, props = {}, ...children) => {
// if children is passed as props, merge with ...children
if (props && props.children) {
if (Array.isArray(children)) {
if (Array.isArray(props.children))
children = [...props.children, ...children];
else
children.push(props.children);
}
else {
if (Array.isArray(props.children))
children = props.children;
else
children = [props.children];
}
}
// render WebComponent in SSR
if (isSSR() && _nano.ssrTricks.isWebComponent(tagNameOrComponent)) {
const element = _nano.ssrTricks.renderWebComponent(tagNameOrComponent, props, children, _render);
if (element === null)
return `ERROR: "<${tagNameOrComponent} />"`;
else
return element;
}
// if tagNameOrComponent is a component
if (typeof tagNameOrComponent !== 'string')
return { component: tagNameOrComponent, props: { ...props, children: children } };
// custom message if document is not defined in SSR
try {
if (isSSR() && typeof tagNameOrComponent === 'string' && !document)
throw new Error('document is not defined');
}
catch (err) {
console.log('ERROR:', err.message, '\n > Please read: https://github.com/nanojsx/nano/issues/106');
}
let ref;
const element = tagNameOrComponent === 'svg'
? hNS('svg')
: document.createElement(tagNameOrComponent);
// check if the element includes the event (for example 'oninput')
const isEvent = (el, p) => {
// check if the event begins with 'on'
if (0 !== p.indexOf('on'))
return false;
// we return true if SSR, since otherwise it will get rendered
if (el._ssr)
return true;
// check if the event is present in the element as object (null) or as function
return typeof el[p] === 'object' || typeof el[p] === 'function';
};
for (const p in props) {
// https://stackoverflow.com/a/45205645/12656855
// style object to style string
if (p === 'style' && typeof props[p] === 'object') {
const styles = Object.keys(props[p])
.map(k => `${k}:${props[p][k]}`)
.join(';')
.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
props[p] = `${styles};`;
}
// handel ref
if (p === 'ref')
ref = props[p];
// handle events
else if (isEvent(element, p.toLowerCase()))
element.addEventListener(p.toLowerCase().substring(2), (e) => props[p](e));
// dangerouslySetInnerHTML
else if (p === 'dangerouslySetInnerHTML' && props[p].__html) {
if (!isSSR()) {
const fragment = document.createElement('fragment');
fragment.innerHTML = props[p].__html;
element.appendChild(fragment);
}
else {
element.innerHTML = props[p].__html;
}
}
// modern dangerouslySetInnerHTML
else if (p === 'innerHTML' && props[p].__dangerousHtml) {
if (!isSSR()) {
const fragment = document.createElement('fragment');
fragment.innerHTML = props[p].__dangerousHtml;
element.appendChild(fragment);
}
else {
element.innerHTML = props[p].__dangerousHtml;
}
}
// className
else if (/^className$/i.test(p))
element.setAttribute('class', props[p]);
// setAttribute
else if (typeof props[p] !== 'undefined')
element.setAttribute(p, props[p]);
}
// these tags should not be escaped by default (in ssr)
const escape = !['noscript', 'script', 'style'].includes(tagNameOrComponent);
appendChildren(element, children, escape);
if (ref)
ref(element);
return element;
};
//# sourceMappingURL=core.js.map