Skip to content

Commit 2a0a0e0

Browse files
committed
Merge branch 'master' into comment-support
2 parents 3835bac + 3722d01 commit 2a0a0e0

File tree

9 files changed

+673
-313
lines changed

9 files changed

+673
-313
lines changed

README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,38 @@ import { html, render } from 'https://unpkg.com/htm/preact/standalone.mjs'
7070

7171
## Usage
7272

73+
If you're using Preact or React, we've included off-the-shelf bindings to make your life easier.
74+
They also have the added benefit of sharing a template cache across all modules.
75+
76+
```js
77+
import { render } from 'preact';
78+
import { html } from 'htm/preact';
79+
render(html`<a href="/">Hello!</a>`, document.body);
80+
```
81+
82+
Similarly, for React:
83+
84+
```js
85+
import ReactDOM from 'react-dom';
86+
import { html } from 'htm/react';
87+
ReactDOM.render(html`<a href="/">Hello!</a>`, document.body);
88+
```
89+
90+
### Advanced Usage
91+
7392
Since `htm` is a generic library, we need to tell it what to "compile" our templates to.
93+
You can bind `htm` to any function of the form `h(type, props, ...children)` _([hyperscript])_.
94+
This function can return anything - `htm` never looks at the return value.
7495

75-
The target should be a function of the form `h(type, props, ...children)` _([hyperscript])_, and can return anything.
96+
Here's an example `h()` function that returns tree nodes:
7697

7798
```js
78-
// this is our hyperscript function. for now, it just returns a description object.
7999
function h(type, props, ...children) {
80100
return { type, props, children };
81101
}
82102
```
83103

84-
To use that `h()` function, we need to create our own `html` tag function by binding `htm` to our `h()` function:
104+
To use our custom `h()` function, we need to create our own `html` tag function by binding `htm` to our `h()` function:
85105

86106
```js
87107
import htm from 'htm';

packages/babel-plugin-htm/index.mjs

Lines changed: 49 additions & 51 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
@@ -7,15 +7,15 @@ import htm from 'htm';
77
* @param {string} [options.tag=html] The tagged template "tag" function name to process.
88
* @param {boolean} [options.monomorphic=false] Output monomorphic inline objects instead of using String literals.
99
* @param {boolean} [options.useBuiltIns=false] Use the native Object.assign instead of trying to polyfill it.
10+
* @param {boolean} [options.useNativeSpread=false] Use the native { ...a, ...b } syntax for prop spreads.
1011
* @param {boolean} [options.variableArity=true] If `false`, always passes exactly 3 arguments to the pragma function.
1112
*/
1213
export default function htmBabelPlugin({ types: t }, options = {}) {
1314
const pragma = options.pragma===false ? false : dottedIdentifier(options.pragma || 'h');
1415
const useBuiltIns = options.useBuiltIns;
16+
const useNativeSpread = options.useNativeSpread;
1517
const inlineVNodes = options.monomorphic || pragma===false;
1618

17-
const symbol = Symbol();
18-
1919
function dottedIdentifier(keypath) {
2020
const path = keypath.split('.');
2121
let out;
@@ -31,12 +31,32 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
3131
const end = parts.pop() || '';
3232
return new RegExp(parts.join('/'), end);
3333
}
34-
34+
3535
function propertyName(key) {
36-
if (key.match(/(^\d|[^a-z0-9_$])/i)) return t.stringLiteral(key);
37-
return t.identifier(key);
36+
if (t.isValidIdentifier(key)) {
37+
return t.identifier(key);
38+
}
39+
return t.stringLiteral(key);
3840
}
39-
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+
4060
function stringValue(str) {
4161
if (options.monomorphic) {
4262
return t.objectExpression([
@@ -75,18 +95,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
7595
return t.callExpression(pragma, [tag, props].concat(children));
7696
}
7797

78-
function flatten(props, result = []) {
79-
const { [symbol]: head, ...tail } = props;
80-
if (head) head.forEach(obj => {
81-
flatten(obj, result);
82-
});
83-
if (Object.keys(tail).length > 0) {
84-
result.push(tail);
85-
}
86-
return result;
87-
}
88-
8998
function spreadNode(args, state) {
99+
if (args.length === 0) {
100+
return t.nullLiteral();
101+
}
90102
if (args.length > 0 && t.isNode(args[0])) {
91103
args.unshift({});
92104
}
@@ -99,28 +111,31 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
99111
if (args.length === 2 && !t.isNode(args[0]) && Object.keys(args[0]).length === 0) {
100112
return propsNode(args[1]);
101113
}
114+
115+
if (useNativeSpread) {
116+
const properties = [];
117+
args.forEach(arg => {
118+
if (t.isNode(arg)) {
119+
properties.push(t.spreadElement(arg));
120+
}
121+
else {
122+
properties.push(...objectProperties(arg));
123+
}
124+
});
125+
return t.objectExpression(properties);
126+
}
127+
102128
const helper = useBuiltIns ? dottedIdentifier('Object.assign') : state.addHelper('extends');
103129
return t.callExpression(helper, args.map(propsNode));
104130
}
105131

106132
function propsNode(props) {
107-
return t.isNode(props) ? props : 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-
return t.objectProperty(propertyName(key), value);
117-
})
118-
);
133+
return t.isNode(props) ? props : t.objectExpression(objectProperties(props));
119134
}
120135

121136
function transform(node, state) {
122137
if (node === undefined) return t.identifier('undefined');
123-
if (node == null) return t.nullLiteral();
138+
if (node === null) return t.nullLiteral();
124139

125140
const { tag, props, children } = node;
126141
function childMapper(child) {
@@ -130,27 +145,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
130145
return t.isNode(child) ? child : transform(child, state);
131146
}
132147
const newTag = typeof tag === 'string' ? t.stringLiteral(tag) : tag;
133-
const newProps = props ? spreadNode(flatten(props), state) : t.nullLiteral();
148+
const newProps = spreadNode(props, state);
134149
const newChildren = t.arrayExpression(children.map(childMapper));
135150
return createVNode(newTag, newProps, newChildren);
136151
}
137-
138-
function h(tag, props, ...children) {
139-
return { tag, props, children };
140-
}
141-
142-
const html = htm.bind(h);
143-
144-
function treeify(statics, expr) {
145-
const assign = Object.assign;
146-
try {
147-
Object.assign = function(...objs) { return { [symbol]: objs }; };
148-
return html(statics, ...expr);
149-
}
150-
finally {
151-
Object.assign = assign;
152-
}
153-
}
154152

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

168-
const tree = treeify(statics, expr);
166+
const tree = treeify(build(statics), expr);
169167
const node = !Array.isArray(tree)
170168
? transform(tree, state)
171169
: t.arrayExpression(tree.map(root => transform(root, state)));
@@ -174,4 +172,4 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
174172
}
175173
}
176174
};
177-
}
175+
}

0 commit comments

Comments
 (0)