diff --git a/cjs/index.js b/cjs/index.js index 060522c..f72a6dd 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -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])/; @@ -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._}; } @@ -35,63 +44,67 @@ exports.bind = bind; * @param {Map?} 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] += ''); } 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] += ''); - } - else - template[i] += ``; - } - 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] += ``; + } + 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._ = _; -} diff --git a/esm/index.js b/esm/index.js index a77bde9..4c98e78 100644 --- a/esm/index.js +++ b/esm/index.js @@ -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])/; @@ -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._}; } @@ -33,64 +42,66 @@ export const bind = value => new Bound(value); * @param {Map?} 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] += ''); } 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] += ''); - } - else - template[i] += ``; - } - 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] += ``; + } + 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._ = _; -} diff --git a/test/index.js b/test/index.js index 04a9724..4d46358 100644 --- a/test/index.js +++ b/test/index.js @@ -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 @@ -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", { @@ -47,15 +60,13 @@ const myDocument = h("p", { }), h(Span, { id: "greetings" }, "Hello"), " ", h(World, null)); -/* c8 ignore start */ - -const expected = `

Hello, Hello

`; - -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 */ \ No newline at end of file +assert(myDocument, `

Hello, Hello

`); +h = createPragma(svg, { + xml: true +}); +const svgDocument = h("rect", { + x: 10, + y: 20 +}); +assert(svgDocument, ''); +console.log('\x1b[1mOK\x1b[0m'); \ No newline at end of file diff --git a/test/index.jsx b/test/index.jsx index 8ca9696..3a5419d 100644 --- a/test/index.jsx +++ b/test/index.jsx @@ -1,11 +1,21 @@ // your template literal library of choice -const {render, html} = require('uhtml-ssr'); +const {render, html, svg} = require('uhtml-ssr'); // this module const {bind, createPragma} = 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); +let h = createPragma(html); // any component (passed as template value) const Bold = ({children}) => html`${children}`; @@ -26,18 +36,28 @@ const test = 123; // test it! const myDocument = ( -

+

Hello, Hello

); -/* c8 ignore start */ -const expected = `

Hello, Hello

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

Hello, Hello

` +); + +h = createPragma(svg, { + xml: true +}); + +const svgDocument = ( + +); + +assert( + svgDocument, + '' +); + console.log('\x1b[1mOK\x1b[0m'); -/* c8 ignore stop */