├── .npmrc
├── cjs
├── package.json
└── index.js
├── test
├── package.json
├── .babelrc
├── jsx.js
├── index.jsx
└── index.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── index.d.ts
├── package.json
├── README.md
└── esm
└── index.js
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | coverage/
4 | node_modules/
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .eslintrc.json
4 | .travis.yml
5 | coverage/
6 | node_modules/
7 | rollup/
8 | test/
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | git:
5 | depth: 1
6 | branches:
7 | only:
8 | - main
9 | after_success:
10 | - "npm run coveralls"
11 |
--------------------------------------------------------------------------------
/test/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "@babel/plugin-transform-react-jsx",
5 | {
6 | "pragma": "h",
7 | "pragmaFrag": "h"
8 | }
9 | ],
10 | ["module:@ungap/plugin-transform-static-jsx"]
11 | ],
12 | "comments": true
13 | }
14 |
--------------------------------------------------------------------------------
/test/jsx.js:
--------------------------------------------------------------------------------
1 | const {render, html, svg} = require('uhtml-ssr');
2 | const {bind, createPragma} = require('../cjs/index.js');
3 |
4 | module.exports = {
5 | render, html, svg,
6 | bind,
7 | jsx2: {
8 | html: createPragma(html),
9 | svg: createPragma(svg, {xml: true})
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 | PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export function defaultAttribute(name: string, value: any): attr;
2 | export function bind(value: any): Bound;
3 | export function createPragma(tag: Function, { attribute, keyed, cache, xml }?: config): Function;
4 | /**
5 | * - a DOM attribute facade with a `name` and a `value`.
6 | */
7 | export type attr = {
8 | /**
9 | * - the attribute name as shown in the template literal.
10 | */
11 | name: string;
12 | /**
13 | * - the attribute value to pass along to the attribute.
14 | */
15 | value: any;
16 | };
17 | /**
18 | * - optionally configure the pragma function
19 | */
20 | export type config = {
21 | /**
22 | * - a `callback(name, value)` to return a `{name, value}` literal.
23 | * If `null` or `undefined` is returned, the attribute is skipped all along.
24 | */
25 | attribute?: Function;
26 | /**
27 | * - a `callback(entry, props)` to return a keyed version of the `tag`.
28 | */
29 | keyed?: Function;
30 | /**
31 | * - a cache for already known/parsed templates.
32 | */
33 | cache?: Map;
34 | /**
35 | * - treat nodes as XML with self-closing tags.
36 | */
37 | xml?: boolean;
38 | };
39 | /*! (c) Andrea Giammarchi - ISC */
40 | /**
41 | * A value wrap/placeholder to easily find "bound" properties.
42 | * The `bind(any)` export will result into `.name=${value}` in the template.
43 | */
44 | declare class Bound {
45 | /**
46 | * @param {any} _ a "protected" value to carry along for further `instanceof` checks.
47 | */
48 | constructor(_: any);
49 | _: any;
50 | }
51 | export {};
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsx2tag",
3 | "version": "0.3.1",
4 | "description": "Enable JSX for Template Literal Tags based projects",
5 | "main": "./cjs/index.js",
6 | "types": "./index.d.ts",
7 | "scripts": {
8 | "build": "npm run cjs && npm run test && npm run types",
9 | "cjs": "ascjs --no-default esm cjs",
10 | "coveralls": "c8 report --reporter=text-lcov | coveralls",
11 | "test": "cd test; npx babel index.jsx -d ./ && c8 node index.js && rm -rf ../coverage && mv coverage ../ && cd .. && c8 report --reporter=text",
12 | "types": "tsc --declaration --allowJs --emitDeclarationOnly --outDir ./ esm/index.js"
13 | },
14 | "keywords": [
15 | "JSX",
16 | "template",
17 | "tag",
18 | "transformer"
19 | ],
20 | "author": "Andrea Giammarchi",
21 | "license": "ISC",
22 | "devDependencies": {
23 | "@babel/cli": "^7.14.5",
24 | "@babel/core": "^7.14.6",
25 | "@babel/plugin-transform-react-jsx": "^7.14.5",
26 | "@ungap/plugin-transform-static-jsx": "^0.2.0",
27 | "ascjs": "^5.0.1",
28 | "c8": "^7.7.3",
29 | "coveralls": "^3.1.0",
30 | "typescript": "^4.3.4",
31 | "uhtml-ssr": "^0.7.1"
32 | },
33 | "module": "./esm/index.js",
34 | "type": "module",
35 | "exports": {
36 | ".": {
37 | "import": "./esm/index.js",
38 | "default": "./cjs/index.js"
39 | },
40 | "./package.json": "./package.json"
41 | },
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/WebReflection/jsx2tag.git"
45 | },
46 | "bugs": {
47 | "url": "https://github.com/WebReflection/jsx2tag/issues"
48 | },
49 | "homepage": "https://github.com/WebReflection/jsx2tag#readme"
50 | }
51 |
--------------------------------------------------------------------------------
/test/index.jsx:
--------------------------------------------------------------------------------
1 | /** @jsx h *//** @jsxFrag h */
2 |
3 | const {render, html, bind, jsx2} = require('./jsx.js');
4 |
5 | const assert = (value, expected) => {
6 | /* c8 ignore start */
7 | if (expected !== render(String, value)) {
8 | console.error('got ', render(String, value));
9 | console.error('expected', expected);
10 | process.exit(1);
11 | }
12 | /* c8 ignore stop */
13 | };
14 |
15 | let h = jsx2.html;
16 |
17 | // any component (passed as template value)
18 | const Bold = ({children}) => html`${children}`;
19 |
20 | class Span {
21 | constructor({id, children}) {
22 | return html`${children}`;
23 | }
24 | }
25 |
26 | // This is specific for ube or classes with a `tagName`
27 | // these well be used as interpolations values
28 | function World() { return World.tagName; }
29 | World.tagName = 'div';
30 |
31 | // any generic value
32 | const test = 123;
33 |
34 | // test it!
35 | const myDocument = (
36 |
37 | Hello,
38 | Hello
39 |
40 | );
41 |
42 | assert(
43 | myDocument,
44 | `Hello, Hello
`
45 | );
46 |
47 | h = jsx2.svg;
48 |
49 | const svgDocument = (
50 |
51 | );
52 |
53 | assert(
54 | svgDocument,
55 | ''
56 | );
57 |
58 | const fragment = (
59 | <>
60 |
61 |
62 | OK
63 | >
64 | );
65 |
66 | assert(
67 | fragment,
68 | 'OK'
69 | );
70 |
71 | console.log('Test: \x1b[1mOK\x1b[0m');
72 |
73 | const svg = (
74 |
79 | );
80 |
81 | function MyElement({ children }) {
82 | return {children}
;
83 | }
84 |
85 | const me = (
86 |
87 | Some Text and some more text
88 |
89 | );
90 |
91 | assert(
92 | me,
93 | 'Some Text and some more text
'
94 | );
95 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 |
3 | /** @jsxFrag h */
4 | const {
5 | render,
6 | html,
7 | bind,
8 | jsx2
9 | } = require('./jsx.js');
10 |
11 | const assert = (value, expected) => {
12 | /* c8 ignore start */
13 | if (expected !== render(String, value)) {
14 | console.error('got ', render(String, value));
15 | console.error('expected', expected);
16 | process.exit(1);
17 | }
18 | /* c8 ignore stop */
19 |
20 | };
21 |
22 | let h = jsx2.html; // any component (passed as template value)
23 |
24 | const Bold = ({
25 | children
26 | }) => html`${children}`;
27 |
28 | class Span {
29 | constructor({
30 | id,
31 | children
32 | }) {
33 | return html`${children}`;
34 | }
35 |
36 | } // This is specific for ube or classes with a `tagName`
37 | // these well be used as interpolations values
38 |
39 |
40 | function World() {
41 | return World.tagName;
42 | }
43 |
44 | World.tagName = 'div'; // any generic value
45 |
46 | const test = 123; // test it!
47 |
48 | const myDocument = h("p", {
49 | "\x01class": "what",
50 | nope: null,
51 | test: bind(test),
52 | onClick: console.log
53 | }, h(Bold, null, "Hello"), ", ", h("input", {
54 | "\x01type": "password",
55 | disabled: true
56 | }), h(Span, {
57 | id: "greetings"
58 | }, "Hello"), " ", h(World, null));
59 | assert(myDocument, `Hello, Hello
`);
60 | h = jsx2.svg;
61 | const svgDocument = h("rect", {
62 | x: 10,
63 | "\x01y": "20"
64 | });
65 | assert(svgDocument, '');
66 | const fragment = h(h, null, h("rect", {
67 | key: Math.random(),
68 | x: '1',
69 | "\x01y": "2"
70 | }), h("rect", {
71 | "\x01x": "3",
72 | y: 4
73 | }), "OK");
74 | assert(fragment, 'OK');
75 | console.log('Test: \x1b[1mOK\x1b[0m');
76 | const svg = h("svg", null, h("g", {
77 | "\x01transform": "translate(20,20)"
78 | }, h("text", null, "hello")));
79 |
80 | function MyElement({
81 | children
82 | }) {
83 | return h("div", null, children);
84 | }
85 |
86 | const me = h(MyElement, null, "Some Text ", h("b", null, "and some more text"));
87 | assert(me, 'Some Text and some more text
');
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSX2TAG
2 |
3 | [](https://travis-ci.com/WebReflection/jsx2tag) [](https://coveralls.io/github/WebReflection/jsx2tag?branch=main)
4 |
5 | **Social Media Photo by [Andre Taissin](https://unsplash.com/@andretaissin) on [Unsplash](https://unsplash.com/)**
6 |
7 |
8 | Enable JSX for Template Literal Tags based projects.
9 |
10 | * [µhtml live demo](https://codepen.io/WebReflection/pen/KKWxXYY?editors=0010)
11 | * [µland live demo](https://codepen.io/WebReflection/pen/bGqxYpZ?editors=0010)
12 | * [µbe live demo](https://codepen.io/WebReflection/pen/BaWqNpd?editors=0010)
13 | * [lit-html live demo](https://codepen.io/WebReflection/pen/abJaVzm?editors=0010)
14 |
15 |
16 | ### Features
17 |
18 | * a `createPragma(tag, config?)` utility to have a `React.createElement` like function to use as *pragma*
19 | * a `bind` utility to mimic `.prop=${value}` in the template
20 | * automatic `onEventName` to `@eventName` conversion
21 | * automatic `?prop=${value}` conversion in the template, when the property is boolean
22 | * optionally boost performance via [@ungap/plugin-transform-static-jsx](https://github.com/ungap/plugin-transform-static-jsx#readme), able to create best template literals tags' arguments
23 |
24 |
25 | ### Example
26 |
27 | See [test/index.jsx](./test/index.jsx) to see all features applied.
28 |
29 | ```js
30 | /** @jsx h *//** @jsxFrag h */
31 |
32 | // your template literal library of choice
33 | const {render, html} = require('uhtml-ssr');
34 |
35 | // this module utils
36 | const {bind, createPragma} = require('jsx2tag');
37 |
38 | // create your `h` / pragma function
39 | const h = createPragma(html);
40 | // if your env works already with `React.createElement`, use:
41 | // const React = {createElement: createPragma(html)};
42 |
43 | // any component (passed as template value)
44 | const Bold = ({children}) => html`${children}`;
45 |
46 | // any generic value
47 | const test = 123;
48 |
49 | // test it!
50 | const myDocument = (
51 |
52 | Hello,
53 | Hello
54 |
55 | );
56 |
57 | render(String, myDocument);
58 | // Hello, Hello
59 | ```
60 |
61 | ## How To Transpile JSX
62 |
63 | Specify `pragma` and `pragmaFrag` or use this syntax on top:
64 |
65 | ```js
66 | /** @jsx h */
67 | /** @jsxFrag h */
68 | ```
69 |
70 | Otherwise, follow [@Robbb_J](https://twitter.com/Robbb_J) post [about minimal requirements](https://blog.r0b.io/post/using-jsx-without-react/) and you'll be good.
71 |
72 | A huge thanks to him for writing such simple, step by step, guide.
73 |
74 | ## How to render keyed components
75 |
76 | The `config` object accepts a `keyed(tagName, props)` callback that can return a keyed version of the component.
77 |
78 | ```js
79 | /** @jsx h *//** @jsxFrag h */
80 | import {createPragma} from '//unpkg.com/jsx2tag?module';
81 | import {render, html} from '//unpkg.com/uhtml?module';
82 |
83 | // used as weakMap key for global keyed references
84 | const refs = {};
85 | const h = createPragma(html, {
86 | // invoked when a key={value} is found in the node
87 | // to render regular elements (or µbe classes)
88 | keyed(tagName, {key}) {
89 | const ref = refs[tagName] || (refs[tagName] = {});
90 | return html.for(ref, key);
91 | }
92 | });
93 |
94 | render(document.body, );
95 |
96 | ```
97 |
98 | Alternatively, each library might have its own way, but the gist of this feature, whenever available, is that the `key` property is all we're after:
99 |
100 | ```js
101 | /** @jsx h *//** @jsxFrag h */
102 |
103 | import {createPragma} from '//unpkg.com/jsx2tag?module';
104 | import {render, html} from '//unpkg.com/uhtml?module';
105 |
106 | const h = createPragma(html);
107 |
108 | const App = ({name, key}) => html.for(App, key)`Hello ${name} 👋`;
109 |
110 | render(document.body, );
111 | ```
112 |
113 | Conditional *keyed* components are also possible.
114 |
115 | Here another *uhtml* example:
116 |
117 | ```js
118 | /** @jsx h *//** @jsxFrag h */
119 |
120 | import {createPragma} from '//unpkg.com/jsx2tag?module';
121 | import {render, html} from '//unpkg.com/uhtml?module';
122 |
123 | const h = createPragma(html);
124 |
125 | const App = ({name, key}) => {
126 | const tag = key ? html.for(App, key) : html;
127 | return tag`Hello ${name} 👋`;
128 | };
129 |
130 | render(document.body, );
131 | ```
132 |
133 | In few words, there's literally *nothing* stopping template literal tags libraries to be *keyed* compatible.
134 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi - ISC */
2 |
3 | /**
4 | * A value wrap/placeholder to easily find "bound" properties.
5 | * The `bind(any)` export will result into `.name=${value}` in the template.
6 | */
7 | class Bound {
8 | /**
9 | * @param {any} _ a "protected" value to carry along for further `instanceof` checks.
10 | */
11 | constructor(_) {
12 | this._ = _;
13 | }
14 | }
15 |
16 | const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
17 | const er = '\x01';
18 | const re = /^on([A-Z])/;
19 | const place = (_, $) => ('@' + $.toLowerCase());
20 | const fragment = ['', ''];
21 |
22 | /**
23 | * @typedef {object} attr - a DOM attribute facade with a `name` and a `value`.
24 | * @property {string} name - the attribute name as shown in the template literal.
25 | * @property {any} value - the attribute value to pass along to the attribute.
26 | */
27 |
28 | /**
29 | * Return the `name` and `value` to use with an attribute.
30 | * * boolean values transform the `name` as `"?" + name`.
31 | * * bound values transform the `name` as `"." + name`
32 | * * events like `onX` transform the `name` as `"@" + name`
33 | * * strings, numbers, `null`, or `undefined` values don't change the `name`
34 | * @param {string} name the attribute name.
35 | * @param {any} value the attribute value.
36 | * @returns {attr} the attribute facade to use in the template as `name=${value}`.
37 | */
38 | export const defaultAttribute = (name, value) => {
39 | switch (typeof value) {
40 | case 'string':
41 | case 'number':
42 | return {name, value};
43 | case 'boolean':
44 | return {name: '?' + name, value};
45 | case 'object':
46 | case 'undefined':
47 | if (value == null)
48 | return {name, value};
49 | if (value instanceof Bound)
50 | return {name: '.' + name, value: value._};
51 | }
52 | return {name: name.replace(re, place), value};
53 | };
54 |
55 | /**
56 | * Allow binding values directly to nodes via `name={bind(value)}`.
57 | * @param {any} value the value expected, in the template, as `.name=${value}`.
58 | * @returns
59 | */
60 | export const bind = value => new Bound(value);
61 |
62 | /**
63 | * @typedef {object} config - optionally configure the pragma function
64 | * @property {function} [attribute=defaultAttribute] - a `callback(name, value)` to return a `{name, value}` literal.
65 | * If `null` or `undefined` is returned, the attribute is skipped all along.
66 | * @property {function} [keyed=()=>tag] - a `callback(entry, props)` to return a keyed version of the `tag`.
67 | * @property {Map} [cache=new Map()] - a cache for already known/parsed templates.
68 | * @property {boolean} [xml=false] - treat nodes as XML with self-closing tags.
69 | */
70 |
71 | /**
72 | * Return an `h` / pragma function usable with JSX transformation.
73 | * @param {function} tag a template literal tag function to invoke.
74 | * @param {config} [config={}] an optional configuration object.
75 | * @returns {function} the `h` / `React.createElement` like pragma function to use with JSX.
76 | */
77 | export const createPragma = (
78 | tag,
79 | {
80 | attribute = defaultAttribute,
81 | keyed = () => tag,
82 | cache = new Map,
83 | xml = false
84 | } = {}
85 | ) => function h(entry, attributes, ...children) {
86 | const component = typeof entry === 'function';
87 |
88 | // handle fragments as tag array
89 | if (component && entry === h)
90 | return tag.call(this, fragment, children);
91 |
92 | // handle classes and component callbacks (keep µbe classes around)
93 | if (component && !('tagName' in entry)) {
94 | // pass {...props, children} to the component
95 | (attributes || (attributes = {})).children = children;
96 | return 'prototype' in entry ? new entry(attributes) : entry(attributes);
97 | }
98 |
99 | // handler µbe classes or regular HTML/SVG/XML cases
100 | const template = ['<'];
101 | const args = [template];
102 | let isKeyed = false;
103 | let i = 0;
104 | if (component) {
105 | args.push(entry);
106 | i = template.push('') - 1;
107 | }
108 | else
109 | template[i] += entry;
110 | for (const key in attributes) {
111 | if (key.length) {
112 | if (key[0] === er)
113 | template[i] += ` ${key.slice(1)}="${attributes[key]}"`;
114 | else if (key === 'key')
115 | isKeyed = !isKeyed;
116 | else {
117 | const attr = attribute(key, attributes[key]);
118 | if (attr != null) {
119 | template[i] += ` ${attr.name}="`;
120 | args.push(attr.value);
121 | i = template.push('"') - 1;
122 | }
123 | }
124 | }
125 | }
126 |
127 | // handle children or self-closing XML tags
128 | const {length} = (children = children.flat());
129 | template[i] += (length || !xml) ? '>' : ' />';
130 | for (let child, j = 0; j < length; j++) {
131 | child = children[j];
132 | if (typeof child === 'string')
133 | template[i] += child;
134 | else {
135 | args.push(child);
136 | i = template.push('') - 1;
137 | }
138 | }
139 |
140 | // handle closing tag
141 | if (
142 | length || (
143 | !xml && (
144 | (component && !empty.test(component.tagName)) ||
145 | !empty.test(entry)
146 | )
147 | )
148 | ) {
149 | if (component) {
150 | template[i] += '';
151 | args.push(entry);
152 | template.push('>');
153 | }
154 | else
155 | template[i] += `${entry}>`;
156 | }
157 |
158 | // be sure the template argument is always the same
159 | const whole = template.join(er);
160 | args[0] = cache.get(whole) || template;
161 | if (args[0] === template)
162 | cache.set(whole, template);
163 |
164 | return (isKeyed ? keyed(entry, attributes) : tag).apply(this, args);
165 | };
166 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*! (c) Andrea Giammarchi - ISC */
3 |
4 | /**
5 | * A value wrap/placeholder to easily find "bound" properties.
6 | * The `bind(any)` export will result into `.name=${value}` in the template.
7 | */
8 | class Bound {
9 | /**
10 | * @param {any} _ a "protected" value to carry along for further `instanceof` checks.
11 | */
12 | constructor(_) {
13 | this._ = _;
14 | }
15 | }
16 |
17 | const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
18 | const er = '\x01';
19 | const re = /^on([A-Z])/;
20 | const place = (_, $) => ('@' + $.toLowerCase());
21 | const fragment = ['', ''];
22 |
23 | /**
24 | * @typedef {object} attr - a DOM attribute facade with a `name` and a `value`.
25 | * @property {string} name - the attribute name as shown in the template literal.
26 | * @property {any} value - the attribute value to pass along to the attribute.
27 | */
28 |
29 | /**
30 | * Return the `name` and `value` to use with an attribute.
31 | * * boolean values transform the `name` as `"?" + name`.
32 | * * bound values transform the `name` as `"." + name`
33 | * * events like `onX` transform the `name` as `"@" + name`
34 | * * strings, numbers, `null`, or `undefined` values don't change the `name`
35 | * @param {string} name the attribute name.
36 | * @param {any} value the attribute value.
37 | * @returns {attr} the attribute facade to use in the template as `name=${value}`.
38 | */
39 | const defaultAttribute = (name, value) => {
40 | switch (typeof value) {
41 | case 'string':
42 | case 'number':
43 | return {name, value};
44 | case 'boolean':
45 | return {name: '?' + name, value};
46 | case 'object':
47 | case 'undefined':
48 | if (value == null)
49 | return {name, value};
50 | if (value instanceof Bound)
51 | return {name: '.' + name, value: value._};
52 | }
53 | return {name: name.replace(re, place), value};
54 | };
55 | exports.defaultAttribute = defaultAttribute;
56 |
57 | /**
58 | * Allow binding values directly to nodes via `name={bind(value)}`.
59 | * @param {any} value the value expected, in the template, as `.name=${value}`.
60 | * @returns
61 | */
62 | const bind = value => new Bound(value);
63 | exports.bind = bind;
64 |
65 | /**
66 | * @typedef {object} config - optionally configure the pragma function
67 | * @property {function} [attribute=defaultAttribute] - a `callback(name, value)` to return a `{name, value}` literal.
68 | * If `null` or `undefined` is returned, the attribute is skipped all along.
69 | * @property {function} [keyed=()=>tag] - a `callback(entry, props)` to return a keyed version of the `tag`.
70 | * @property {Map} [cache=new Map()] - a cache for already known/parsed templates.
71 | * @property {boolean} [xml=false] - treat nodes as XML with self-closing tags.
72 | */
73 |
74 | /**
75 | * Return an `h` / pragma function usable with JSX transformation.
76 | * @param {function} tag a template literal tag function to invoke.
77 | * @param {config} [config={}] an optional configuration object.
78 | * @returns {function} the `h` / `React.createElement` like pragma function to use with JSX.
79 | */
80 | const createPragma = (
81 | tag,
82 | {
83 | attribute = defaultAttribute,
84 | keyed = () => tag,
85 | cache = new Map,
86 | xml = false
87 | } = {}
88 | ) => function h(entry, attributes, ...children) {
89 | const component = typeof entry === 'function';
90 |
91 | // handle fragments as tag array
92 | if (component && entry === h)
93 | return tag.call(this, fragment, children);
94 |
95 | // handle classes and component callbacks (keep µbe classes around)
96 | if (component && !('tagName' in entry)) {
97 | // pass {...props, children} to the component
98 | (attributes || (attributes = {})).children = children;
99 | return 'prototype' in entry ? new entry(attributes) : entry(attributes);
100 | }
101 |
102 | // handler µbe classes or regular HTML/SVG/XML cases
103 | const template = ['<'];
104 | const args = [template];
105 | let isKeyed = false;
106 | let i = 0;
107 | if (component) {
108 | args.push(entry);
109 | i = template.push('') - 1;
110 | }
111 | else
112 | template[i] += entry;
113 | for (const key in attributes) {
114 | if (key.length) {
115 | if (key[0] === er)
116 | template[i] += ` ${key.slice(1)}="${attributes[key]}"`;
117 | else if (key === 'key')
118 | isKeyed = !isKeyed;
119 | else {
120 | const attr = attribute(key, attributes[key]);
121 | if (attr != null) {
122 | template[i] += ` ${attr.name}="`;
123 | args.push(attr.value);
124 | i = template.push('"') - 1;
125 | }
126 | }
127 | }
128 | }
129 |
130 | // handle children or self-closing XML tags
131 | const {length} = (children = children.flat());
132 | template[i] += (length || !xml) ? '>' : ' />';
133 | for (let child, j = 0; j < length; j++) {
134 | child = children[j];
135 | if (typeof child === 'string')
136 | template[i] += child;
137 | else {
138 | args.push(child);
139 | i = template.push('') - 1;
140 | }
141 | }
142 |
143 | // handle closing tag
144 | if (
145 | length || (
146 | !xml && (
147 | (component && !empty.test(component.tagName)) ||
148 | !empty.test(entry)
149 | )
150 | )
151 | ) {
152 | if (component) {
153 | template[i] += '';
154 | args.push(entry);
155 | template.push('>');
156 | }
157 | else
158 | template[i] += `${entry}>`;
159 | }
160 |
161 | // be sure the template argument is always the same
162 | const whole = template.join(er);
163 | args[0] = cache.get(whole) || template;
164 | if (args[0] === template)
165 | cache.set(whole, template);
166 |
167 | return (isKeyed ? keyed(entry, attributes) : tag).apply(this, args);
168 | };
169 | exports.createPragma = createPragma;
170 |
--------------------------------------------------------------------------------