The vanilla alternative for writing JSX-based React applications
git clone git://soophie.de/figure
1
# tlx2
Template literals syntax for React Hooks1
# segment2
Reactive template literals for React
5
import {useState, createElement} from 'https://cdn.skypack.dev/react';6
import {render} from 'https://cdn.skypack.dev/react-dom';7
import {createTlx, css} from '../tlx.js';9
const tlx = createTlx(createElement);7
import {segment, html, css} from '../segment.js';9
const Range = ({index, value, setValue}) => {10
const style = css`
28
}29
`;33
return tlx`31
return html`32
<div class="range v-${index}-${value}">33
${style}34
<input
72
const hex = (dec) => dec.toString(16).toUpperCase().padStart(2, '0');76
return tlx`74
return html`75
<div class="picker">76
${style}77
<div class="preview"></div>
83
</div>84
`;85
}87
const app = document.querySelector('#app');88
segment({createElement});89
render(createElement(Picker), app);91
</script>
1
export const defCss = ({html, compose, feed}) => {2
return (parts, ...props) => {3
const [css, refs] = compose(parts, props);4
return html`5
<style type="text/css">6
${feed(css, refs).join('')}7
</style>8
`;9
};10
}12
export const defStyle = ({compose, feed}) => {13
return (parts, ...props) => {14
const [css, refs] = compose(parts, props);15
const styles = {};16
for(const item of css.split(';')) {17
if(item.trim().length === 0) {18
break;19
}20
let [key, value] = item.split(':');21
key = key.trim();22
let match = null;23
if((match = /-(\w)/.exec(key)) !== null) {24
const char = match[1];25
key = key.replace(`-${char}`, char.toUpperCase());26
}27
value = value.trim();28
styles[key] = feed(value, refs).join('');29
}30
return styles;31
};32
}
1
const parser = new DOMParser();2
let count = 1000;3
let create = null;5
const segment = ({createElement}) => {6
create = createElement;7
}9
const html = (strings, ...props) => {10
const [html, refs] = compose(strings, props);11
let dom;12
try {13
dom = parser.parseFromString(html, 'text/html');14
}15
catch(error) {16
console.error(error);17
throw 'Invalid DOM structure!';18
}19
const node = dom.body.childNodes[0] ?? dom.head.childNodes[0];20
const elements = render(node, refs);21
if(elements.length === 0) {22
throw 'No DOM element was returned!';23
}24
if(elements.length > 1) {25
console.warn('Only one DOM element can be returned!');26
}27
return elements[0];28
}30
/**31
* Joins the template literal strings together and replaces the properties with references.32
* The properties are being mapped to there corresponding references and with the populated33
* HTML string returned.34
*35
* @param {string[]} strings - The template literal strings36
* @param {any[]} props - The template literal properties37
* @return {any[]} The joined HTML string and the properties mapped to there references.38
*/39
const compose = (strings, props) => {40
const refs = {};41
let string = '';42
for(let i = 0; i < strings.length; i++) {43
string += strings[i];44
if(props[i] !== undefined) {45
const uid = `$tlx-${count++}`;46
refs[uid] = props[i];47
string += uid ?? '';48
}49
}50
return [string.trim(), refs];51
}53
/**54
* Injects the properties into the corresponding reference locations of the string.55
*56
* @param {string} string - The string containing references57
* @param {any[]} refs - The properties mapped to there references58
* @return {any[]} The string populate with the passed properties59
*/60
const feed = (string, refs) => {61
const expr = /\$tlx-\d+/g;62
let values = [];63
let match = null;64
let last = 0;65
while((match = expr.exec(string)) !== null) {66
const index = match.index;67
const uid = match[0];68
values.push(string.substring(last, index));69
let value = refs[uid];70
if(value instanceof Function) {71
value = value();72
}73
values.push(value);74
last = index + uid.length;75
}76
values.push(string.substring(last));77
return values;78
}80
const render = (node, refs) => {81
if(node.nodeType === Node.TEXT_NODE) {82
return feed(node.textContent, refs);83
}84
if(node.nodeType === Node.COMMENT_NODE) {85
return [];86
}87
const tag = node.tagName;88
const attributes = {};89
for(const attribute of node.attributes) {90
const key = attribute.name;91
const value = attribute.textContent;92
let isDynamic = false;93
for(const ref in refs) {94
if(value === ref) {95
let match = null;96
if((match = /^@(\w+)$/.exec(key)) !== null) {97
const event = match[1];98
attributes[`on${event.substring(0, 1).toUpperCase()}${event.substring(1)}`] = refs[ref];99
}100
else {101
attributes[key] = refs[ref];102
}103
isDynamic = true;104
break;105
}106
}107
if(!isDynamic) {108
if(key === 'style') {109
const styles = {};110
for(const item of value.split(';')) {111
if(item.trim().length === 0) {112
break;113
}114
let [key, value] = item.split(':');115
key = key.trim();116
let match = null;117
if((match = /-(\w)/.exec(key)) !== null) {118
const char = match[1];119
key = key.replace(`-${char}`, char.toUpperCase());120
}121
value = value.trim();122
styles[key] = feed(value, refs).join('');123
}124
attributes[key] = styles;125
}126
else {127
attributes[key] = feed(value, refs).join('');128
}129
}130
}131
const children = [];132
(node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));133
return [create(tag, attributes, ...children)];134
}136
import {defCss, defStyle} from './mods.js';138
const css = defCss({html, compose, feed});139
const style = defStyle({html, compose, feed});141
export { segment, html, css, style };
1
const parser = new DOMParser();2
let counter = 1000;3
let tlx = null;5
export const createTlx = (createElement) => {6
function parse(parts, ...props) {7
const [html, refs] = ltr(parts, props);8
const dom = parser.parseFromString(html, 'text/html');9
const node = dom.body.childNodes[0] ?? dom.head.childNodes[0];10
const elements = render(node, refs);11
if(elements.length !== 1) {12
throw 'invalid VDOM structure!';13
}14
return elements[0];15
}17
function render(node, refs) {18
if(node.nodeType === Node.TEXT_NODE) {19
return apply(node.textContent, refs);20
}21
if(node.nodeType === Node.COMMENT_NODE) {22
return [];23
}24
const tag = node.tagName;25
const attributes = {};26
for(const attribute of node.attributes) {27
const key = attribute.name;28
const value = attribute.textContent;29
let isDynamic = false;30
for(const ref in refs) {31
if(value === ref) {32
let match = null;33
if((match = /^@(\w+)$/.exec(key)) !== null) {34
const event = match[1];35
attributes[`on${event.substr(0, 1).toUpperCase()}${event.substr(1)}`] = refs[ref];36
}37
else {38
attributes[key] = refs[ref];39
}40
isDynamic = true;41
break;42
}43
}44
if(!isDynamic) {45
if(key === 'style') {46
const styles = {};47
for(const item of value.split(';')) {48
if(item.trim().length === 0) {49
break;50
}51
let [key, value] = item.split(':');52
key = key.trim();53
let match = null;54
if((match = /-(\w)/.exec(key)) !== null) {55
const char = match[1];56
key = key.replace(`-${char}`, char.toUpperCase());57
}58
value = value.trim();59
styles[key] = apply(value, refs).join('');60
}61
attributes[key] = styles;62
}63
else {64
attributes[key] = apply(value, refs).join('');65
}66
}67
}68
const children = [];69
(node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));70
return [createElement(tag, attributes, ...children)];71
}73
tlx = parse;74
return tlx;75
}77
export const css = (parts, ...props) => {78
const [css, refs] = ltr(parts, props);79
return tlx`80
<style type="text/css">81
${apply(css, refs).join('')}82
</style>83
`;84
}86
export const style = (parts, ...props) => {87
const [css, refs] = ltr(parts, props);88
const styles = {};89
for(const item of css.split(';')) {90
if(item.trim().length === 0) {91
break;92
}93
let [key, value] = item.split(':');94
key = key.trim();95
let match = null;96
if((match = /-(\w)/.exec(key)) !== null) {97
const char = match[1];98
key = key.replace(`-${char}`, char.toUpperCase());99
}100
value = value.trim();101
styles[key] = apply(value, refs).join('');102
}103
return styles;104
}106
function apply(value, refs) {107
let values = [];108
const expr = /\$tlx-\d+/g;109
let match = null;110
let last = 0;111
while((match = expr.exec(value)) !== null) {112
const index = match.index;113
values.push(value.substring(last, index));114
let refValue = refs[match[0]];115
if(refValue instanceof Function) {116
refValue = refValue();117
}118
values.push(refValue);119
last = index + match[0].length;120
}121
values.push(value.substring(last));122
return values;123
}125
function ltr(parts, props) {126
let string = '';127
const refs = {};128
for(let i = 0; i < parts.length; i++) {129
string += parts[i];130
if(props[i] !== undefined) {131
const id = `$tlx-${counter}`;132
refs[id] = props[i];133
string += id ?? '';134
counter++;135
}136
}137
return [string.trim(), refs];138
}