From 9c590aaad8e19871baf357e92fb135e68b572a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Fri, 19 Apr 2019 21:29:50 -0300 Subject: [PATCH 1/4] babel-transform-jsx: handle JSX fragments --- .../index.mjs | 156 +++++++++++------- test/babel-transform-jsx.test.mjs | 14 ++ 2 files changed, 114 insertions(+), 56 deletions(-) diff --git a/packages/babel-plugin-transform-jsx-to-htm/index.mjs b/packages/babel-plugin-transform-jsx-to-htm/index.mjs index 205ca4f..d7a0916 100644 --- a/packages/babel-plugin-transform-jsx-to-htm/index.mjs +++ b/packages/babel-plugin-transform-jsx-to-htm/index.mjs @@ -86,50 +86,24 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { cooked: buffer })); 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); - } - } - } - } - - const children = t.react.buildChildren(node); + } + + 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,20 +118,67 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { } } - if (name.match(/^[A-Z]/)) { - raw(''); - } - else { - raw(''); - } + if (!isFragment) { + if (name.match(/^[A-Z]/)) { + raw(''); + } + else { + raw(''); + } + } } - else { + 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('<'); + 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); + } + } + } + } + } + + processChildren(node, name, isFragment); if (isRoot) { commit(); @@ -195,7 +216,30 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { buffer = bufferBefore; state.set('jsxElement', true); - } + }, + + JSXFragment(path, state) { + let quasisBefore = quasis.slice(); + let expressionsBefore = expressions.slice(); + let bufferBefore = buffer; + + buffer = ''; + quasis.length = 0; + expressions.length = 0; + + processChildren(path.node, '', true); + + commit(); + const template = t.templateLiteral(quasis, expressions); + const replacement = t.taggedTemplateExpression(tag, template); + path.replaceWith(replacement); + + quasis = quasisBefore; + expressions = expressionsBefore; + buffer = bufferBefore; + + state.set('jsxElement', true); + } } }; } diff --git a/test/babel-transform-jsx.test.mjs b/test/babel-transform-jsx.test.mjs index 7df2535..2db7fcc 100644 --- a/test/babel-transform-jsx.test.mjs +++ b/test/babel-transform-jsx.test.mjs @@ -101,6 +101,20 @@ 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
`;'); + }); + }); + describe('props', () => { test('static values', () => { expect( From 2facdb5d2f088b94e055973c151433ce6543c1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Sat, 27 Apr 2019 23:07:55 -0300 Subject: [PATCH 2/4] Fix fragments when last root child is an expression --- packages/babel-plugin-transform-jsx-to-htm/index.mjs | 2 +- test/babel-transform-jsx.test.mjs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-transform-jsx-to-htm/index.mjs b/packages/babel-plugin-transform-jsx-to-htm/index.mjs index d7a0916..6a84b42 100644 --- a/packages/babel-plugin-transform-jsx-to-htm/index.mjs +++ b/packages/babel-plugin-transform-jsx-to-htm/index.mjs @@ -181,7 +181,7 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { processChildren(node, name, isFragment); if (isRoot) { - commit(); + commit(true); const template = t.templateLiteral(quasis, expressions); const replacement = t.taggedTemplateExpression(tag, template); path.replaceWith(replacement); diff --git a/test/babel-transform-jsx.test.mjs b/test/babel-transform-jsx.test.mjs index 2db7fcc..6c44d32 100644 --- a/test/babel-transform-jsx.test.mjs +++ b/test/babel-transform-jsx.test.mjs @@ -113,6 +113,12 @@ describe('babel-plugin-transform-jsx-to-htm', () => { compile(`<>
Foo
Bar
`) ).toBe('html`
Foo
Bar
`;'); }); + + test('root expressions', () => { + expect( + compile(`{Foo}{Bar}`) + ).toBe('html`${Foo}${Bar}`;'); + }); }); describe('props', () => { From 29751dd9f00f5ec3abcb957de98b3460270e3758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Wed, 5 Jun 2019 20:46:47 -0300 Subject: [PATCH 3/4] Convert indentation from space to tab --- .../index.mjs | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/packages/babel-plugin-transform-jsx-to-htm/index.mjs b/packages/babel-plugin-transform-jsx-to-htm/index.mjs index 6a84b42..f553226 100644 --- a/packages/babel-plugin-transform-jsx-to-htm/index.mjs +++ b/packages/babel-plugin-transform-jsx-to-htm/index.mjs @@ -86,24 +86,24 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { cooked: buffer })); buffer = ''; - } - - 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); + } + + 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) { - if (!isFragment) { - raw('>'); - } + if (!isFragment) { + raw('>'); + } for (let i = 0; i < children.length; i++) { let child = children[i]; if (t.isStringLiteral(child)) { @@ -118,67 +118,67 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { } } - if (!isFragment) { - if (name.match(/^[A-Z]/)) { - raw(''); - } - else { - raw(''); - } - } + 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('<'); - 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); - } - } - } - } - } - - processChildren(node, name, isFragment); + const name = getName(open.name); + const isFragment = name === 'React.Fragment'; + + if (!isFragment) { + 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); + } + } + } + } + } + + processChildren(node, name, isFragment); if (isRoot) { commit(true); @@ -216,9 +216,9 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { buffer = bufferBefore; state.set('jsxElement', true); - }, - - JSXFragment(path, state) { + }, + + JSXFragment(path, state) { let quasisBefore = quasis.slice(); let expressionsBefore = expressions.slice(); let bufferBefore = buffer; @@ -229,17 +229,17 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { processChildren(path.node, '', true); - commit(); - const template = t.templateLiteral(quasis, expressions); - const replacement = t.taggedTemplateExpression(tag, template); - path.replaceWith(replacement); + commit(); + const template = t.templateLiteral(quasis, expressions); + const replacement = t.taggedTemplateExpression(tag, template); + path.replaceWith(replacement); quasis = quasisBefore; expressions = expressionsBefore; - buffer = bufferBefore; - - state.set('jsxElement', true); - } + buffer = bufferBefore; + + state.set('jsxElement', true); + } } }; } From aca13d14c1091952d6295d4a0b877e613574c391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Wed, 5 Jun 2019 20:58:02 -0300 Subject: [PATCH 4/4] Extract jsx visitor logic into a separated function --- .../index.mjs | 63 +++++++++---------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/packages/babel-plugin-transform-jsx-to-htm/index.mjs b/packages/babel-plugin-transform-jsx-to-htm/index.mjs index f553226..6e5ccc2 100644 --- a/packages/babel-plugin-transform-jsx-to-htm/index.mjs +++ b/packages/babel-plugin-transform-jsx-to-htm/index.mjs @@ -188,6 +188,32 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) { } } + 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 { name: 'transform-jsx-to-htm', inherits: jsx, @@ -201,44 +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) { - let quasisBefore = quasis.slice(); - let expressionsBefore = expressions.slice(); - let bufferBefore = buffer; - - buffer = ''; - quasis.length = 0; - expressions.length = 0; - - processChildren(path.node, '', true); - - commit(); - const template = t.templateLiteral(quasis, expressions); - const replacement = t.taggedTemplateExpression(tag, template); - path.replaceWith(replacement); - - quasis = quasisBefore; - expressions = expressionsBefore; - buffer = bufferBefore; - - state.set('jsxElement', true); + jsxVisitorHandler(path, state, true); } } };