Skip to content

Commit 04cba18

Browse files
committed
Attempt to fix babel plugin
1 parent d517751 commit 04cba18

File tree

3 files changed

+59
-42
lines changed

3 files changed

+59
-42
lines changed

packages/babel-plugin-htm/index.mjs

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
import htm from 'htm';
22

3-
// htm() uses the HTML parser, which serializes attribute values.
4-
// this is a problem, because composite values here can be made up
5-
// of strings and AST nodes, which serialize to [object Object].
6-
// Since the handoff from AST node handling to htm() is synchronous,
7-
// this global lookup will always reflect the corresponding
8-
// AST-derived values for the current htm() invocation.
9-
let currentExpressions;
10-
113
/**
124
* @param {Babel} babel
135
* @param {object} options
@@ -64,7 +56,7 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
6456
options.monomorphic && t.objectProperty(propertyName('text'), t.nullLiteral())
6557
].filter(Boolean));
6658
}
67-
59+
6860
return t.callExpression(pragma, [tag, props, children]);
6961
}
7062

@@ -85,48 +77,49 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
8577
child = child.trim();
8678
}
8779
if (typeof child==='string') {
88-
const matches = child.match(/\$\$\$_h_\[(\d+)\]/);
89-
if (matches) return currentExpressions[matches[1]];
9080
return stringValue(child);
9181
}
9282
return child;
9383
}
9484

9585
function h(tag, props) {
9686
if (typeof tag==='string') {
97-
const matches = tag.match(/\$\$\$_h_\[(\d+)\]/);
98-
if (matches) tag = currentExpressions[matches[1]];
99-
else tag = t.stringLiteral(tag);
87+
tag = t.stringLiteral(tag);
10088
}
10189

102-
//const propsNode = props==null || Object.keys(props).length===0 ? t.nullLiteral() : t.objectExpression(
103-
const propsNode = t.objectExpression(
104-
Object.keys(props).map(key => {
105-
let value = props[key];
106-
if (typeof value==='string') {
107-
const tokenizer = /\$\$\$_h_\[(\d+)\]/g;
108-
let token, lhs, root, index=0, lastIndex=0;
109-
const append = expr => {
110-
if (lhs) expr = t.binaryExpression('+', lhs, expr);
111-
root = lhs = expr;
112-
};
113-
while ((token = tokenizer.exec(value))) {
114-
append(t.stringLiteral(value.substring(index, token.index)));
115-
append(currentExpressions[token[1]]);
116-
index = token.index;
117-
lastIndex = tokenizer.lastIndex;
118-
}
119-
if (lastIndex < value.length) {
120-
append(t.stringLiteral(value.substring(lastIndex)));
90+
let propsNode;
91+
92+
if (t.isObjectExpression(props)) {
93+
propsNode = props;
94+
for (let i in props) {
95+
if (props.hasOwnProperty(i) && props[i] && props[i].type) {
96+
for (let j=0; j<props.properties.length; j++) {
97+
if (props.properties[j].start > props[i].start) {
98+
props.properties.splice(j, 0, t.objectProperty(propertyName(i), props[i]));
99+
break;
100+
}
121101
}
122-
value = root;
123-
}
124-
else if (typeof value==='boolean') {
125-
value = t.booleanLiteral(value);
102+
delete props[i];
126103
}
127-
return t.objectProperty(propertyName(key), value);
128-
})
129-
);
104+
}
105+
}
106+
else {
107+
propsNode = t.objectExpression(
108+
Object.keys(props).map(key => {
109+
let value = props[key];
110+
if (typeof value==='string') {
111+
value = t.stringLiteral(value);
112+
}
113+
else if (typeof value==='boolean') {
114+
value = t.booleanLiteral(value);
115+
}
116+
else if (typeof value==='number') {
117+
value = t.stringLiteral(value + '');
118+
}
119+
return t.objectProperty(propertyName(key), value);
120+
})
121+
);
122+
}
130123

131124
// recursive iteration of possibly nested arrays of children.
132125
let children = [];
@@ -164,8 +157,7 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
164157
if (htmlName[0]==='/' ? patternStringToRegExp(htmlName).test(tag) : tag === htmlName) {
165158
const statics = path.node.quasi.quasis.map(e => e.value.raw);
166159
const expr = path.node.quasi.expressions;
167-
currentExpressions = expr;
168-
path.replaceWith(html(statics, ...expr.map((p, i) => `$$$_h_[${i}]`)));
160+
path.replaceWith(html(statics, ...expr));
169161
}
170162
}
171163
}

test/babel.test.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ describe('htm/babel', () => {
3030
).toBe(`var name="world";h("div",{id:"hello"},["hello, ",name]);`);
3131
});
3232

33+
test('basic nested transformation', () => {
34+
expect(
35+
transform('html`<a b=${2} ...${{ c: 3 }}>d: ${4}</a>`;', {
36+
babelrc: false,
37+
compact: true,
38+
plugins: [
39+
htmBabelPlugin
40+
]
41+
}).code
42+
).toBe(`h("a",{b:2,c:3},["d: ",4]);`);
43+
});
44+
3345
test('inline vnode transformation: (pragma:false)', () => {
3446
expect(
3547
transform('var name="world",vnode=html`<div id=hello>hello, ${name}</div>`;', {
@@ -57,4 +69,16 @@ describe('htm/babel', () => {
5769
}).code
5870
).toBe(`var name="world",vnode={type:1,tag:"div",props:{id:"hello"},children:[{type:3,tag:null,props:null,children:null,text:"hello, "},name],text:null};`);
5971
});
72+
73+
test('preserves placeholder-looking strings in attributes and text', () => {
74+
expect(
75+
transform('html`<div $$$_h_[1]=$$$_h_[2]>$$$_h_[3]</div>`;', {
76+
babelrc: false,
77+
compact: true,
78+
plugins: [
79+
htmBabelPlugin
80+
]
81+
}).code
82+
).toBe(`h("div",{"$$$_h_[1]":"$$$_h_[2]"},["$$$_h_[3]"]);`);
83+
});
6084
});

test/index.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ describe('htm', () => {
8181
expect(html`<a ...${{ foo: 'bar' }} b />`).toEqual({ tag: 'a', props: { b: true, foo: 'bar' }, children: [] });
8282
expect(html`<a b="1" ...${{ foo: 'bar' }} />`).toEqual({ tag: 'a', props: { b: '1', foo: 'bar' }, children: [] });
8383
expect(html`<a x="1"><b y="2" ...${{ c: 'bar' }}/></a>`).toEqual(h('a', { x: '1' }, h('b', { y: '2', c: 'bar' }) ));
84+
expect(html`<a b=${2} ...${{ c: 3 }}>d: ${4}</a>`).toEqual(h('a',{ b: 2, c: 3 }, 'd: ', 4));
8485
expect(html`<a ...${{ c: 'bar' }}><b ...${{ d: 'baz' }}/></a>`).toEqual(h('a', { c: 'bar' }, h('b', { d: 'baz' }) ));
8586
});
8687

0 commit comments

Comments
 (0)