From d86f4db59f0a54b7a9b0d53c345424609a99157d Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 10 Feb 2021 23:20:02 -0800 Subject: [PATCH 01/11] handle optional closing tags for self-closing tags. fixes #41 --- index.js | 188 ++++++++++++++++++++++++++++++++++------------------ test/svg.js | 25 +++++++ 2 files changed, 149 insertions(+), 64 deletions(-) diff --git a/index.js b/index.js index de6fc07..1e658b6 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ var attrToProp = require('hyperscript-attribute-to-property') + var VAR = 0, TEXT = 1, OPEN = 2, CLOSE = 3, ATTR = 4 var ATTR_KEY = 5, ATTR_KEY_W = 6 var ATTR_VALUE_W = 7, ATTR_VALUE = 8 @@ -7,21 +8,24 @@ var ATTR_VALUE_SQ = 9, ATTR_VALUE_DQ = 10 var ATTR_EQ = 11, ATTR_BREAK = 12 var COMMENT = 13 -module.exports = function (h, opts) { - if (!opts) opts = {} +module.exports = function (h, opts={}) { + var concat = opts.concat || function (a, b) { return String(a) + String(b) } - if (opts.attrToProp !== false) { + + if (opts.attrToProp !== false) h = attrToProp(h) - } + return function (strings) { - var state = TEXT, reg = '' + + var state = TEXT, reg = '', isSelfClosing = false var arglen = arguments.length var parts = [] for (var i = 0; i < strings.length; i++) { + if (i < arglen - 1) { var arg = arguments[i+1] var p = parse(strings[i]) @@ -43,120 +47,151 @@ module.exports = function (h, opts) { p.push([ VAR, xstate, arg ]) } parts.push.apply(parts, p) - } else parts.push.apply(parts, parse(strings[i])) + } else { + parts.push.apply(parts, parse(strings[i])) + } } - var tree = [null,{},[]] - var stack = [[tree,-1]] + var tree = [ null, {}, [] ] + var stack = [ [ tree, -1 ] ] + for (var i = 0; i < parts.length; i++) { var cur = stack[stack.length-1][0] - var p = parts[i], s = p[0] - if (s === OPEN && /^\//.test(p[1])) { + var p = parts[i], state = p[0] + + if (state === OPEN && /^\//.test(p[1])) { var ix = stack[stack.length-1][1] if (stack.length > 1) { stack.pop() - stack[stack.length-1][0][2][ix] = h( - cur[0], cur[1], cur[2].length ? cur[2] : undefined - ) + stack[stack.length-1][0][2][ix] = h(cur[0], cur[1], cur[2].length ? cur[2] : undefined) } - } else if (s === OPEN) { + + } else if (state === OPEN) { var c = [p[1],{},[]] cur[2].push(c) stack.push([c,cur[2].length-1]) - } else if (s === ATTR_KEY || (s === VAR && p[1] === ATTR_KEY)) { + + } else if (state === ATTR_KEY || (state === VAR && p[1] === ATTR_KEY)) { var key = '' var copyKey for (; i < parts.length; i++) { if (parts[i][0] === ATTR_KEY) { key = concat(key, parts[i][1]) + } else if (parts[i][0] === VAR && parts[i][1] === ATTR_KEY) { if (typeof parts[i][2] === 'object' && !key) { - for (copyKey in parts[i][2]) { - if (parts[i][2].hasOwnProperty(copyKey) && !cur[1][copyKey]) { + for (copyKey in parts[i][2]) + if (parts[i][2].hasOwnProperty(copyKey) && !cur[1][copyKey]) cur[1][copyKey] = parts[i][2][copyKey] - } - } + } else { key = concat(key, parts[i][2]) } - } else break + + } else { + break + } } - if (parts[i][0] === ATTR_EQ) i++ + + if (parts[i][0] === ATTR_EQ) + i++ + var j = i + for (; i < parts.length; i++) { if (parts[i][0] === ATTR_VALUE || parts[i][0] === ATTR_KEY) { - if (!cur[1][key]) cur[1][key] = strfn(parts[i][1]) - else parts[i][1]==="" || (cur[1][key] = concat(cur[1][key], parts[i][1])); - } else if (parts[i][0] === VAR - && (parts[i][1] === ATTR_VALUE || parts[i][1] === ATTR_KEY)) { - if (!cur[1][key]) cur[1][key] = strfn(parts[i][2]) - else parts[i][2]==="" || (cur[1][key] = concat(cur[1][key], parts[i][2])); + if (!cur[1][key]) + cur[1][key] = strfn(parts[i][1]) + else + parts[i][1]==="" || (cur[1][key] = concat(cur[1][key], parts[i][1])); + + } else if (parts[i][0] === VAR && (parts[i][1] === ATTR_VALUE || parts[i][1] === ATTR_KEY)) { + if (!cur[1][key]) + cur[1][key] = strfn(parts[i][2]) + else + parts[i][2]==="" || (cur[1][key] = concat(cur[1][key], parts[i][2])); + } else { - if (key.length && !cur[1][key] && i === j - && (parts[i][0] === CLOSE || parts[i][0] === ATTR_BREAK)) { + if (key.length && !cur[1][key] && i === j && (parts[i][0] === CLOSE || parts[i][0] === ATTR_BREAK)) { // https://html.spec.whatwg.org/multipage/infrastructure.html#boolean-attributes // empty string is falsy, not well behaved value in browser cur[1][key] = key.toLowerCase() } - if (parts[i][0] === CLOSE) { + + if (parts[i][0] === CLOSE) i-- - } + break } } - } else if (s === ATTR_KEY) { + + } else if (state === ATTR_KEY) { cur[1][p[1]] = true - } else if (s === VAR && p[1] === ATTR_KEY) { + + } else if (state === VAR && p[1] === ATTR_KEY) { cur[1][p[2]] = true - } else if (s === CLOSE) { - if (selfClosing(cur[0]) && stack.length) { + + } else if (state === CLOSE) { + + const isSelfClosing = p[1] || selfClosingVoid(cur[0]) + //if (selfClosing(cur[0]) && stack.length) { + if (isSelfClosing && stack.length) { var ix = stack[stack.length-1][1] stack.pop() - stack[stack.length-1][0][2][ix] = h( - cur[0], cur[1], cur[2].length ? cur[2] : undefined - ) + stack[stack.length-1][0][2][ix] = h(cur[0], cur[1], cur[2].length ? cur[2] : undefined) } - } else if (s === VAR && p[1] === TEXT) { - if (p[2] === undefined || p[2] === null) p[2] = '' - else if (!p[2]) p[2] = concat('', p[2]) - if (Array.isArray(p[2][0])) { + + } else if (state === VAR && p[1] === TEXT) { + if (p[2] === undefined || p[2] === null) + p[2] = '' + else if (!p[2]) + p[2] = concat('', p[2]) + + if (Array.isArray(p[2][0])) cur[2].push.apply(cur[2], p[2]) - } else { + else cur[2].push(p[2]) - } - } else if (s === TEXT) { + + } else if (state === TEXT) { cur[2].push(p[1]) - } else if (s === ATTR_EQ || s === ATTR_BREAK) { + + } else if (state === ATTR_EQ || state === ATTR_BREAK) { // no-op + } else { - throw new Error('unhandled: ' + s) + throw new Error('unhandled: ' + state) + } } - if (tree[2].length > 1 && /^\s*$/.test(tree[2][0])) { + if (tree[2].length > 1 && /^\s*$/.test(tree[2][0])) tree[2].shift() - } - if (tree[2].length > 2 - || (tree[2].length === 2 && /\S/.test(tree[2][1]))) { - if (opts.createFragment) return opts.createFragment(tree[2]) + if (tree[2].length > 2 || (tree[2].length === 2 && /\S/.test(tree[2][1]))) { + if (opts.createFragment) + return opts.createFragment(tree[2]) + throw new Error( 'multiple root elements must be wrapped in an enclosing tag' ) } - if (Array.isArray(tree[2][0]) && typeof tree[2][0][0] === 'string' - && Array.isArray(tree[2][0][2])) { + + if (Array.isArray(tree[2][0]) && typeof tree[2][0][0] === 'string' && Array.isArray(tree[2][0][2])) tree[2][0] = h(tree[2][0][0], tree[2][0][1], tree[2][0][2]) - } + return tree[2][0] function parse (str) { - var res = [] - if (state === ATTR_VALUE_W) state = ATTR + var res = [ ] + + if (state === ATTR_VALUE_W) + state = ATTR + for (var i = 0; i < str.length; i++) { var c = str.charAt(i) if (state === TEXT && c === '<') { - if (reg.length) res.push([TEXT, reg]) + if (reg.length) + res.push([TEXT, reg]) reg = '' state = OPEN } else if (c === '>' && !quot(state) && state !== COMMENT) { @@ -167,7 +202,8 @@ module.exports = function (h, opts) { } else if (state === ATTR_VALUE && reg.length) { res.push([ATTR_VALUE,reg]) } - res.push([CLOSE]) + res.push([CLOSE, isSelfClosing]) + isSelfClosing = false reg = '' state = TEXT } else if (state === COMMENT && /-$/.test(reg) && c === '-') { @@ -175,6 +211,7 @@ module.exports = function (h, opts) { res.push([ATTR_VALUE,reg.substr(0, reg.length - 1)]) } reg = '' + isSelfClosing = true state = TEXT } else if (state === OPEN && /^!--$/.test(reg)) { if (opts.comments) { @@ -185,11 +222,13 @@ module.exports = function (h, opts) { } else if (state === TEXT || state === COMMENT) { reg += c } else if (state === OPEN && c === '/' && reg.length) { - // no-op, self closing tag without a space
+ // self closing tag without a space
+ isSelfClosing = true + } else if (state === OPEN && /\s/.test(c)) { - if (reg.length) { + if (reg.length) res.push([OPEN, reg]) - } + reg = '' state = ATTR } else if (state === OPEN) { @@ -198,7 +237,8 @@ module.exports = function (h, opts) { state = ATTR_KEY reg = c } else if (state === ATTR && /\s/.test(c)) { - if (reg.length) res.push([ATTR_KEY,reg]) + if (reg.length) + res.push([ATTR_KEY,reg]) res.push([ATTR_BREAK]) } else if (state === ATTR_KEY && /\s/.test(c)) { res.push([ATTR_KEY,reg]) @@ -208,6 +248,10 @@ module.exports = function (h, opts) { res.push([ATTR_KEY,reg],[ATTR_EQ]) reg = '' state = ATTR_VALUE_W + } else if (state === ATTR_KEY && c === '/') { + isSelfClosing = true + reg='' + state = ATTR } else if (state === ATTR_KEY) { reg += c } else if ((state === ATTR_KEY_W || state === ATTR) && c === '=') { @@ -218,7 +262,11 @@ module.exports = function (h, opts) { if (/[\w-]/.test(c)) { reg += c state = ATTR_KEY - } else state = ATTR + } else if (c === '/') { + isSelfClosing = true + } else { + state = ATTR + } } else if (state === ATTR_VALUE_W && c === '"') { state = ATTR_VALUE_DQ } else if (state === ATTR_VALUE_W && c === "'") { @@ -276,6 +324,16 @@ function quot (state) { return state === ATTR_VALUE_SQ || state === ATTR_VALUE_DQ } +//area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr +var voidCloseRE = RegExp('^(' + [ + 'area', 'base', 'br', 'col', 'command', 'embed', + 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', + 'source', 'track', 'wbr' +].join('|') + ')(?:[\.#][a-zA-Z0-9\u007F-\uFFFF_:-]+)*$') + +function selfClosingVoid (tag) { return voidCloseRE.test(tag) } + +/* var closeRE = RegExp('^(' + [ 'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param', @@ -292,4 +350,6 @@ var closeRE = RegExp('^(' + [ 'path', 'polygon', 'polyline', 'rect', 'set', 'stop', 'tref', 'use', 'view', 'vkern' ].join('|') + ')(?:[\.#][a-zA-Z0-9\u007F-\uFFFF_:-]+)*$') + function selfClosing (tag) { return closeRE.test(tag) } +*/ diff --git a/test/svg.js b/test/svg.js index 1ee0fa1..e7f515b 100644 --- a/test/svg.js +++ b/test/svg.js @@ -19,3 +19,28 @@ test('svg mixed with html', function (t) { t.equal(vdom.create(tree).toString(), expected) t.end() }) + +test('svg mixed with html and close / self-closing tags', function (t) { + var expected = `
+

test

+ + + + + + + +
` + var tree = hx`
+

test

+ + + + + + + +
` + t.equal(vdom.create(tree).toString(), expected) + t.end() +}) From 49d77edccda713e8c6b7e872439ac444b67be5f0 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 11 Feb 2021 07:43:05 -0800 Subject: [PATCH 02/11] restore non-es6 argument handling --- index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 1e658b6..b5ebc09 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -var attrToProp = require('hyperscript-attribute-to-property') +var attrToProp = require('https://cdn.skypack.dev/hyperscript-attribute-to-property') var VAR = 0, TEXT = 1, OPEN = 2, CLOSE = 3, ATTR = 4 @@ -8,7 +8,8 @@ var ATTR_VALUE_SQ = 9, ATTR_VALUE_DQ = 10 var ATTR_EQ = 11, ATTR_BREAK = 12 var COMMENT = 13 -module.exports = function (h, opts={}) { +module.exports = function (h, opts) { + if (!opts) opts = { } var concat = opts.concat || function (a, b) { return String(a) + String(b) From 54f8e1df6ccfa6bbc7c4b93d1a7bcccf2104701c Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 11 Feb 2021 07:44:19 -0800 Subject: [PATCH 03/11] restore module string to commonjs --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b5ebc09..e31b980 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -var attrToProp = require('https://cdn.skypack.dev/hyperscript-attribute-to-property') +var attrToProp = require('hyperscript-attribute-to-property') var VAR = 0, TEXT = 1, OPEN = 2, CLOSE = 3, ATTR = 4 From 91431f56b93e9d58c189320d9f7a1d7eadd93480 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 1 Jan 2022 12:37:15 -0800 Subject: [PATCH 04/11] support direct descendant css selectors when parsing inline ` + t.equal( + vdom.create(tree).toString(), + `` + ) + t.end() +}) From f6513fcc215b8dbe8fbf98e600b49b20d0595015 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 1 Jan 2022 12:38:29 -0800 Subject: [PATCH 05/11] update deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index de50b87..ad4863a 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "license": "BSD", "devDependencies": { "covert": "^1.1.0", - "hyperscript": "^1.4.7", - "tape": "^4.4.0", + "hyperscript": "^2.0.2", + "tape": "^5.4.0", "virtual-dom": "^2.1.1" }, "dependencies": { From 799628cffbd2d389aba6449020621bb6ac48c343 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 1 Jan 2022 12:40:09 -0800 Subject: [PATCH 06/11] update git repo URLs for npm package --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ad4863a..1cd75a0 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/substack/hyperx.git" + "url": "git+https://github.com/mreinstein/hyperx.git" }, "bugs": { - "url": "https://github.com/substack/hyperx/issues" + "url": "https://github.com/mreinstein/hyperx/issues" }, - "homepage": "https://github.com/substack/hyperx#readme" + "homepage": "https://github.com/mreinstein/hyperx#readme" } From a95362892cccbffba0a0ab0fa51c178956ed7f45 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 1 Jan 2022 12:44:15 -0800 Subject: [PATCH 07/11] add details on why this module exists as a fork --- readme.markdown | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/readme.markdown b/readme.markdown index 8218bbf..d0c8df8 100644 --- a/readme.markdown +++ b/readme.markdown @@ -1,3 +1,11 @@ +# why does mreinstein/hyperx exist? + +The existing [hyperx](https://npmjs.com/package/hyperx) module is lacking maintainers right now. I've forked that repo and provide fixes for issues [#41](https://github.com/choojs/hyperx/issues/41) and [#82](https://github.com/choojs/hyperx/issues/82). + +I've [requested to become a maintainer](https://github.com/choojs/hyperx/issues/83) on the hyperx repo or for my PRs to be merged. Until then I'll maintain this (hopefully temporary) fork. + + + # hyperx tagged template string virtual dom builder From 1e50d5640f4049aca5c29cc1a869ccfb50f3ad14 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 1 Jan 2022 12:46:11 -0800 Subject: [PATCH 08/11] 2.5.5 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1cd75a0..17e7220 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "hyperx", - "version": "2.5.4", + "name": "hyperx-tmp", + "version": "2.5.5", "description": "tagged template string virtual dom builder", "main": "index.js", "scripts": { From c7bf2d67afdefed4f9a74adc4e4d762517004a7c Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 2 Jan 2022 09:53:20 -0800 Subject: [PATCH 09/11] handle + reg += c + } else { + res.push([CLOSE, isSelfClosing]) + isSelfClosing = false + reg = '' + } + state = TEXT + } else if (state === COMMENT && /-$/.test(reg) && c === '-') { if (opts.comments) { res.push([ATTR_VALUE,reg.substr(0, reg.length - 1)]) @@ -242,6 +251,11 @@ module.exports = function (h, opts) { if (reg.length) res.push([OPEN, reg]) + if (reg === 'style') + isInStyleTag = true + else if (reg === '/style') + isInStyleTag = false + reg = '' state = ATTR } else if (state === OPEN) { diff --git a/test/style.js b/test/style.js index 9345443..14cd390 100644 --- a/test/style.js +++ b/test/style.js @@ -35,3 +35,22 @@ test('embedded style', function (t) { ) t.end() }) + +test('embedded style with attributes', function (t) { + var key = 'type' + var value = 'text' + var tree = hx`` + t.equal( + vdom.create(tree).toString(), + `` + ) + t.end() +}) From 361b647599715b1a2ca736b10da4f1776ce371b0 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 2 Jan 2022 09:54:00 -0800 Subject: [PATCH 10/11] 2.5.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17e7220..509caeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hyperx-tmp", - "version": "2.5.5", + "version": "2.5.6", "description": "tagged template string virtual dom builder", "main": "index.js", "scripts": { From b748594436e868ee3fb12e3a095e937041037338 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 29 Oct 2023 17:43:33 -0700 Subject: [PATCH 11/11] remove note about why my feature fork exists, in prep for the mmerge --- readme.markdown | 8 -------- 1 file changed, 8 deletions(-) diff --git a/readme.markdown b/readme.markdown index d0c8df8..8218bbf 100644 --- a/readme.markdown +++ b/readme.markdown @@ -1,11 +1,3 @@ -# why does mreinstein/hyperx exist? - -The existing [hyperx](https://npmjs.com/package/hyperx) module is lacking maintainers right now. I've forked that repo and provide fixes for issues [#41](https://github.com/choojs/hyperx/issues/41) and [#82](https://github.com/choojs/hyperx/issues/82). - -I've [requested to become a maintainer](https://github.com/choojs/hyperx/issues/83) on the hyperx repo or for my PRs to be merged. Until then I'll maintain this (hopefully temporary) fork. - - - # hyperx tagged template string virtual dom builder