Last active
January 29, 2019 19:33
-
-
Save i-like-robots/eff7a94ddde1b5a8a0a159fd654bff87 to your computer and use it in GitHub Desktop.
A quick experiment refactoring Hyperons to output real DOM nodes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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 = { | |
'"': '"', | |
"'": ''', | |
'&': '&', | |
'<': '<', | |
'>': '>' | |
}; | |
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; | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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