diff --git a/packages/babel-plugin-transform-jsx-to-htm/index.mjs b/packages/babel-plugin-transform-jsx-to-htm/index.mjs index 205ca4f..6e5ccc2 100644 --- a/packages/babel-plugin-transform-jsx-to-htm/index.mjs +++ b/packages/babel-plugin-transform-jsx-to-htm/index.mjs @@ -87,49 +87,23 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { })); buffer = ''; } - - function processNode(node, path, isRoot) { - const open = node.openingElement; - const { name } = open.name; - - if (name.match(/^[A-Z]/)) { - raw('<'); - expr(t.identifier(name)); - } - else { - raw('<'); - raw(name); - } - - if (open.attributes) { - for (let i = 0; i < open.attributes.length; i++) { - const attr = open.attributes[i]; - raw(' '); - if (t.isJSXSpreadAttribute(attr)) { - raw('...'); - expr(attr.argument); - continue; - } - const { name, value } = attr; - raw(name.name); - if (value) { - raw('='); - if (value.expression) { - expr(value.expression); - } - else if (t.isStringLiteral(value)) { - escapePropValue(value); - } - else { - expr(value); - } - } - } + + function getName(node) { + switch (node.type) { + case 'JSXMemberExpression': + return `${node.object.name}.${node.property.name}` + + default: + return node.name; } + } + function processChildren(node, name, isFragment) { const children = t.react.buildChildren(node); if (children && children.length !== 0) { - raw('>'); + if (!isFragment) { + raw('>'); + } for (let i = 0; i < children.length; i++) { let child = children[i]; if (t.isStringLiteral(child)) { @@ -144,27 +118,100 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { } } + if (!isFragment) { + if (name.match(/^[A-Z]/)) { + raw(''); + } + else { + raw(''); + } + } + } + else if (!isFragment) { + raw('/>'); + } + } + + function processNode(node, path, isRoot) { + const open = node.openingElement; + const name = getName(open.name); + const isFragment = name === 'React.Fragment'; + + if (!isFragment) { if (name.match(/^[A-Z]/)) { - raw(''); } else { - raw(''); + } + + if (open.attributes) { + for (let i = 0; i < open.attributes.length; i++) { + const attr = open.attributes[i]; + raw(' '); + if (t.isJSXSpreadAttribute(attr)) { + raw('...'); + expr(attr.argument); + continue; + } + const { name, value } = attr; + raw(name.name); + if (value) { + raw('='); + if (value.expression) { + expr(value.expression); + } + else if (t.isStringLiteral(value)) { + escapePropValue(value); + } + else { + expr(value); + } + } + } } } - else { - raw('/>'); - } + + processChildren(node, name, isFragment); if (isRoot) { + commit(true); + const template = t.templateLiteral(quasis, expressions); + const replacement = t.taggedTemplateExpression(tag, template); + path.replaceWith(replacement); + } + } + + function jsxVisitorHandler(path, state, isFragment) { + let quasisBefore = quasis.slice(); + let expressionsBefore = expressions.slice(); + let bufferBefore = buffer; + + buffer = ''; + quasis.length = 0; + expressions.length = 0; + + if (isFragment) { + processChildren(path.node, '', true); commit(); const template = t.templateLiteral(quasis, expressions); const replacement = t.taggedTemplateExpression(tag, template); path.replaceWith(replacement); + } else { + processNode(path.node, path, true); } + + quasis = quasisBefore; + expressions = expressionsBefore; + buffer = bufferBefore; + + state.set('jsxElement', true); } return { @@ -180,21 +227,11 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { }, JSXElement(path, state) { - let quasisBefore = quasis.slice(); - let expressionsBefore = expressions.slice(); - let bufferBefore = buffer; - - buffer = ''; - quasis.length = 0; - expressions.length = 0; - - processNode(path.node, path, true); - - quasis = quasisBefore; - expressions = expressionsBefore; - buffer = bufferBefore; - - state.set('jsxElement', true); + jsxVisitorHandler(path, state, false); + }, + + JSXFragment(path, state) { + jsxVisitorHandler(path, state, true); } } }; diff --git a/test/babel-transform-jsx.test.mjs b/test/babel-transform-jsx.test.mjs index 7df2535..6c44d32 100644 --- a/test/babel-transform-jsx.test.mjs +++ b/test/babel-transform-jsx.test.mjs @@ -101,6 +101,26 @@ describe('babel-plugin-transform-jsx-to-htm', () => { }); }); + describe('fragments', () => { + test('React.Fragment', () => { + expect( + compile(`
Foo
Bar
`) + ).toBe('html`
Foo
Bar
`;'); + }); + + test('short syntax', () => { + expect( + compile(`<>
Foo
Bar
`) + ).toBe('html`
Foo
Bar
`;'); + }); + + test('root expressions', () => { + expect( + compile(`{Foo}{Bar}`) + ).toBe('html`${Foo}${Bar}`;'); + }); + }); + describe('props', () => { test('static values', () => { expect(