├── .gitignore
├── example
├── deku
│ ├── index.html
│ ├── package.json
│ └── main.js
├── main_loop
│ ├── index.html
│ ├── package.json
│ └── main.js
├── react_browser
│ ├── index.html
│ ├── package.json
│ └── main.js
├── hyperscript.js
├── vdom.js
└── react.js
├── test
├── title.js
├── multi_elem_error.js
├── style.js
├── z_hyperscript.js
├── ignore_surounding_whitespace.js
├── svg.js
├── vdom.js
├── esc.js
├── types.js
├── children.js
├── value.js
├── concat.js
├── key.js
└── attr.js
├── bench
├── raw.js
└── loop.js
├── package.json
├── LICENSE
├── readme.markdown
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | example/*/bundle.js
3 |
--------------------------------------------------------------------------------
/example/deku/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/example/main_loop/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/example/react_browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/example/deku/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "watch": "watchify main.js -o bundle.js -dv",
4 | "build": "browserify main.js > bundle.js"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/main_loop/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "watch": "watchify main.js -o bundle.js -dv",
4 | "build": "browserify main.js > bundle.js"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/react_browser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "watch": "watchify main.js -o bundle.js -dv",
4 | "build": "browserify main.js > bundle.js"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/title.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('title html tag', function (t) {
7 | var tree = hx`hello`
8 | t.equal(vdom.create(tree).toString(), 'hello')
9 | t.end()
10 | })
11 |
--------------------------------------------------------------------------------
/test/multi_elem_error.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('multiple element error', function (t) {
7 | t.plan(1)
8 | t.throws(function () {
9 | var tree = hx`one
two
`
10 | }, 'exception')
11 | t.end()
12 | })
13 |
--------------------------------------------------------------------------------
/example/hyperscript.js:
--------------------------------------------------------------------------------
1 | var h = require('hyperscript')
2 | var hyperx = require('../')
3 | var hx = hyperx(h)
4 |
5 | var title = 'world'
6 | var wow = [1,2,3]
7 | var tree = hx`
8 |
hello ${title}!
9 | ${hx`cool`}
10 | wow
11 | ${wow.map(function (w) {
12 | return hx`${w}\n`
13 | })}
14 | `
15 | console.log(tree.outerHTML)
16 |
--------------------------------------------------------------------------------
/example/vdom.js:
--------------------------------------------------------------------------------
1 | var vdom = require('virtual-dom')
2 | var hyperx = require('../')
3 | var hx = hyperx(vdom.h)
4 |
5 | var title = 'world'
6 | var wow = [1,2,3]
7 | var tree = hx`
8 |
hello ${title}!
9 | ${hx`cool`}
10 | wow
11 | ${wow.map(function (w, i) {
12 | return hx`${w}\n`
13 | })}
14 | `
15 | console.log(vdom.create(tree).toString())
16 |
--------------------------------------------------------------------------------
/example/react.js:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var toString = require('react-dom/server').renderToString
3 | var hyperx = require('../')
4 | var hx = hyperx(React.createElement)
5 |
6 | var title = 'world'
7 | var wow = [1,2,3]
8 | var tree = hx`
9 |
hello ${title}!
10 | ${hx`cool`}
11 | wow
12 | ${wow.map(function (w, i) {
13 | return hx`${w}\n`
14 | })}
15 | `
16 | console.log(toString(tree))
17 |
--------------------------------------------------------------------------------
/test/style.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('style', function (t) {
7 | var key = 'type'
8 | var value = 'text'
9 | var tree = hx``
12 | t.equal(
13 | vdom.create(tree).toString(),
14 | ''
15 | )
16 | t.end()
17 | })
18 |
--------------------------------------------------------------------------------
/example/deku/main.js:
--------------------------------------------------------------------------------
1 | var deku = require('deku')
2 | var hyperx = require('../..')
3 | var hx = hyperx(deku.element)
4 |
5 | var render = deku.createApp(document.querySelector('#content'))
6 |
7 | var state = {
8 | times: 0
9 | }
10 |
11 | rerender()
12 |
13 | function rerender () {
14 | render(hx`
15 |
clicked ${state.times} times
16 |
17 | `)
18 |
19 | function increment () {
20 | state.times += 1
21 | rerender()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/main_loop/main.js:
--------------------------------------------------------------------------------
1 | var vdom = require('virtual-dom')
2 | var hyperx = require('../../')
3 | var hx = hyperx(vdom.h)
4 |
5 | var main = require('main-loop')
6 | var loop = main({ times: 0 }, render, vdom)
7 | document.querySelector('#content').appendChild(loop.target)
8 |
9 | function render (state) {
10 | return hx`
11 |
clicked ${state.times} times
12 |
13 | `
14 |
15 | function onclick () {
16 | loop.update({ times: state.times + 1 })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/bench/raw.js:
--------------------------------------------------------------------------------
1 | var vdom = require('virtual-dom')
2 | var h = vdom.h
3 |
4 | function render (state) {
5 | return h('div', [
6 | h('h1', { y: 'ab' + (1+2) + 'cd' }, [
7 | 'hello ' + state.title + '!'
8 | ]),
9 | h('i', 'cool'),
10 | 'wow'
11 | ].concat(state.wow.map(function (w, i) {
12 | return h('b', {}, w)
13 | })))
14 | }
15 |
16 | var start = Date.now()
17 | var N = 10000
18 | for (var i = 0; i < N; i++) {
19 | render({ title: 'hello' + i, wow: [1,2,3] })
20 | }
21 | var elapsed = Date.now() - start
22 | console.log(elapsed / N, 'ms')
23 |
--------------------------------------------------------------------------------
/bench/loop.js:
--------------------------------------------------------------------------------
1 | var vdom = require('virtual-dom')
2 | var hyperx = require('../')
3 | var hx = hyperx(vdom.h)
4 |
5 | function render (state) {
6 | return hx`
7 |
hello ${state.title}!
8 | cool
9 | wow
10 | ${state.wow.map(function (w, i) {
11 | return hx`${w}\n`
12 | })}
13 | `
14 | }
15 |
16 | var start = Date.now()
17 | var N = 10000
18 | for (var i = 0; i < N; i++) {
19 | render({ title: 'hello' + i, wow: [1,2,3] })
20 | }
21 | var elapsed = Date.now() - start
22 | console.log(elapsed / N, 'ms')
23 |
--------------------------------------------------------------------------------
/example/react_browser/main.js:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var render = require('react-dom').render
3 | var hyperx = require('../../')
4 | var hx = hyperx(React.createElement)
5 |
6 | var App = React.createClass({
7 | getInitialState: function () { return { n: 0 } },
8 | render: function () {
9 | return hx`
10 |
clicked ${this.state.n} times
11 |
12 | `
13 | },
14 | handleClick: function () {
15 | this.setState({ n: this.state.n + 1 })
16 | }
17 | })
18 | render(React.createElement(App), document.querySelector('#content'))
19 |
--------------------------------------------------------------------------------
/test/z_hyperscript.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var h = require('hyperscript')
3 | var hyperx = require('../')
4 | var hx = hyperx(h)
5 |
6 | var expected = `
7 |
hello world!
8 | cool
9 | wow
10 | 123
11 | `
12 |
13 | test('hyperscript', function (t) {
14 | var title = 'world'
15 | var wow = [1,2,3]
16 | var tree = hx`
17 |
hello ${title}!
18 | ${hx`cool`}
19 | wow
20 | ${wow.map(function (w) {
21 | return hx`${w}\n`
22 | })}
23 | `
24 | t.equal(tree.outerHTML, expected)
25 | t.end()
26 | })
27 |
--------------------------------------------------------------------------------
/test/ignore_surounding_whitespace.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('ignore whitespace surrounding an element', function (t) {
7 | var tree = hx``;
8 | t.equal(vdom.create(tree).toString(), '')
9 | tree = hx`
10 | `;
11 | t.equal(vdom.create(tree).toString(), '')
12 | tree = hx`
13 |
14 | `;
15 | t.equal(vdom.create(tree).toString(), '')
16 | // It shouldn't strip whitespace from a text node
17 | t.equal(hx` hello world `, ' hello world ')
18 | t.end()
19 | })
20 |
--------------------------------------------------------------------------------
/test/svg.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('svg mixed with html', function (t) {
7 | var expected = `
8 |
test
9 |
12 | `
13 | var tree = hx`
14 |
test
15 |
18 | `
19 | t.equal(vdom.create(tree).toString(), expected)
20 | t.end()
21 | })
22 |
--------------------------------------------------------------------------------
/test/vdom.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | var expected = `
7 |
hello world!
8 | cool
9 | wow
10 | 123
11 | `
12 |
13 | test('vdom', function (t) {
14 | var title = 'world'
15 | var wow = [1,2,3]
16 | var tree = hx`
17 |
hello ${title}!
18 | ${hx`cool`}
19 | wow
20 | ${wow.map(function (w, i) {
21 | return hx`${w}\n`
22 | })}
23 | `
24 | t.equal(vdom.create(tree).toString(), expected)
25 | t.end()
26 | })
27 |
--------------------------------------------------------------------------------
/test/esc.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('escape double quotes', function (t) {
7 | var value = '">'
8 | var tree = hx``
9 | t.equal(
10 | vdom.create(tree).toString(),
11 | ``
12 | )
13 | t.end()
14 | })
15 |
16 | test('escape single quotes', function (t) {
17 | var value = "'>"
18 | var tree = hx``
19 | t.equal(
20 | vdom.create(tree).toString(),
21 | ``
22 | )
23 | t.end()
24 | })
25 |
--------------------------------------------------------------------------------
/test/types.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('undefined value (empty)', function (t) {
7 | var tree = hx`${undefined}
`
8 | t.equal(vdom.create(tree).toString(), '')
9 | t.end()
10 | })
11 |
12 | test('null value (empty)', function (t) {
13 | var tree = hx`${null}
`
14 | t.equal(vdom.create(tree).toString(), '')
15 | t.end()
16 | })
17 |
18 | test('boolean value', function (t) {
19 | var tree = hx`${false}
`
20 | t.equal(vdom.create(tree).toString(), 'false
')
21 | t.end()
22 | })
23 |
24 | test('numeric value', function (t) {
25 | var tree = hx`${555}
`
26 | t.equal(vdom.create(tree).toString(), '555
')
27 | t.end()
28 | })
29 |
--------------------------------------------------------------------------------
/test/children.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('1 child', function (t) {
7 | var tree = hx`foobar
`
8 | t.equal(vdom.create(tree).toString(), 'foobar
')
9 | t.end()
10 | })
11 |
12 | test('no children', function (t) {
13 | var tree = hx`
`
14 | t.equal(vdom.create(tree).toString(), '
')
15 | t.end()
16 | })
17 |
18 | test('multiple children', function (t) {
19 | var html = `
20 |
title
21 |
22 |
23 | -
24 | click
25 |
26 |
27 |
28 |
`
29 | var tree = hx`
30 |
31 |
title
32 |
33 |
34 | -
35 | click
36 |
37 |
38 |
39 |
`
40 | t.equal(vdom.create(tree).toString(), html)
41 | t.end()
42 | })
43 |
--------------------------------------------------------------------------------
/test/value.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('value', function (t) {
7 | var key = 'type'
8 | var value = 'text'
9 | var tree = hx``
10 | t.equal(vdom.create(tree).toString(), '')
11 | t.end()
12 | })
13 |
14 | test('pre value', function (t) {
15 | var key = 'type'
16 | var value = 'ext'
17 | var tree = hx``
18 | t.equal(vdom.create(tree).toString(), '')
19 | t.end()
20 | })
21 |
22 | test('post key', function (t) {
23 | var key = 'type'
24 | var value = 'tex'
25 | var tree = hx``
26 | t.equal(vdom.create(tree).toString(), '')
27 | t.end()
28 | })
29 |
30 | test('pre post key', function (t) {
31 | var key = 'type'
32 | var value = 'ex'
33 | var tree = hx``
34 | t.equal(vdom.create(tree).toString(), '')
35 | t.end()
36 | })
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hyperx",
3 | "version": "2.0.4",
4 | "description": "tagged template string virtual dom builder",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "tape test/*.js",
8 | "coverage": "covert test/*.js"
9 | },
10 | "keywords": [
11 | "jsx",
12 | "virtual-dom",
13 | "vdom",
14 | "react",
15 | "hyperscript",
16 | "template string",
17 | "template",
18 | "es6"
19 | ],
20 | "author": "substack",
21 | "license": "BSD",
22 | "devDependencies": {
23 | "covert": "^1.1.0",
24 | "hyperscript": "^1.4.7",
25 | "tape": "^4.4.0",
26 | "virtual-dom": "^2.1.1"
27 | },
28 | "dependencies": {
29 | "hyperscript-attribute-to-property": "^1.0.0"
30 | },
31 | "directories": {
32 | "example": "example",
33 | "test": "test"
34 | },
35 | "repository": {
36 | "type": "git",
37 | "url": "git+https://github.com/substack/hyperx.git"
38 | },
39 | "bugs": {
40 | "url": "https://github.com/substack/hyperx/issues"
41 | },
42 | "homepage": "https://github.com/substack/hyperx#readme"
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, James Halliday
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this list
8 | of conditions and the following disclaimer.
9 |
10 | Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/test/concat.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(function (tagName, opts, children) {
5 | return {
6 | expr: 'h(' + JSON.stringify(tagName)
7 | + ',' + JSON.stringify(opts)
8 | + ',[' + children.map(child).join(',')
9 | + '])'
10 | }
11 | function child (c) {
12 | if (c.expr) return c.expr
13 | else if (Array.isArray(c)) return c.map(child).join(',')
14 | else return JSON.stringify(c)
15 | }
16 | }, { concat: concat })
17 |
18 | function concat (a, b) {
19 | if (!a.expr && !b.expr) return String(a) + String(b)
20 | var aexpr, bexpr
21 | if (a.expr) aexpr = '(' + a.expr + ')'
22 | else aexpr = JSON.stringify(a)
23 | if (b.expr) bexpr = '(' + b.expr + ')'
24 | else bexpr = JSON.stringify(b)
25 | return { expr: aexpr + '+' + bexpr }
26 | }
27 |
28 | var expected = `
29 |
hello world!
30 | cool
31 | wow
32 | 123
33 | `
34 |
35 | test('vdom', function (t) {
36 | var title = 'world'
37 | var wow = [1,2,3]
38 | var str = hx`
39 |
hello ${title}!
40 | ${hx`cool`}
41 | wow
42 | ${wow.map(function (w, i) {
43 | return hx`${w}\n`
44 | })}
45 | `.expr
46 | var tree = Function(['h'],'return ' + str)(vdom.h)
47 | t.equal(vdom.create(tree).toString(), expected)
48 | t.end()
49 | })
50 |
--------------------------------------------------------------------------------
/test/key.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('key', function (t) {
7 | var key = 'type'
8 | var value = 'text'
9 | var tree = hx``
10 | t.equal(vdom.create(tree).toString(), '')
11 | t.end()
12 | })
13 |
14 | test('pre key', function (t) {
15 | var key = 'ype'
16 | var value = 'text'
17 | var tree = hx``
18 | t.equal(vdom.create(tree).toString(), '')
19 | t.end()
20 | })
21 |
22 | test('post key', function (t) {
23 | var key = 'typ'
24 | var value = 'text'
25 | var tree = hx``
26 | t.equal(vdom.create(tree).toString(), '')
27 | t.end()
28 | })
29 |
30 | test('pre post key', function (t) {
31 | var key = 'yp'
32 | var value = 'text'
33 | var tree = hx``
34 | t.equal(vdom.create(tree).toString(), '')
35 | t.end()
36 | })
37 |
38 | test('boolean key', function (t) {
39 | var key = 'checked'
40 | var tree = hx``
41 | t.equal(vdom.create(tree).toString(),
42 | '')
43 | t.end()
44 | })
45 |
46 | test('multiple keys', function (t) {
47 | var props = {
48 | type: 'text',
49 | 'data-special': 'true'
50 | }
51 | var key = 'data-'
52 | var value = 'bar'
53 | var tree = hx``
54 | t.equal(vdom.create(tree).toString(), '')
55 | t.end()
56 | })
57 |
58 | test('multiple keys dont overwrite existing ones', function (t) {
59 | var props = {
60 | type: 'text'
61 | }
62 | var tree = hx``
63 | t.equal(vdom.create(tree).toString(), '')
64 | t.end()
65 | })
66 |
--------------------------------------------------------------------------------
/test/attr.js:
--------------------------------------------------------------------------------
1 | var test = require('tape')
2 | var vdom = require('virtual-dom')
3 | var hyperx = require('../')
4 | var hx = hyperx(vdom.h)
5 |
6 | test('class', function (t) {
7 | var tree = hx``
8 | t.equal(vdom.create(tree).toString(), '')
9 | t.end()
10 | })
11 |
12 | test('boolean attribute', function (t) {
13 | var tree = hx``
14 | t.equal(vdom.create(tree).toString(), '')
15 | t.end()
16 | })
17 |
18 | test('boolean attribute followed by normal attribute', function (t) {
19 | var tree = hx``
20 | t.equal(vdom.create(tree).toString(), '')
21 | t.end()
22 | })
23 |
24 | test('boolean attribute preceded by normal attribute', function (t) {
25 | var tree = hx``
26 | t.equal(vdom.create(tree).toString(), '')
27 | t.end()
28 | })
29 |
30 | test('unquoted attribute', function (t) {
31 | var tree = hx``
32 | t.equal(vdom.create(tree).toString(), '')
33 | t.end()
34 | })
35 |
36 | test('unquoted attribute preceded by boolean attribute', function (t) {
37 | var tree = hx``
38 | t.equal(vdom.create(tree).toString(), '')
39 | t.end()
40 | })
41 |
42 | test('unquoted attribute succeeded by boolean attribute', function (t) {
43 | var tree = hx``
44 | t.equal(vdom.create(tree).toString(), '')
45 | t.end()
46 | })
47 |
48 | test('unquoted attribute preceded by normal attribute', function (t) {
49 | var tree = hx``
50 | t.equal(vdom.create(tree).toString(), '')
51 | t.end()
52 | })
53 |
54 | test('unquoted attribute succeeded by normal attribute', function (t) {
55 | var tree = hx``
56 | t.equal(vdom.create(tree).toString(), '')
57 | t.end()
58 | })
59 |
60 | test('consecutive unquoted attributes', function (t) {
61 | var tree = hx``
62 | t.equal(vdom.create(tree).toString(), '')
63 | t.end()
64 | })
65 |
--------------------------------------------------------------------------------
/readme.markdown:
--------------------------------------------------------------------------------
1 | # hyperx
2 |
3 | tagged template string virtual dom builder
4 |
5 | This module is similar to JSX, but provided as a standards-compliant ES6 tagged
6 | template string function.
7 |
8 | hyperx works with [virtual-dom](https://npmjs.com/package/virtual-dom),
9 | [react](https://npmjs.com/package/react),
10 | [hyperscript](https://npmjs.com/package/hyperscript), or any DOM builder with a
11 | hyperscript-style API: `h(tagName, attrs, children)`.
12 |
13 | You might also want to check out the [hyperxify][2] browserify transform to
14 | statically compile hyperx into javascript expressions to save sending the hyperx
15 | parser down the wire.
16 |
17 | [2]: https://npmjs.com/package/hyperxify
18 |
19 | # compatibility
20 |
21 | [Template strings][1] are available in:
22 | node 4+, chrome 41, firefox 34, edge, opera 28, safari 9
23 |
24 | If you're targeting these platforms, there's no need to use a transpiler!
25 |
26 | [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings
27 |
28 | # examples
29 |
30 | ## virtual-dom node example
31 |
32 | ``` js
33 | var vdom = require('virtual-dom')
34 | var hyperx = require('hyperx')
35 | var hx = hyperx(vdom.h)
36 |
37 | var title = 'world'
38 | var wow = [1,2,3]
39 | var tree = hx`
40 |
hello ${title}!
41 | ${hx`cool`}
42 | wow
43 | ${wow.map(function (w, i) {
44 | return hx`${w}\n`
45 | })}
46 | `
47 | console.log(vdom.create(tree).toString())
48 | ```
49 |
50 | output:
51 |
52 | ```
53 | $ node vdom.js
54 |
55 |
hello world!
56 | cool
57 | wow
58 | 123
59 |
60 | ```
61 |
62 | ## react node example
63 |
64 | ``` js
65 | var React = require('react')
66 | var toString = require('react-dom/server').renderToString
67 | var hyperx = require('hyperx')
68 | var hx = hyperx(React.createElement)
69 |
70 | var title = 'world'
71 | var wow = [1,2,3]
72 | var tree = hx`
73 |
hello ${title}!
74 | ${hx`cool`}
75 | wow
76 | ${wow.map(function (w, i) {
77 | return hx`${w}\n`
78 | })}
79 | `
80 | console.log(toString(tree))
81 | ```
82 |
83 | ## hyperscript node example
84 |
85 | ``` js
86 | var h = require('hyperscript')
87 | var hyperx = require('hyperx')
88 | var hx = hyperx(h)
89 |
90 | var title = 'world'
91 | var wow = [1,2,3]
92 | var tree = hx`
93 |
hello ${title}!
94 | ${hx`cool`}
95 | wow
96 | ${wow.map(function (w) {
97 | return hx`${w}\n`
98 | })}
99 | `
100 | console.log(tree.outerHTML)
101 | ```
102 |
103 | ## virtual-dom/main-loop browser example
104 |
105 | ``` js
106 | var vdom = require('virtual-dom')
107 | var hyperx = require('hyperx')
108 | var hx = hyperx(vdom.h)
109 |
110 | var main = require('main-loop')
111 | var loop = main({ times: 0 }, render, vdom)
112 | document.querySelector('#content').appendChild(loop.target)
113 |
114 | function render (state) {
115 | return hx`
116 |
clicked ${state.times} times
117 |
118 | `
119 |
120 | function onclick () {
121 | loop.update({ times: state.times + 1 })
122 | }
123 | }
124 | ```
125 |
126 | ## react browser example
127 |
128 | ``` js
129 | var React = require('react')
130 | var render = require('react-dom').render
131 | var hyperx = require('hyperx')
132 | var hx = hyperx(React.createElement)
133 |
134 | var App = React.createClass({
135 | getInitialState: function () { return { n: 0 } },
136 | render: function () {
137 | return hx`
138 |
clicked ${this.state.n} times
139 |
140 | `
141 | },
142 | handleClick: function () {
143 | this.setState({ n: this.state.n + 1 })
144 | }
145 | })
146 | render(React.createElement(App), document.querySelector('#content'))
147 | ```
148 |
149 | ## console.log example
150 |
151 | ``` js
152 | var hyperx = require('hyperx')
153 |
154 | var convertTaggedTemplateOutputToDomBuilder = hyperx(function (tagName, attrs, children) {
155 | console.log(tagName, attrs, children)
156 | })
157 |
158 | convertTaggedTemplateOutputToDomBuilder`hello world
`
159 |
160 | // Running this produces: h1 {} [ 'hello world' ]
161 | ```
162 |
163 |
164 | # api
165 |
166 | ```
167 | var hyperx = require('hyperx')
168 | ```
169 |
170 | ## var hx = hyperx(h, opts={})
171 |
172 | Return a tagged template function `hx` from a hyperscript-style factory function
173 | `h`.
174 |
175 | Values to use for `h`:
176 |
177 | * virtual-dom - `vdom.h`
178 | * react - `React.createElement`
179 | * hyperscript - hyperscript
180 |
181 | Optionally provide:
182 |
183 | * `opts.concat(a, b)` - custom concatenation function to combine quasiliteral
184 | strings with expressions. The `h` factory function will receive the objects
185 | returned by the concatenation function and can make specific use of them. This
186 | is useful if you want to implement a pre-processor to generate javascript from
187 | hyperx syntax.
188 |
189 | # prior art
190 |
191 | * http://www.2ality.com/2014/07/jsx-template-strings.html?m=1
192 | * http://facebook.github.io/jsx/#why-not-template-literals (respectfully disagree)
193 |
194 | # license
195 |
196 | BSD
197 |
198 | # install
199 |
200 | ```
201 | npm install hyperx
202 | ```
203 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var attrToProp = require('hyperscript-attribute-to-property')
2 |
3 | var VAR = 0, TEXT = 1, OPEN = 2, CLOSE = 3, ATTR = 4
4 | var ATTR_KEY = 5, ATTR_KEY_W = 6
5 | var ATTR_VALUE_W = 7, ATTR_VALUE = 8
6 | var ATTR_VALUE_SQ = 9, ATTR_VALUE_DQ = 10
7 | var ATTR_EQ = 11, ATTR_BREAK = 12
8 |
9 | module.exports = function (h, opts) {
10 | h = attrToProp(h)
11 | if (!opts) opts = {}
12 | var concat = opts.concat || function (a, b) {
13 | return String(a) + String(b)
14 | }
15 |
16 | return function (strings) {
17 | var state = TEXT, reg = ''
18 | var arglen = arguments.length
19 | var parts = []
20 |
21 | for (var i = 0; i < strings.length; i++) {
22 | if (i < arglen - 1) {
23 | var arg = arguments[i+1]
24 | var p = parse(strings[i])
25 | var xstate = state
26 | if (xstate === ATTR_VALUE_DQ) xstate = ATTR_VALUE
27 | if (xstate === ATTR_VALUE_SQ) xstate = ATTR_VALUE
28 | if (xstate === ATTR_VALUE_W) xstate = ATTR_VALUE
29 | if (xstate === ATTR) xstate = ATTR_KEY
30 | p.push([ VAR, xstate, arg ])
31 | parts.push.apply(parts, p)
32 | } else parts.push.apply(parts, parse(strings[i]))
33 | }
34 |
35 | var tree = [null,{},[]]
36 | var stack = [[tree,-1]]
37 | for (var i = 0; i < parts.length; i++) {
38 | var cur = stack[stack.length-1][0]
39 | var p = parts[i], s = p[0]
40 | if (s === OPEN && /^\//.test(p[1])) {
41 | var ix = stack[stack.length-1][1]
42 | if (stack.length > 1) {
43 | stack.pop()
44 | stack[stack.length-1][0][2][ix] = h(
45 | cur[0], cur[1], cur[2].length ? cur[2] : undefined
46 | )
47 | }
48 | } else if (s === OPEN) {
49 | var c = [p[1],{},[]]
50 | cur[2].push(c)
51 | stack.push([c,cur[2].length-1])
52 | } else if (s === ATTR_KEY || (s === VAR && p[1] === ATTR_KEY)) {
53 | var key = ''
54 | var copyKey
55 | for (; i < parts.length; i++) {
56 | if (parts[i][0] === ATTR_KEY) {
57 | key = concat(key, parts[i][1])
58 | } else if (parts[i][0] === VAR && parts[i][1] === ATTR_KEY) {
59 | if (typeof parts[i][2] === 'object' && !key) {
60 | for (copyKey in parts[i][2]) {
61 | if (parts[i][2].hasOwnProperty(copyKey) && !cur[1][copyKey]) {
62 | cur[1][copyKey] = parts[i][2][copyKey]
63 | }
64 | }
65 | } else {
66 | key = concat(key, parts[i][2])
67 | }
68 | } else break
69 | }
70 | if (parts[i][0] === ATTR_EQ) i++
71 | var j = i
72 | for (; i < parts.length; i++) {
73 | if (parts[i][0] === ATTR_VALUE || parts[i][0] === ATTR_KEY) {
74 | if (!cur[1][key]) cur[1][key] = strfn(parts[i][1])
75 | else cur[1][key] = concat(cur[1][key], parts[i][1])
76 | } else if (parts[i][0] === VAR
77 | && (parts[i][1] === ATTR_VALUE || parts[i][1] === ATTR_KEY)) {
78 | if (!cur[1][key]) cur[1][key] = strfn(parts[i][2])
79 | else cur[1][key] = concat(cur[1][key], parts[i][2])
80 | } else {
81 | if (key.length && !cur[1][key] && i === j
82 | && (parts[i][0] === CLOSE || parts[i][0] === ATTR_BREAK)) {
83 | // https://html.spec.whatwg.org/multipage/infrastructure.html#boolean-attributes
84 | // empty string is falsy, not well behaved value in browser
85 | cur[1][key] = key.toLowerCase()
86 | }
87 | break
88 | }
89 | }
90 | } else if (s === ATTR_KEY) {
91 | cur[1][p[1]] = true
92 | } else if (s === VAR && p[1] === ATTR_KEY) {
93 | cur[1][p[2]] = true
94 | } else if (s === CLOSE) {
95 | if (selfClosing(cur[0]) && stack.length) {
96 | var ix = stack[stack.length-1][1]
97 | stack.pop()
98 | stack[stack.length-1][0][2][ix] = h(
99 | cur[0], cur[1], cur[2].length ? cur[2] : undefined
100 | )
101 | }
102 | } else if (s === VAR && p[1] === TEXT) {
103 | if (p[2] === undefined || p[2] === null) p[2] = ''
104 | else if (!p[2]) p[2] = concat('', p[2])
105 | if (Array.isArray(p[2][0])) {
106 | cur[2].push.apply(cur[2], p[2])
107 | } else {
108 | cur[2].push(p[2])
109 | }
110 | } else if (s === TEXT) {
111 | cur[2].push(p[1])
112 | } else if (s === ATTR_EQ || s === ATTR_BREAK) {
113 | // no-op
114 | } else {
115 | throw new Error('unhandled: ' + s)
116 | }
117 | }
118 |
119 | if (tree[2].length > 1 && /^\s*$/.test(tree[2][0])) {
120 | tree[2].shift()
121 | }
122 |
123 | if (tree[2].length > 2
124 | || (tree[2].length === 2 && /\S/.test(tree[2][1]))) {
125 | throw new Error(
126 | 'multiple root elements must be wrapped in an enclosing tag'
127 | )
128 | }
129 | if (Array.isArray(tree[2][0]) && typeof tree[2][0][0] === 'string'
130 | && Array.isArray(tree[2][0][2])) {
131 | tree[2][0] = h(tree[2][0][0], tree[2][0][1], tree[2][0][2])
132 | }
133 | return tree[2][0]
134 |
135 | function parse (str) {
136 | var res = []
137 | if (state === ATTR_VALUE_W) state = ATTR
138 | for (var i = 0; i < str.length; i++) {
139 | var c = str.charAt(i)
140 | if (state === TEXT && c === '<') {
141 | if (reg.length) res.push([TEXT, reg])
142 | reg = ''
143 | state = OPEN
144 | } else if (c === '>' && !quot(state)) {
145 | if (state === OPEN) {
146 | res.push([OPEN,reg])
147 | } else if (state === ATTR_KEY) {
148 | res.push([ATTR_KEY,reg])
149 | } else if (state === ATTR_VALUE && reg.length) {
150 | res.push([ATTR_VALUE,reg])
151 | }
152 | res.push([CLOSE])
153 | reg = ''
154 | state = TEXT
155 | } else if (state === TEXT) {
156 | reg += c
157 | } else if (state === OPEN && /\s/.test(c)) {
158 | res.push([OPEN, reg])
159 | reg = ''
160 | state = ATTR
161 | } else if (state === OPEN) {
162 | reg += c
163 | } else if (state === ATTR && /[\w-]/.test(c)) {
164 | state = ATTR_KEY
165 | reg = c
166 | } else if (state === ATTR && /\s/.test(c)) {
167 | if (reg.length) res.push([ATTR_KEY,reg])
168 | res.push([ATTR_BREAK])
169 | } else if (state === ATTR_KEY && /\s/.test(c)) {
170 | res.push([ATTR_KEY,reg])
171 | reg = ''
172 | state = ATTR_KEY_W
173 | } else if (state === ATTR_KEY && c === '=') {
174 | res.push([ATTR_KEY,reg],[ATTR_EQ])
175 | reg = ''
176 | state = ATTR_VALUE_W
177 | } else if (state === ATTR_KEY) {
178 | reg += c
179 | } else if ((state === ATTR_KEY_W || state === ATTR) && c === '=') {
180 | res.push([ATTR_EQ])
181 | state = ATTR_VALUE_W
182 | } else if ((state === ATTR_KEY_W || state === ATTR) && !/\s/.test(c)) {
183 | res.push([ATTR_BREAK])
184 | if (/[\w-]/.test(c)) {
185 | reg += c
186 | state = ATTR_KEY
187 | } else state = ATTR
188 | } else if (state === ATTR_VALUE_W && c === '"') {
189 | state = ATTR_VALUE_DQ
190 | } else if (state === ATTR_VALUE_W && c === "'") {
191 | state = ATTR_VALUE_SQ
192 | } else if (state === ATTR_VALUE_DQ && c === '"') {
193 | res.push([ATTR_VALUE,reg],[ATTR_BREAK])
194 | reg = ''
195 | state = ATTR
196 | } else if (state === ATTR_VALUE_SQ && c === "'") {
197 | res.push([ATTR_VALUE,reg],[ATTR_BREAK])
198 | reg = ''
199 | state = ATTR
200 | } else if (state === ATTR_VALUE_W && !/\s/.test(c)) {
201 | state = ATTR_VALUE
202 | i--
203 | } else if (state === ATTR_VALUE && /\s/.test(c)) {
204 | res.push([ATTR_VALUE,reg],[ATTR_BREAK])
205 | reg = ''
206 | state = ATTR
207 | } else if (state === ATTR_VALUE || state === ATTR_VALUE_SQ
208 | || state === ATTR_VALUE_DQ) {
209 | reg += c
210 | }
211 | }
212 | if (state === TEXT && reg.length) {
213 | res.push([TEXT,reg])
214 | reg = ''
215 | } else if (state === ATTR_VALUE && reg.length) {
216 | res.push([ATTR_VALUE,reg])
217 | reg = ''
218 | } else if (state === ATTR_VALUE_DQ && reg.length) {
219 | res.push([ATTR_VALUE,reg])
220 | reg = ''
221 | } else if (state === ATTR_VALUE_SQ && reg.length) {
222 | res.push([ATTR_VALUE,reg])
223 | reg = ''
224 | } else if (state === ATTR_KEY) {
225 | res.push([ATTR_KEY,reg])
226 | reg = ''
227 | }
228 | return res
229 | }
230 | }
231 |
232 | function strfn (x) {
233 | if (typeof x === 'function') return x
234 | else if (typeof x === 'string') return x
235 | else if (x && typeof x === 'object') return x
236 | else return concat('', x)
237 | }
238 | }
239 |
240 | function quot (state) {
241 | return state === ATTR_VALUE_SQ || state === ATTR_VALUE_DQ
242 | }
243 |
244 | var hasOwn = Object.prototype.hasOwnProperty
245 | function has (obj, key) { return hasOwn.call(obj, key) }
246 |
247 | var closeRE = RegExp('^(' + [
248 | 'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'command', 'embed',
249 | 'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
250 | 'source', 'track', 'wbr',
251 | // SVG TAGS
252 | 'animate', 'animateTransform', 'circle', 'cursor', 'desc', 'ellipse',
253 | 'feBlend', 'feColorMatrix', 'feComposite',
254 | 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap',
255 | 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR',
256 | 'feGaussianBlur', 'feImage', 'feMergeNode', 'feMorphology',
257 | 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile',
258 | 'feTurbulence', 'font-face-format', 'font-face-name', 'font-face-uri',
259 | 'glyph', 'glyphRef', 'hkern', 'image', 'line', 'missing-glyph', 'mpath',
260 | 'path', 'polygon', 'polyline', 'rect', 'set', 'stop', 'tref', 'use', 'view',
261 | 'vkern'
262 | ].join('|') + ')(?:[\.#][a-zA-Z0-9\u007F-\uFFFF_:-]+)*$')
263 | function selfClosing (tag) { return closeRE.test(tag) }
264 |
--------------------------------------------------------------------------------