Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concatenate mixed static + dynamic attribute values #93

Merged
merged 8 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 34 additions & 53 deletions packages/babel-plugin-htm/index.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import htm from 'htm';
import { build, treeify } from '../../src/build.mjs';

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

const symbol = Symbol();

function dottedIdentifier(keypath) {
const path = keypath.split('.');
let out;
Expand All @@ -33,16 +31,32 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
const end = parts.pop() || '';
return new RegExp(parts.join('/'), end);
}

function propertyValue(valueOrNode) {
return t.isNode(valueOrNode) ? valueOrNode : t.valueToNode(valueOrNode);
}


function propertyName(key) {
if (key.match(/(^\d|[^a-z0-9_$])/i)) return t.stringLiteral(key);
return t.identifier(key);
if (t.isValidIdentifier(key)) {
return t.identifier(key);
}
return t.stringLiteral(key);
}


function objectProperties(obj) {
return Object.keys(obj).map(key => {
const values = obj[key].map(valueOrNode =>
t.isNode(valueOrNode) ? valueOrNode : t.valueToNode(valueOrNode)
);

let node = values[0];
if (values.length > 1 && !t.isStringLiteral(node) && !t.isStringLiteral(values[1])) {
node = t.binaryExpression('+', t.stringLiteral(''), node);
}
values.slice(1).forEach(value => {
node = t.binaryExpression('+', node, value);
});

return t.objectProperty(propertyName(key), node);
});
}

function stringValue(str) {
if (options.monomorphic) {
return t.objectExpression([
Expand Down Expand Up @@ -81,18 +95,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
return t.callExpression(pragma, [tag, props].concat(children));
}

function flatten(props, result = []) {
const { [symbol]: head, ...tail } = props;
if (head) head.forEach(obj => {
flatten(obj, result);
});
if (Object.keys(tail).length > 0) {
result.push(tail);
}
return result;
}

function spreadNode(args, state) {
if (args.length === 0) {
return t.nullLiteral();
}
if (args.length > 0 && t.isNode(args[0])) {
args.unshift({});
}
Expand All @@ -113,10 +119,7 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
properties.push(t.spreadElement(arg));
}
else {
Object.keys(arg).forEach(key => {
const value = arg[key];
properties.push(t.objectProperty(propertyName(key), propertyValue(value)));
});
properties.push(...objectProperties(arg));
}
});
return t.objectExpression(properties);
Expand All @@ -127,17 +130,12 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
}

function propsNode(props) {
return t.isNode(props) ? props : t.objectExpression(
Object.keys(props).map(key => {
const value = props[key];
return t.objectProperty(propertyName(key), propertyValue(value));
})
);
return t.isNode(props) ? props : t.objectExpression(objectProperties(props));
}

function transform(node, state) {
if (node === undefined) return t.identifier('undefined');
if (node == null) return t.nullLiteral();
if (node === null) return t.nullLiteral();

const { tag, props, children } = node;
function childMapper(child) {
Expand All @@ -147,27 +145,10 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
return t.isNode(child) ? child : transform(child, state);
}
const newTag = typeof tag === 'string' ? t.stringLiteral(tag) : tag;
const newProps = props ? spreadNode(flatten(props), state) : t.nullLiteral();
const newProps = spreadNode(props, state);
const newChildren = t.arrayExpression(children.map(childMapper));
return createVNode(newTag, newProps, newChildren);
}

function h(tag, props, ...children) {
return { tag, props, children };
}

const html = htm.bind(h);

function treeify(statics, expr) {
const assign = Object.assign;
try {
Object.assign = function(...objs) { return { [symbol]: objs }; };
return html(statics, ...expr);
}
finally {
Object.assign = assign;
}
}

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

const tree = treeify(statics, expr);
const tree = treeify(build(statics), expr);
const node = !Array.isArray(tree)
? transform(tree, state)
: t.arrayExpression(tree.map(root => transform(root, state)));
Expand All @@ -191,4 +172,4 @@ export default function htmBabelPlugin({ types: t }, options = {}) {
}
}
};
}
}
Loading