Skip to content

Instantly share code, notes, and snippets.

@i-like-robots
Last active January 29, 2019 19:33
Show Gist options
  • Save i-like-robots/eff7a94ddde1b5a8a0a159fd654bff87 to your computer and use it in GitHub Desktop.
Save i-like-robots/eff7a94ddde1b5a8a0a159fd654bff87 to your computer and use it in GitHub Desktop.
A quick experiment refactoring Hyperons to output real DOM nodes
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.hyperons = {}));
}(this, function (exports) {
'use strict';
/**
* Base Component class
* @param {object} props The initial component props
*/
function Component(props) {
this.props = props;
this.state = this.state || {};
}
Component.prototype.render = function() {};
var Fragment = Symbol('Fragment');
function createElement(type, props) {
var children = [], len = arguments.length - 2;
while ( len-- > 0 ) children[ len ] = arguments[ len + 2 ];
props = props || {};
props.children = children.length === 0 && props.children ? props.children : children;
if (type && type.defaultProps) {
for (var prop in type.defaultProps) {
if (props[prop] === undefined) {
props[prop] = type.defaultProps[prop];
}
}
}
return { type: type, props: props }
}
var UPPERCASE = /([A-Z])/g;
var MS = /^ms-/;
var UNITLESS_PROPS = new Set([
'animationIterationCount',
'columns',
'columnCount',
'flex',
'flexGrow',
'flexShrink',
'fontWeight',
'gridColumn',
'gridColumnEnd',
'gridColumnStart',
'gridRow',
'gridRowEnd',
'gridRowStart',
'lineHeight',
'opacity',
'order',
'orphans',
'tabSize',
'widows',
'zIndex',
'zoom'
]);
var CACHE = {};
function hyphenateChar(char) {
return '-' + char.toLowerCase()
}
function hyphenateString(prop) {
return prop.replace(UPPERCASE, hyphenateChar).replace(MS, '-ms-')
}
function stringifyStyles(styles) {
var out = '';
for (var prop in styles) {
var value = styles[prop];
if (value != null) {
var unit = typeof value === 'number' && value !== 0 && !UNITLESS_PROPS.has(prop) ? 'px' : '';
prop = CACHE[prop] || (CACHE[prop] = hyphenateString(prop));
out += prop + ":" + value + unit + ";";
}
}
return out
}
// https://www.w3.org/International/questions/qa-escapes#use
var ESCAPE_REGEXP = /["'&<>]/g;
var ESCAPE_MAP = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
};
function escapeChar(char) {
return ESCAPE_MAP[char]
}
function escapeString(value) {
if (!ESCAPE_REGEXP.test(value)) {
return value
}
return String(value).replace(ESCAPE_REGEXP, escapeChar)
}
var ATTR_ALIASES = {
acceptCharset: 'acceptcharset',
accessKey: 'accesskey',
className: 'class',
colSpan: 'colspan',
crossOrigin: 'crossorigin',
dateTime: 'datetime',
defaultChecked: 'checked',
defaultSelected: 'selected',
defaultValue: 'value',
htmlFor: 'for',
longDesc: 'longdesc',
maxLength: 'maxlength',
readOnly: 'readonly',
rowSpan: 'rowspan',
tabIndex: 'tabindex',
useMap: 'usemap'
};
// <https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes>
var BOOLEAN_ATTRS = new Set([
'async',
'allowfullscreen',
'allowpaymentrequest',
'autofocus',
'autoplay',
'checked',
'controls',
'default',
'defer',
'disabled',
'formnovalidate',
'hidden',
'ismap',
'multiple',
'muted',
'novalidate',
'nowrap',
'open',
'readonly',
'required',
'reversed',
'selected'
]);
// https://www.w3.org/TR/html/syntax.html#void-elements
var VOID_ELEMENTS = new Set([
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr'
]);
var EMPTY_OBJECT = Object.freeze({});
function renderToDOM(element) {
if (typeof element === 'string') {
return escapeString(element)
} else if (typeof element === 'number') {
return String(element)
} else if (typeof element === 'boolean' || element == null) {
return ''
} else if (Array.isArray(element)) {
var children = [];
for (var i = 0, len = element.length; i < len; i++) {
var output = renderToDOM(element[i]);
if (output) {
children.push(output);
}
}
return children
}
var type = element.type;
if (type) {
var props = element.props || EMPTY_OBJECT;
if (type.prototype && type.prototype.render) {
var instance = new type(props);
return renderToDOM(instance.render())
} else if (typeof type === 'function') {
return renderToDOM(type(props))
} else if (type === Fragment) {
return renderToDOM(props.children)
} else if (typeof type === 'string') {
var node = document.createElement(type);
var innerHTML;
for (var prop in props) {
var value = props[prop];
if (prop === 'children' || prop === 'key' || prop === 'ref') {
// Whatever...
} else if (prop === 'style') {
node.setAttribute('style', stringifyStyles(value));
} else if (prop === 'dangerouslySetInnerHTML') {
innerHTML = value.__html;
} else {
var name = ATTR_ALIASES[prop] || prop;
if (BOOLEAN_ATTRS.has(name)) {
node[name] = value;
} else if (value != null) {
node.setAttribute(name, value);
}
}
}
if (!VOID_ELEMENTS.has(type)) {
if (innerHTML) {
node.innerHTML = innerHTML;
} else {
props.children.map(function (child) {
var output = [].concat(renderToDOM(child));
output.forEach(function (chunk) {
if (chunk) {
if (typeof chunk === 'string') {
node.innerHTML += chunk;
} else if (chunk instanceof HTMLElement) {
node.appendChild(chunk);
}
}
});
});
}
}
return node
}
}
}
exports.Fragment = Fragment;
exports.Component = Component;
exports.createElement = createElement;
exports.h = createElement;
exports.renderToString = renderToDOM;
exports.render = renderToDOM;
}));
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hyperons test</title>
</head>
<body>
<script src="hyperons.js"></script>
<script>
const { h, render } = hyperons
const SomeDiv = (props) => h('div', { className: 'test-123' }, props.children)
const app = h(SomeDiv, null, 'Hello, World!')
document.body.appendChild(render(app))
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment