Skip to content

Commit 6752939

Browse files
committed
Implement multiple root elements
1 parent dbdc12e commit 6752939

File tree

4 files changed

+71
-66
lines changed

4 files changed

+71
-66
lines changed

packages/babel-plugin-htm/index.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
166166
const expr = path.node.quasi.expressions;
167167

168168
const tree = treeify(statics, expr);
169-
path.replaceWith(transform(tree, state));
169+
const node = !Array.isArray(tree)
170+
? transform(tree, state)
171+
: t.arrayExpression(tree.map(root => transform(root, state)));
172+
path.replaceWith(node);
170173
}
171174
}
172175
}

src/build.mjs

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,6 @@ const PROPS_ASSIGN = 2;
44
const CHILD_RECURSE = 3;
55
const CHILD_APPEND = 4;
66

7-
const TAG_START = 60;
8-
const TAG_END = 62;
9-
const EQUALS = 61;
10-
const QUOTE_DOUBLE = 34;
11-
const QUOTE_SINGLE = 39;
12-
const TAB = 9;
13-
const NEWLINE = 10;
14-
const RETURN = 13;
15-
const SPACE = 32;
16-
const SLASH = 47;
17-
187
const MODE_SLASH = 0;
198
const MODE_TEXT = 1;
209
const MODE_WHITESPACE = 3;
@@ -41,7 +30,7 @@ export const evaluate = (h, current, fields) => {
4130
else if (code === CHILD_RECURSE) {
4231
args.push(evaluate(h, value, fields));
4332
}
44-
else {
33+
else { // code === CHILD_APPEND
4534
args.push(value);
4635
}
4736
}
@@ -54,10 +43,10 @@ export const evaluate = (h, current, fields) => {
5443
export const build = (statics) => {
5544
let mode = MODE_TEXT;
5645
let buffer = '';
57-
let quote = -1;
46+
let quote = '';
5847
let fallbackPropValue = true;
59-
let charCode, propName;
6048
let current = [];
49+
let char, propName;
6150

6251
const commit = field => {
6352
if (mode === MODE_TEXT) {
@@ -72,7 +61,7 @@ export const build = (statics) => {
7261
else if (mode === MODE_WHITESPACE && buffer === '...') {
7362
current.push(field, 0, PROPS_ASSIGN);
7463
}
75-
else if (mode === MODE_ATTRIBUTE || mode === MODE_WHITESPACE) {
64+
else if (mode) { // mode === MODE_ATTRIBUTE || mode === MODE_WHITESPACE
7665
if (mode === MODE_WHITESPACE) {
7766
propName = buffer;
7867
buffer = '';
@@ -88,73 +77,72 @@ export const build = (statics) => {
8877

8978
for (let i=0; i<statics.length; i++) {
9079
if (i) {
91-
if (mode === MODE_TEXT) commit();
80+
if (mode === MODE_TEXT) {
81+
commit();
82+
}
9283
commit(i);
9384
}
9485

9586
for (let j=0; j<statics[i].length; j++) {
96-
charCode = statics[i].charCodeAt(j);
87+
char = statics[i].charAt(j);
9788

9889
if (mode === MODE_TEXT) {
99-
if (charCode === TAG_START) {
90+
if (char === '<') {
10091
// commit buffer
10192
commit();
10293
current = [current];
10394
mode = MODE_TAGNAME;
10495
continue;
10596
}
10697
}
107-
else {
108-
if (quote === charCode) {
109-
quote = -1;
110-
continue;
111-
}
112-
113-
if (quote < 0) {
114-
switch (charCode) {
115-
case QUOTE_SINGLE:
116-
case QUOTE_DOUBLE:
117-
quote = charCode;
118-
continue;
119-
case TAG_END:
120-
commit();
98+
else if (quote === char) {
99+
quote = '';
100+
continue;
101+
}
102+
else if (!quote) {
103+
switch (char) {
104+
case '"':
105+
case "'":
106+
quote = char;
107+
continue;
108+
case '>':
109+
commit();
121110

122-
if (!mode) {
123-
if (current.length === 1) {
124-
current = current[0];
125-
}
126-
current[0].push(0, current, CHILD_RECURSE);
111+
if (!mode) {
112+
if (current.length === 1) {
127113
current = current[0];
128114
}
129-
mode = MODE_TEXT;
130-
continue;
131-
case EQUALS:
132-
if (mode) {
133-
mode = MODE_ATTRIBUTE;
134-
propName = buffer;
135-
buffer = fallbackPropValue = '';
136-
}
137-
continue;
138-
case SLASH:
115+
current[0].push(0, current, CHILD_RECURSE);
116+
current = current[0];
117+
}
118+
mode = MODE_TEXT;
119+
continue;
120+
case '=':
121+
if (mode) {
122+
mode = MODE_ATTRIBUTE;
123+
propName = buffer;
124+
buffer = fallbackPropValue = '';
125+
}
126+
continue;
127+
case '/':
128+
commit();
129+
mode = MODE_SLASH;
130+
continue;
131+
case '\t':
132+
case '\r':
133+
case '\n':
134+
case ' ':
135+
if (mode) {
136+
// <a disabled>
139137
commit();
140-
mode = MODE_SLASH;
141-
continue;
142-
case TAB:
143-
case NEWLINE:
144-
case RETURN:
145-
case SPACE:
146-
if (mode) {
147-
// <a disabled>
148-
commit();
149-
mode = MODE_WHITESPACE;
150-
}
151-
continue;
152-
}
138+
mode = MODE_WHITESPACE;
139+
}
140+
continue;
153141
}
154142
}
155-
buffer += statics[i].charAt(j);
143+
buffer += char;
156144
}
157145
}
158146
commit();
159-
return current[1];
147+
return current;
160148
};

src/index.mjs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ const CACHE = {};
1717

1818
export default function html(statics) {
1919
let key = '.';
20-
for (let i=0; i<statics.length; i++) key += statics[i].length + ',' + statics[i];
20+
for (let i=0; i<statics.length; i++) {
21+
key += statics[i].length + ',' + statics[i];
22+
}
2123
const tpl = CACHE[key] || (CACHE[key] = build(statics));
2224

23-
// eslint-disable-next-line prefer-rest-params
24-
return tpl && evaluate(this, tpl, arguments);
25+
const res = [];
26+
for (let i=1; i<tpl.length; i+=3) {
27+
// eslint-disable-next-line prefer-rest-params
28+
res.push(evaluate(this, tpl[i], arguments));
29+
}
30+
return res.length > 1 ? res : res[0];
2531
}

test/index.test.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ describe('htm', () => {
2626
expect(html`<div/>`).toEqual({ tag: 'div', props: null, children: [] });
2727
expect(html`<span />`).toEqual({ tag: 'span', props: null, children: [] });
2828
});
29+
30+
test('multiple root elements', () => {
31+
expect(html`<a /><b></b><c><//>`).toEqual([
32+
{ tag: 'a', props: null, children: [] },
33+
{ tag: 'b', props: null, children: [] },
34+
{ tag: 'c', props: null, children: [] }
35+
]);
36+
});
2937

3038
test('single dynamic tag name', () => {
3139
expect(html`<${'foo'} />`).toEqual({ tag: 'foo', props: null, children: [] });

0 commit comments

Comments
 (0)