UNPKG

lupine.web

Version:

lupine.web is a extremely fast, small size and lightweight frontend framework, using React TSX syntax.

158 lines (138 loc) 5.95 kB
import { processStyle } from './bind-styles'; import { VNodeType, VNodeProps } from '../jsx'; import { ComponentStateStore, evaluateComponentWithStore } from './use-state'; import { renderAttribute, genUniqueId } from './render-attribute'; const selfClosingTags = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr', ]; // shared state, global throttle for requests but safe let _yieldCounter = 0; const YIELD_THRESHOLD = 50; import { encodeHtml } from '../lib/encode-html'; async function renderChildrenAsync(html: string[], children: any, uniqueClassName?: string, referToCssId?: string) { if (typeof children === 'string') { html.push(encodeHtml(children)); } else if (children === false || children === null || typeof children === 'undefined') { // add nothing } else if (typeof children === 'number' || typeof children === 'boolean') { // true will be added html.push(children.toString()); } else if (Array.isArray(children)) { for (const item of children) { await renderChildrenAsync(html, item, uniqueClassName, referToCssId); } } else if (children.type && children.props) { await renderComponentAsync(children.type, children.props, uniqueClassName, referToCssId); html.push(...children.props._html); children.props._html.length = 0; } else { console.warn('Unexpected', children); } } export const renderComponentAsync = async (type: VNodeType<any>, props: VNodeProps<any>, uniqueClassName?: string, referToCssId?: string) => { _yieldCounter++; if (_yieldCounter >= YIELD_THRESHOLD) { _yieldCounter = 0; await new Promise((resolve) => { setTimeout(resolve, 0); }); } if (Array.isArray(props)) { const jsxNodes = { type: 'Fragment', props: { children: props } } as any; await renderComponentAsync(jsxNodes.type, jsxNodes.props, uniqueClassName, referToCssId); return; } props._html = []; if (typeof type === 'function') { const store = new ComponentStateStore(type, props); props._result = await evaluateComponentWithStore(store); if (props._result === null || props._result === undefined || props._result === false) { props._result = { type: 'Fragment', props }; } if (typeof props._result.type === 'function') { await renderComponentAsync(props._result.type, props._result.props, uniqueClassName, referToCssId); if (props._result.props._html) { props._html.push(...props._result.props._html); props._result.props._html.length = 0; } return; } } const newType = (props._result && props._result.type) || type; const newProps = (props._result && props._result.props) || props; if (typeof newType === 'string') { // if (newProps._id) { // console.warn('This component reference is used more than once:', newProps); // } if (newProps['class'] && newProps['className']) { console.warn(`One tag can't have both class and className, and className will be ignored.`, newProps); delete newProps['className']; } else if (newProps['className']) { newProps['class'] = newProps['className']; delete newProps['className']; } let newUniqueClassName = uniqueClassName; // if having css, we need to replace & for mapping css and classnames // if having ref, we still need to replace & for searching if (newProps['css'] || newProps['ref']) { newUniqueClassName = genUniqueId(newProps); if (!newProps['class']) newProps['class'] = newUniqueClassName; if (newProps['css'] || !referToCssId) referToCssId = newUniqueClassName; } let newReferToCssId = referToCssId; if (newProps['ref']) { if (newProps['ref'].referToCssId) { newReferToCssId = newProps['ref'].referToCssId; } // save for outerComponent/innerComponent loading newProps['ref']._refCssId = newReferToCssId; newProps['ref']._cssName = newUniqueClassName; } const attrs = renderAttribute(newType, newProps, { type, props }, newUniqueClassName, newReferToCssId); if (selfClosingTags.includes(newType.toLowerCase())) { if (newType === 'Fragment' && newProps.ref) { props._html.push(`<!--[ref-${newProps._id}]--><fragment ${newProps._id}></fragment>`); } else if (newType !== 'Fragment') { props._html.push(`<${newType}${attrs ? ' ' : ''}${attrs} />`); } } else { if (newType === 'Fragment' && newProps.ref) { props._html.push(`<!--[ref-${newProps._id}]--><fragment ${newProps._id}></fragment>`); } else if (newType !== 'Fragment') { props._html.push(`<${newType}${attrs ? ' ' : ''}${attrs}>`); } if (newProps['css'] && newUniqueClassName) { // notice: if referToCssId is provided, it should be only used in classnames. const cssText = processStyle(newUniqueClassName, newProps['css'], false).join(''); props._html.push(`<style id="sty-${newUniqueClassName}">${cssText}</style>`); } if (newProps.children || newProps.children === 0) { await renderChildrenAsync(props._html, newProps.children, newUniqueClassName, newReferToCssId); } else if (newProps['dangerouslySetInnerHTML']) { props._html.push(newProps['dangerouslySetInnerHTML']); } if (newType === 'Fragment' && newProps.ref) { props._html.push(`<!--/[ref-${newProps._id}]-->`); } else if (newType !== 'Fragment') { props._html.push(`</${newType}>`); } } } else if (newType.name === 'Fragment') { await renderChildrenAsync(props._html, newProps.children, uniqueClassName, referToCssId); } else { console.warn('Unknown type: ', type, props, newType, newProps); } };