diff --git a/README.md b/README.md
index f2c9705..0132d1c 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,21 @@ function onclick (e) {
}
```
+### Multiple root elements
+
+If you have more than one root element they will be combined with a [DocumentFragment](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment).
+
+```js
+var html = require('nanohtml')
+
+var el = html`
+
Chashu
+ Nori
+`
+
+document.querySelector('ul').appendChild(el)
+```
+
## Static optimizations
Parsing HTML has significant overhead. Being able to parse HTML statically,
ahead of time can speed up rendering to be about twice as fast.
diff --git a/lib/babel.js b/lib/babel.js
index c4a217e..9c44105 100644
--- a/lib/babel.js
+++ b/lib/babel.js
@@ -102,6 +102,15 @@ module.exports = (babel) => {
[t.stringLiteral(text)]
)
+ /**
+ * Returns a node that creates a fragment.
+ */
+ const createFragment = (text) =>
+ t.callExpression(
+ t.memberExpression(t.identifier('document'), t.identifier('createDocumentFragment')),
+ []
+ )
+
/**
* Returns a node that sets a DOM property.
*/
@@ -185,8 +194,10 @@ module.exports = (babel) => {
const expressions = path.node.expressions
const expressionPlaceholders = expressions.map((expr, i) => getPlaceholder(i))
- const root = hyperx(transform, { comments: true }).apply(null,
- [quasis].concat(expressionPlaceholders))
+ const root = hyperx(transform, {
+ comments: true,
+ createFragment: children => transform('nanohtml-fragment', {}, children)
+ }).apply(null, [quasis].concat(expressionPlaceholders))
/**
* Convert placeholders used in the template string back to the AST nodes
@@ -219,22 +230,26 @@ module.exports = (babel) => {
const result = []
- var isCustomElement = props.is
- delete props.is
+ if (tag === 'nanohtml-fragment') {
+ result.push(t.assignmentExpression('=', id, createFragment()))
+ } else {
+ var isCustomElement = props.is
+ delete props.is
- // Use the SVG namespace for svg elements.
- if (SVG_TAGS.includes(tag)) {
- state.svgNamespaceId.used = true
+ // Use the SVG namespace for svg elements.
+ if (SVG_TAGS.includes(tag)) {
+ state.svgNamespaceId.used = true
- if (isCustomElement) {
- result.push(t.assignmentExpression('=', id, createNsCustomBuiltIn(state.svgNamespaceId, tag, isCustomElement)))
+ if (isCustomElement) {
+ result.push(t.assignmentExpression('=', id, createNsCustomBuiltIn(state.svgNamespaceId, tag, isCustomElement)))
+ } else {
+ result.push(t.assignmentExpression('=', id, createNsElement(state.svgNamespaceId, tag)))
+ }
+ } else if (isCustomElement) {
+ result.push(t.assignmentExpression('=', id, createCustomBuiltIn(tag, isCustomElement)))
} else {
- result.push(t.assignmentExpression('=', id, createNsElement(state.svgNamespaceId, tag)))
+ result.push(t.assignmentExpression('=', id, createElement(tag)))
}
- } else if (isCustomElement) {
- result.push(t.assignmentExpression('=', id, createCustomBuiltIn(tag, isCustomElement)))
- } else {
- result.push(t.assignmentExpression('=', id, createElement(tag)))
}
Object.keys(props).forEach((propName) => {
diff --git a/lib/browser.js b/lib/browser.js
index f9b34f1..3960de5 100644
--- a/lib/browser.js
+++ b/lib/browser.js
@@ -89,6 +89,18 @@ function nanoHtmlCreateElement (tag, props, children) {
return el
}
-module.exports = hyperx(nanoHtmlCreateElement, {comments: true})
+function createFragment (nodes) {
+ var fragment = document.createDocumentFragment()
+ for (var i = 0; i < nodes.length; i++) {
+ if (typeof nodes[i] === 'string') nodes[i] = document.createTextNode(nodes[i])
+ fragment.appendChild(nodes[i])
+ }
+ return fragment
+}
+
+module.exports = hyperx(nanoHtmlCreateElement, {
+ comments: true,
+ createFragment: createFragment
+})
module.exports.default = module.exports
module.exports.createElement = nanoHtmlCreateElement
diff --git a/lib/browserify-transform.js b/lib/browserify-transform.js
index 3990855..7602553 100644
--- a/lib/browserify-transform.js
+++ b/lib/browserify-transform.js
@@ -124,7 +124,7 @@ function processNode (node, args) {
var needsAc = false
var needsSa = false
- var hx = hyperx(function (tag, props, children) {
+ function createElement (tag, props, children) {
var res = []
var elname = VARNAME + tagCount
@@ -134,27 +134,31 @@ function processNode (node, args) {
return DELIM + [elname, 'var ' + elname + ' = document.createComment(' + JSON.stringify(props.comment) + ')', null].join(DELIM) + DELIM
}
- // Whether this element needs a namespace
- var namespace = props.namespace
- if (!namespace && SVG_TAGS.indexOf(tag) !== -1) {
- namespace = SVGNS
- }
+ if (tag === 'nanohtml-fragment') {
+ res.push('var ' + elname + ' = document.createDocumentFragment()')
+ } else {
+ // Whether this element needs a namespace
+ var namespace = props.namespace
+ if (!namespace && SVG_TAGS.indexOf(tag) !== -1) {
+ namespace = SVGNS
+ }
- // Whether this element is extended
- var isCustomElement = props.is
- delete props.is
+ // Whether this element is extended
+ var isCustomElement = props.is
+ delete props.is
- // Create the element
- if (namespace) {
- if (isCustomElement) {
- res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
+ // Create the element
+ if (namespace) {
+ if (isCustomElement) {
+ res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
+ } else {
+ res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')')
+ }
+ } else if (isCustomElement) {
+ res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
} else {
- res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')')
+ res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ')')
}
- } else if (isCustomElement) {
- res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
- } else {
- res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ')')
}
function addAttr (to, key, val) {
@@ -272,8 +276,16 @@ function processNode (node, args) {
}
// Return delim'd parts as a child
+ // return [elname, res]
return DELIM + [elname, res.join('\n'), null].join(DELIM) + DELIM
- }, { comments: true })
+ }
+
+ var hx = hyperx(createElement, {
+ comments: true,
+ createFragment: function (nodes) {
+ return createElement('nanohtml-fragment', {}, nodes)
+ }
+ })
// Run through hyperx
var res = hx.apply(null, args)
diff --git a/package.json b/package.json
index 2c1f703..856a7cc 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"camel-case": "^3.0.0",
"convert-source-map": "^1.5.1",
"estree-is-member-expression": "^1.0.0",
- "hyperx": "^2.3.2",
+ "hyperx": "^2.5.0",
"is-boolean-attribute": "0.0.1",
"nanoassert": "^1.1.0",
"nanobench": "^2.1.0",
diff --git a/tests/browser/index.js b/tests/browser/index.js
index 6cb2f2b..422f7dd 100644
--- a/tests/browser/index.js
+++ b/tests/browser/index.js
@@ -2,3 +2,4 @@ require('./api.js')
require('./elements.js')
require('./raw.js')
require('./events.js')
+require('./multiple.js')
diff --git a/tests/browser/multiple.js b/tests/browser/multiple.js
new file mode 100644
index 0000000..603662c
--- /dev/null
+++ b/tests/browser/multiple.js
@@ -0,0 +1,27 @@
+var test = require('tape')
+var html = require('../../')
+
+test('multiple elements', function (t) {
+ var multiple = html`HamburgHelsinkihahaBerlintest
`
+
+ var list = document.createElement('ul')
+ list.appendChild(multiple)
+ t.equal(list.children.length, 3, '3 children')
+ t.equal(list.childNodes.length, 4, '4 childNodes')
+ t.equal(list.children[0].tagName, 'LI', 'list tag name')
+ t.equal(list.children[0].textContent, 'Hamburg')
+ t.equal(list.children[1].textContent, 'Helsinki')
+ t.equal(list.children[2].textContent, 'Berlintest')
+ t.equal(list.querySelector('div').textContent, 'test', 'created sub-element')
+ t.equal(list.childNodes[2].nodeValue, 'haha')
+ t.end()
+})
+
+test('nested fragments', function (t) {
+ var fragments = html`1
ab${html`cd2
between3
`}4
`
+ t.equals(fragments.textContent, '1abcd2between34')
+ t.equals(fragments.children.length, 4)
+ t.equals(fragments.childNodes[4].textContent, 'between')
+ t.equals(fragments.childNodes.length, 7)
+ t.end()
+})
diff --git a/tests/server/index.js b/tests/server/index.js
index ee31e46..9f80eef 100644
--- a/tests/server/index.js
+++ b/tests/server/index.js
@@ -86,3 +86,23 @@ test('spread attributes', function (t) {
t.equal(result, expected)
t.end()
})
+
+test('multiple root elements', function (t) {
+ t.plan(1)
+
+ var expected = '1
2
35
'
+ var result = html`1
2
35
`.toString()
+
+ t.equal(expected, result)
+ t.end()
+})
+
+test('nested multiple root elements', function (t) {
+ t.plan(1)
+
+ var expected = '1
2
3
4
'
+ var result = html`1
${html`2
3
`}4
`.toString()
+
+ t.equal(expected, result)
+ t.end()
+})