Skip to content

Commit 89e066f

Browse files
blikblumjviide
authored andcommitted
babel-transform-jsx: handle JSX fragments
1 parent c51b1e0 commit 89e066f

File tree

2 files changed

+114
-56
lines changed

2 files changed

+114
-56
lines changed

packages/babel-plugin-transform-jsx-to-htm/index.mjs

Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -86,50 +86,24 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) {
8686
cooked: buffer
8787
}));
8888
buffer = '';
89-
}
90-
91-
function processNode(node, path, isRoot) {
92-
const open = node.openingElement;
93-
const { name } = open.name;
94-
95-
if (name.match(/^[A-Z]/)) {
96-
raw('<');
97-
expr(t.identifier(name));
98-
}
99-
else {
100-
raw('<');
101-
raw(name);
102-
}
103-
104-
if (open.attributes) {
105-
for (let i = 0; i < open.attributes.length; i++) {
106-
const attr = open.attributes[i];
107-
raw(' ');
108-
if (t.isJSXSpreadAttribute(attr)) {
109-
raw('...');
110-
expr(attr.argument);
111-
continue;
112-
}
113-
const { name, value } = attr;
114-
raw(name.name);
115-
if (value) {
116-
raw('=');
117-
if (value.expression) {
118-
expr(value.expression);
119-
}
120-
else if (t.isStringLiteral(value)) {
121-
escapePropValue(value);
122-
}
123-
else {
124-
expr(value);
125-
}
126-
}
127-
}
128-
}
129-
130-
const children = t.react.buildChildren(node);
89+
}
90+
91+
function getName(node) {
92+
switch (node.type) {
93+
case 'JSXMemberExpression':
94+
return `${node.object.name}.${node.property.name}`
95+
96+
default:
97+
return node.name;
98+
}
99+
}
100+
101+
function processChildren(node, name, isFragment) {
102+
const children = t.react.buildChildren(node);
131103
if (children && children.length !== 0) {
132-
raw('>');
104+
if (!isFragment) {
105+
raw('>');
106+
}
133107
for (let i = 0; i < children.length; i++) {
134108
let child = children[i];
135109
if (t.isStringLiteral(child)) {
@@ -144,20 +118,67 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) {
144118
}
145119
}
146120

147-
if (name.match(/^[A-Z]/)) {
148-
raw('</');
149-
expr(t.identifier(name));
150-
raw('>');
151-
}
152-
else {
153-
raw('</');
154-
raw(name);
155-
raw('>');
156-
}
121+
if (!isFragment) {
122+
if (name.match(/^[A-Z]/)) {
123+
raw('</');
124+
expr(t.identifier(name));
125+
raw('>');
126+
}
127+
else {
128+
raw('</');
129+
raw(name);
130+
raw('>');
131+
}
132+
}
157133
}
158-
else {
134+
else if (!isFragment) {
159135
raw('/>');
160-
}
136+
}
137+
}
138+
139+
function processNode(node, path, isRoot) {
140+
const open = node.openingElement;
141+
const name = getName(open.name);
142+
const isFragment = name === 'React.Fragment';
143+
144+
if (!isFragment) {
145+
if (name.match(/^[A-Z]/)) {
146+
raw('<');
147+
expr(t.identifier(name));
148+
}
149+
else {
150+
raw('<');
151+
raw(name);
152+
}
153+
154+
if (open.attributes) {
155+
for (let i = 0; i < open.attributes.length; i++) {
156+
const attr = open.attributes[i];
157+
raw(' ');
158+
if (t.isJSXSpreadAttribute(attr)) {
159+
raw('...');
160+
expr(attr.argument);
161+
continue;
162+
}
163+
const { name, value } = attr;
164+
raw(name.name);
165+
if (value) {
166+
raw('=');
167+
if (value.expression) {
168+
expr(value.expression);
169+
}
170+
else if (t.isStringLiteral(value)) {
171+
escapePropValue(value);
172+
}
173+
else {
174+
expr(value);
175+
}
176+
}
177+
}
178+
}
179+
}
180+
181+
processChildren(node, name, isFragment);
161182

162183
if (isRoot) {
163184
commit();
@@ -195,7 +216,30 @@ export default function jsxToHtmBabelPlugin({ types: t }, options = {}) {
195216
buffer = bufferBefore;
196217

197218
state.set('jsxElement', true);
198-
}
219+
},
220+
221+
JSXFragment(path, state) {
222+
let quasisBefore = quasis.slice();
223+
let expressionsBefore = expressions.slice();
224+
let bufferBefore = buffer;
225+
226+
buffer = '';
227+
quasis.length = 0;
228+
expressions.length = 0;
229+
230+
processChildren(path.node, '', true);
231+
232+
commit();
233+
const template = t.templateLiteral(quasis, expressions);
234+
const replacement = t.taggedTemplateExpression(tag, template);
235+
path.replaceWith(replacement);
236+
237+
quasis = quasisBefore;
238+
expressions = expressionsBefore;
239+
buffer = bufferBefore;
240+
241+
state.set('jsxElement', true);
242+
}
199243
}
200244
};
201245
}

test/babel-transform-jsx.test.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ describe('babel-plugin-transform-jsx-to-htm', () => {
101101
});
102102
});
103103

104+
describe('fragments', () => {
105+
test('React.Fragment', () => {
106+
expect(
107+
compile(`<React.Fragment><div>Foo</div><div>Bar</div></React.Fragment>`)
108+
).toBe('html`<div>Foo</div><div>Bar</div>`;');
109+
});
110+
111+
test('short syntax', () => {
112+
expect(
113+
compile(`<><div>Foo</div><div>Bar</div></>`)
114+
).toBe('html`<div>Foo</div><div>Bar</div>`;');
115+
});
116+
});
117+
104118
describe('props', () => {
105119
test('static values', () => {
106120
expect(

0 commit comments

Comments
 (0)