Skip to content

Commit

Permalink
Improved signature with xml, cache, and attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
WebReflection committed Jun 11, 2021
1 parent 39f52e8 commit f60ab65
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 140 deletions.
125 changes: 69 additions & 56 deletions cjs/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use strict';
/*! (c) Andrea Giammarchi - ISC */

class Bound {
constructor(_) {
this._ = _;
}
}

const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
const er = '<☠>';
const re = /^on([A-Z])/;
Expand All @@ -12,14 +18,17 @@ const place = (_, $) => ('@' + $.toLowerCase());
* @param {any} value the attribute value.
* @returns {object} An object with `name` and `value` fields.
*/
const attribute = (name, value) => {
const type = typeof value;
switch (type) {
const defaultAttribute = (name, value) => {
switch (typeof value) {
case 'string':
case 'number':
return {name, value};
case 'boolean':
return {name: '?' + name, value};
case 'object':
case 'undefined':
if (value == null)
return {name, value};
if (value instanceof Bound)
return {name: '.' + name, value: value._};
}
Expand All @@ -35,63 +44,67 @@ exports.bind = bind;
* @param {Map<string,string[]>?} cache A cache to avoid passing along different arrays per same template / values.
* @returns {function} The `h` / pragma function to use with JSX.
*/
const createPragma = (tag, cache) => {
if (!cache)
cache = new Map;
return function h(entry, attributes, ...children) {
const component = typeof entry === 'function';
if (component && !('tagName' in entry)) {
(attributes || (attributes = {})).children = children;
return 'prototype' in entry ? new entry(attributes) : entry(attributes);
const createPragma = (
tag,
{
attribute = defaultAttribute,
cache = new Map,
xml = false
} = {}
) => function h(entry, attributes, ...children) {
const component = typeof entry === 'function';
// avoid dealing with µbe classes
if (component && !('tagName' in entry)) {
// pass {...props, children} to the component
(attributes || (attributes = {})).children = children;
return 'prototype' in entry ? new entry(attributes) : entry(attributes);
}
const template = ['<'];
const args = [template];
let i = 0;
if (component) {
args.push(entry);
i = template.push('') - 1;
}
else
template[i] += entry;
for (const key in attributes) {
const {name, value} = attribute(key, attributes[key]);
template[i] += ` ${name}="`;
args.push(value);
i = template.push('"') - 1;
}
const {length} = children;
template[i] += (length || !xml) ? '>' : ' />';
for (let child, j = 0; j < length; j++) {
child = children[j];
if (typeof child === 'string')
template[i] += child;
else {
args.push(child);
i = template.push('') - 1;
}
const template = ['<'];
const args = [null];
let i = 0;
}
if (
length || (
!xml && (
(component && !empty.test(component.tagName)) ||
!empty.test(entry)
)
)
) {
if (component) {
template[i] += '</';
args.push(entry);
i = template.push('') - 1;
template.push('>');
}
else
template[i] += entry;
for (const key in attributes) {
const {name, value} = attribute(key, attributes[key]);
args.push(value);
template[i] += ` ${name}="`;
i = template.push('"') - 1;
}
template[i] += '>';
const {length} = children;
for (let child, j = 0; j < length; j++) {
child = children[j];
if (typeof child === 'string')
template[i] += child;
else {
args.push(child);
i = template.push('') - 1;
}
}
if (
0 < length ||
(component && !empty.test(component.tagName)) ||
!empty.test(entry)
) {
if (component) {
template[i] += '</';
args.push(entry);
template.push('>');
}
else
template[i] += `</${entry}>`;
}
const whole = template.join(er);
args[0] = cache.get(whole) || template;
if (args[0] === template)
cache.set(whole, template);
return tag.apply(this, args);
};
template[i] += `</${entry}>`;
}
const whole = template.join(er);
args[0] = cache.get(whole) || template;
if (args[0] === template)
cache.set(whole, template);
return tag.apply(this, args);
};
exports.createPragma = createPragma;

function Bound(_) {
this._ = _;
}
127 changes: 69 additions & 58 deletions esm/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/*! (c) Andrea Giammarchi - ISC */

class Bound {
constructor(_) {
this._ = _;
}
}

const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
const er = '<☠>';
const re = /^on([A-Z])/;
Expand All @@ -11,14 +17,17 @@ const place = (_, $) => ('@' + $.toLowerCase());
* @param {any} value the attribute value.
* @returns {object} An object with `name` and `value` fields.
*/
const attribute = (name, value) => {
const type = typeof value;
switch (type) {
const defaultAttribute = (name, value) => {
switch (typeof value) {
case 'string':
case 'number':
return {name, value};
case 'boolean':
return {name: '?' + name, value};
case 'object':
case 'undefined':
if (value == null)
return {name, value};
if (value instanceof Bound)
return {name: '.' + name, value: value._};
}
Expand All @@ -33,64 +42,66 @@ export const bind = value => new Bound(value);
* @param {Map<string,string[]>?} cache A cache to avoid passing along different arrays per same template / values.
* @returns {function} The `h` / pragma function to use with JSX.
*/
export const createPragma = (tag, cache) => {
if (!cache)
cache = new Map;
return function h(entry, attributes, ...children) {
const component = typeof entry === 'function';
// avoid dealing with ube classes
if (component && !('tagName' in entry)) {
// pass {...props, children} to the component
(attributes || (attributes = {})).children = children;
return 'prototype' in entry ? new entry(attributes) : entry(attributes);
export const createPragma = (
tag,
{
attribute = defaultAttribute,
cache = new Map,
xml = false
} = {}
) => function h(entry, attributes, ...children) {
const component = typeof entry === 'function';
// avoid dealing with µbe classes
if (component && !('tagName' in entry)) {
// pass {...props, children} to the component
(attributes || (attributes = {})).children = children;
return 'prototype' in entry ? new entry(attributes) : entry(attributes);
}
const template = ['<'];
const args = [template];
let i = 0;
if (component) {
args.push(entry);
i = template.push('') - 1;
}
else
template[i] += entry;
for (const key in attributes) {
const {name, value} = attribute(key, attributes[key]);
template[i] += ` ${name}="`;
args.push(value);
i = template.push('"') - 1;
}
const {length} = children;
template[i] += (length || !xml) ? '>' : ' />';
for (let child, j = 0; j < length; j++) {
child = children[j];
if (typeof child === 'string')
template[i] += child;
else {
args.push(child);
i = template.push('') - 1;
}
const template = ['<'];
const args = [null];
let i = 0;
}
if (
length || (
!xml && (
(component && !empty.test(component.tagName)) ||
!empty.test(entry)
)
)
) {
if (component) {
template[i] += '</';
args.push(entry);
i = template.push('') - 1;
template.push('>');
}
else
template[i] += entry;
for (const key in attributes) {
const {name, value} = attribute(key, attributes[key]);
args.push(value);
template[i] += ` ${name}="`;
i = template.push('"') - 1;
}
template[i] += '>';
const {length} = children;
for (let child, j = 0; j < length; j++) {
child = children[j];
if (typeof child === 'string')
template[i] += child;
else {
args.push(child);
i = template.push('') - 1;
}
}
if (
0 < length ||
(component && !empty.test(component.tagName)) ||
!empty.test(entry)
) {
if (component) {
template[i] += '</';
args.push(entry);
template.push('>');
}
else
template[i] += `</${entry}>`;
}
const whole = template.join(er);
args[0] = cache.get(whole) || template;
if (args[0] === template)
cache.set(whole, template);
return tag.apply(this, args);
};
template[i] += `</${entry}>`;
}
const whole = template.join(er);
args[0] = cache.get(whole) || template;
if (args[0] === template)
cache.set(whole, template);
return tag.apply(this, args);
};

function Bound(_) {
this._ = _;
}
41 changes: 26 additions & 15 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
// your template literal library of choice
const {
render,
html
html,
svg
} = require('uhtml-ssr'); // this module


const {
bind,
createPragma
} = require('../cjs/index.js'); // create your `h` / pragma function
} = require('../cjs/index.js');

const assert = (value, expected) => {
/* c8 ignore start */
if (expected !== render(String, value)) {
console.error('got ', render(String, value));
console.error('expected', expected);
process.exit(1);
}
/* c8 ignore stop */

}; // create your `h` / pragma function

const h = createPragma(html); // any component (passed as template value)

let h = createPragma(html); // any component (passed as template value)

const Bold = ({
children
Expand Down Expand Up @@ -39,6 +51,7 @@ const test = 123; // test it!

const myDocument = h("p", {
class: "what",
nope: null,
test: bind(test),
onClick: console.log
}, h(Bold, null, "Hello"), ", ", h("input", {
Expand All @@ -47,15 +60,13 @@ const myDocument = h("p", {
}), h(Span, {
id: "greetings"
}, "Hello"), " ", h(World, null));
/* c8 ignore start */

const expected = `<p class="what" test="123"><strong>Hello</strong>, <input type="password" disabled><span id="greetings">Hello</span> <div></div></p>`;

if (expected !== render(String, myDocument)) {
console.error('got ', render(String, myDocument));
console.error('expected', expected);
process.exit(1);
}

console.log('\x1b[1mOK\x1b[0m');
/* c8 ignore stop */
assert(myDocument, `<p class="what" test="123"><strong>Hello</strong>, <input type="password" disabled><span id="greetings">Hello</span> <div></div></p>`);
h = createPragma(svg, {
xml: true
});
const svgDocument = h("rect", {
x: 10,
y: 20
});
assert(svgDocument, '<rect x="10" y="20" />');
console.log('\x1b[1mOK\x1b[0m');
Loading

0 comments on commit f60ab65

Please sign in to comment.