|
import { build, treeify } from '../../src/build.mjs'; |
|
|
|
const SEPARATOR = '|'; |
|
|
|
const APPEND = 0; |
|
const SET = 1; |
|
const PUSH = 2; |
|
const ASSIGN = 3; |
|
const EVAL = 4; |
|
const ARG = 5; |
|
const TRUE = 6; |
|
const NULL = 7; |
|
const OBJECT = 8; |
|
const EXPR = 9; |
|
|
|
/** |
|
* @param {Babel} babel |
|
* @param {object} options |
|
* @param {string} [options.tag=html] The tagged template "tag" function name to process. |
|
*/ |
|
export default function htmBabelPlugin({ types: t }, options = {}) { |
|
function patternStringToRegExp(str) { |
|
const parts = str.split('/').slice(1); |
|
const end = parts.pop() || ''; |
|
return new RegExp(parts.join('/'), end); |
|
} |
|
|
|
function transform(node, ops, strings, values) { |
|
function put(value) { |
|
if (t.isNode(value)) { |
|
values.push(value); |
|
return ARG; |
|
} |
|
else if (value === true) { |
|
return TRUE; |
|
} |
|
values.push(t.stringLiteral(value)); |
|
return ARG; |
|
} |
|
|
|
function assign(value) { |
|
values.push(value); |
|
return ASSIGN; |
|
} |
|
|
|
if (t.isNode(node) || typeof node === 'string') { |
|
ops.push(put(node)); |
|
return; |
|
} |
|
|
|
const { tag, props, children } = node; |
|
put(tag); |
|
|
|
if (props.length === 0) { |
|
ops.push(NULL); |
|
} |
|
else if (props.length === 1 && t.isNode(props[0])) { |
|
values.push(props[0]); |
|
ops.push(EXPR); |
|
} |
|
else { |
|
ops.push(OBJECT); |
|
props.forEach((obj, index) => { |
|
if (t.isNode(obj)) { |
|
ops.push(assign(obj)); |
|
} |
|
else { |
|
Object.entries(obj).forEach(([key, values]) => { |
|
if (values.length === 0) { |
|
return; |
|
} |
|
values.forEach((value, index) => { |
|
ops.push(put(value)); |
|
if (index > 0) { |
|
ops.push(APPEND); |
|
} |
|
}); |
|
ops.push(put(key), SET); |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
children.forEach(child => { |
|
transform(child, ops, strings, values); |
|
ops.push(PUSH); |
|
}); |
|
|
|
ops.push(EVAL); |
|
} |
|
|
|
// The tagged template tag function name we're looking for. |
|
// This is static because it's generally assigned via htm.bind(h), |
|
// which could be imported from elsewhere, making tracking impossible. |
|
const htmlName = options.tag || 'html'; |
|
return { |
|
name: 'htm', |
|
visitor: { |
|
TaggedTemplateExpression: { |
|
exit(path) { |
|
const tag = path.node.tag.name; |
|
if (htmlName[0]==='/' ? patternStringToRegExp(htmlName).test(tag) : tag === htmlName) { |
|
const quasis = path.node.quasi.quasis.map(e => e.value.raw); |
|
const expr = path.node.quasi.expressions; |
|
|
|
let tree = treeify(build(quasis), expr); |
|
let ops = []; |
|
let strings = []; |
|
let values = []; |
|
if (Array.isArray(tree)) { |
|
ops.push(TRUE); |
|
tree.forEach(root => { |
|
transform(root, ops, strings, values); |
|
ops.push(PUSH); |
|
}); |
|
} |
|
else if (tree) { |
|
transform(tree, ops, strings, values); |
|
} |
|
|
|
path.replaceWith( |
|
t.callExpression( |
|
path.node.tag, |
|
[ |
|
t.stringLiteral([ops.join(''), ...strings].join(SEPARATOR)), |
|
...values |
|
] |
|
) |
|
); |
|
path.skip(); |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
} |