Skip to content

Commit ff8cf6f

Browse files
authored
Merge pull request #93 from jviide/concatenate
Concatenate mixed static + dynamic attribute values
2 parents 0ed6079 + 9b5bd53 commit ff8cf6f

5 files changed

Lines changed: 364 additions & 232 deletions

File tree

packages/babel-plugin-htm/index.mjs

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import htm from 'htm';
1+
import { build, treeify } from '../../src/build.mjs';
22

33
/**
44
* @param {Babel} babel
@@ -16,8 +16,6 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
1616
const useNativeSpread = options.useNativeSpread;
1717
const inlineVNodes = options.monomorphic || pragma===false;
1818

19-
const symbol = Symbol();
20-
2119
function dottedIdentifier(keypath) {
2220
const path = keypath.split('.');
2321
let out;
@@ -33,16 +31,32 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
3331
const end = parts.pop() || '';
3432
return new RegExp(parts.join('/'), end);
3533
}
36-
37-
function propertyValue(valueOrNode) {
38-
return t.isNode(valueOrNode) ? valueOrNode : t.valueToNode(valueOrNode);
39-
}
40-
34+
4135
function propertyName(key) {
42-
if (key.match(/(^\d|[^a-z0-9_$])/i)) return t.stringLiteral(key);
43-
return t.identifier(key);
36+
if (t.isValidIdentifier(key)) {
37+
return t.identifier(key);
38+
}
39+
return t.stringLiteral(key);
4440
}
45-
41+
42+
function objectProperties(obj) {
43+
return Object.keys(obj).map(key => {
44+
const values = obj[key].map(valueOrNode =>
45+
t.isNode(valueOrNode) ? valueOrNode : t.valueToNode(valueOrNode)
46+
);
47+
48+
let node = values[0];
49+
if (values.length > 1 && !t.isStringLiteral(node) && !t.isStringLiteral(values[1])) {
50+
node = t.binaryExpression('+', t.stringLiteral(''), node);
51+
}
52+
values.slice(1).forEach(value => {
53+
node = t.binaryExpression('+', node, value);
54+
});
55+
56+
return t.objectProperty(propertyName(key), node);
57+
});
58+
}
59+
4660
function stringValue(str) {
4761
if (options.monomorphic) {
4862
return t.objectExpression([
@@ -81,18 +95,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
8195
return t.callExpression(pragma, [tag, props].concat(children));
8296
}
8397

84-
function flatten(props, result = []) {
85-
const { [symbol]: head, ...tail } = props;
86-
if (head) head.forEach(obj => {
87-
flatten(obj, result);
88-
});
89-
if (Object.keys(tail).length > 0) {
90-
result.push(tail);
91-
}
92-
return result;
93-
}
94-
9598
function spreadNode(args, state) {
99+
if (args.length === 0) {
100+
return t.nullLiteral();
101+
}
96102
if (args.length > 0 && t.isNode(args[0])) {
97103
args.unshift({});
98104
}
@@ -113,10 +119,7 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
113119
properties.push(t.spreadElement(arg));
114120
}
115121
else {
116-
Object.keys(arg).forEach(key => {
117-
const value = arg[key];
118-
properties.push(t.objectProperty(propertyName(key), propertyValue(value)));
119-
});
122+
properties.push(...objectProperties(arg));
120123
}
121124
});
122125
return t.objectExpression(properties);
@@ -127,17 +130,12 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
127130
}
128131

129132
function propsNode(props) {
130-
return t.isNode(props) ? props : t.objectExpression(
131-
Object.keys(props).map(key => {
132-
const value = props[key];
133-
return t.objectProperty(propertyName(key), propertyValue(value));
134-
})
135-
);
133+
return t.isNode(props) ? props : t.objectExpression(objectProperties(props));
136134
}
137135

138136
function transform(node, state) {
139137
if (node === undefined) return t.identifier('undefined');
140-
if (node == null) return t.nullLiteral();
138+
if (node === null) return t.nullLiteral();
141139

142140
const { tag, props, children } = node;
143141
function childMapper(child) {
@@ -147,27 +145,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
147145
return t.isNode(child) ? child : transform(child, state);
148146
}
149147
const newTag = typeof tag === 'string' ? t.stringLiteral(tag) : tag;
150-
const newProps = props ? spreadNode(flatten(props), state) : t.nullLiteral();
148+
const newProps = spreadNode(props, state);
151149
const newChildren = t.arrayExpression(children.map(childMapper));
152150
return createVNode(newTag, newProps, newChildren);
153151
}
154-
155-
function h(tag, props, ...children) {
156-
return { tag, props, children };
157-
}
158-
159-
const html = htm.bind(h);
160-
161-
function treeify(statics, expr) {
162-
const assign = Object.assign;
163-
try {
164-
Object.assign = function(...objs) { return { [symbol]: objs }; };
165-
return html(statics, ...expr);
166-
}
167-
finally {
168-
Object.assign = assign;
169-
}
170-
}
171152

172153
// The tagged template tag function name we're looking for.
173154
// This is static because it's generally assigned via htm.bind(h),
@@ -182,7 +163,7 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
182163
const statics = path.node.quasi.quasis.map(e => e.value.raw);
183164
const expr = path.node.quasi.expressions;
184165

185-
const tree = treeify(statics, expr);
166+
const tree = treeify(build(statics), expr);
186167
const node = !Array.isArray(tree)
187168
? transform(tree, state)
188169
: t.arrayExpression(tree.map(root => transform(root, state)));
@@ -191,4 +172,4 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
191172
}
192173
}
193174
};
194-
}
175+
}

0 commit comments

Comments
 (0)