| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346 |
1x
1x
1x
1x
1x
1x
460x
1x
149x
149x
149x
1x
73x
127x
127x
29x
98x
6x
92x
1x
168x
401x
1x
81x
1x
168x
1x
81x
81x
81x
81x
81x
81x
1x
81x
50x
31x
12x
19x
19x
1x
59x
129x
21x
108x
93x
15x
3x
12x
12x
1x
206x
206x
206x
206x
206x
3x
203x
206x
206x
33x
3x
33x
6x
33x
173x
5x
168x
43x
94x
31x
1x
6x
6x
6x
1x
168x
168x
168x
168x
168x
118x
118x
118x
118x
50x
50x
50x
1x
125x
125x
125x
73x
73x
73x
73x
52x
50x
2x
2x
1x
31x
31x
1x
1x
1x
53x
53x
53x
53x
53x
53x
53x
53x
53x
53x
53x
53x
53x
57x
57x
57x
59x
53x
53x
53x
53x
152x
98x
98x
98x
101x
98x
54x
53x
5x
48x
44x
53x
161x
8x
8x
153x
149x
159x
238x
238x
238x
238x
193x
193x
193x
193x
| "use strict";
const t = require("babel-core").types;
const getTagAndClassNamesAndId = require("./utils").getTagAndClassNamesAndId;
const revTransform = require("./rev");
const getOption = require("./utils").getOption;
let isHyperscriptInScope = false;
// utility functions that starts with b means build.
const isHyperscriptCall = node => {
return t.isIdentifier(node.callee, {
name: "h"
});
};
const bJsxAttr = (prop, expressionOrValue) => {
const attributeName = t.isStringLiteral(prop)
? prop.extra.rawValue
: prop.name;
const stringOrExpression = t.isStringLiteral(expressionOrValue)
? expressionOrValue
: t.JSXExpressionContainer(expressionOrValue);
return t.JSXAttribute(bJsxIdent(attributeName), stringOrExpression);
};
const bJsxAttributes = objectExpression => {
return objectExpression.properties.map(node => {
const { key, value, argument } = node;
if (t.isSpreadProperty(node) || t.isSpreadElement(node)) {
return t.JSXSpreadAttribute(argument);
} else if (t.isProperty(node) && node.computed && !t.isStringLiteral(key)) {
// to handle h(Abc, { [kek]: 0, ["norm"]: 1 }) to <Abc {...{ [kek]: 0 }} norm={1} />
return t.JSXSpreadAttribute(t.objectExpression([node]));
} else {
return bJsxAttr(key, value);
}
});
};
const bJsxOpenElem = ({ name, selfClosing = false, attributes = [] }) =>
t.JSXOpeningElement(
t.isJSXMemberExpression(name) ? name : bJsxIdent(name),
attributes,
selfClosing
);
const bJsxIdent = name => t.JSXIdentifier(name);
const bJsxCloseElem = name =>
t.isJSXMemberExpression(name)
? t.JSXClosingElement(name)
: t.JSXClosingElement(bJsxIdent(name));
// Builds self closed element
const bJsxElem = ({
name = "div",
attributes = [],
children = [],
selfClosing = false
}) =>
t.JSXElement(
bJsxOpenElem({ attributes, name, selfClosing }),
selfClosing ? null : bJsxCloseElem(name),
children,
selfClosing
);
// Makes component wrap around his children, so closes it around strings/JSXElements/expressions.
const closeComponent = (jsxElem, children) => {
const { name } = jsxElem.openingElement;
jsxElem.selfClosing = false;
jsxElem.openingElement.selfClosing = false;
jsxElem.closingElement = bJsxCloseElem(
t.isJSXMemberExpression(name) ? name : name.name
);
jsxElem.children = children;
return jsxElem;
};
const injectChildren = (jsxElem, node) => {
if (t.isArrayExpression(node)) {
return closeComponent(jsxElem, transformChildrenArray(node));
}
if (t.isStringLiteral(node)) {
return closeComponent(jsxElem, [t.JSXText(node.value)]);
}
Eif (t.isExpression(node)) {
return closeComponent(jsxElem, [t.JSXExpressionContainer(node)]);
}
};
const transformChildrenArray = (node, noExpressionContainers) => {
return node.elements.map(element => {
// Ugliest hack I ever wrote, but this is to avoid putting computed hyperscript calls into the JSXExpressionContainer for ignored computed root h calls
if (
((t.isJSXElement(element) ||
t.isLogicalExpression(element) ||
t.isConditionalExpression(element) ||
t.isMemberExpression(element) ||
t.isIdentifier(element)) &&
noExpressionContainers) ||
(isHyperscriptCall(element) &&
noExpressionContainers &&
element.arguments &&
element.arguments[0] &&
(element.arguments[0].computed ||
(t.isTemplateLiteral(element.arguments[0]) &&
element.arguments[0].expressions.length > 0) ||
(element.arguments[0].arguments &&
element.arguments[0].arguments.length >= 0)))
) {
return element;
}
if (isHyperscriptCall(element)) {
return transformHyperscriptToJsx(element, false);
}
if (t.isStringLiteral(element)) {
return t.JSXText(element.value);
}
Eif (t.isExpression(element)) {
return t.JSXExpressionContainer(element);
}
});
};
const transformHyperscriptToJsx = (node, isTopLevelCall) => {
const [firstArg, secondArg, thirdArg] = node.arguments;
// Handling few corner cases down here
// Handling of h(obj[field]) and h(`stuff ${computed}`) to ignore and convert to StringLiteral if possible
const isTemplateLiteral = t.isTemplateLiteral(firstArg);
const hasExpressions = isTemplateLiteral && firstArg.expressions.length;
const isComputedClassNameOrComponent =
firstArg.computed || hasExpressions || t.isBinaryExpression(firstArg);
// Intermediate value to convert to StringLiteral if TemplateLiteral has no expressions
let firstArgument;
if (isTemplateLiteral && !hasExpressions) {
firstArgument = t.stringLiteral(firstArg.quasis[0].value.raw);
} else {
firstArgument = firstArg;
}
const isFirstArgIsCalledFunction =
firstArg.arguments && firstArg.arguments.length >= 0;
if (
(isComputedClassNameOrComponent || isFirstArgIsCalledFunction) &&
isTopLevelCall
) {
// If top level call just keep node as is
if (t.isArrayExpression(secondArg) && !thirdArg) {
secondArg.elements = transformChildrenArray(secondArg, true);
}
if (t.isArrayExpression(thirdArg)) {
// This will recursively process all children nodes to get JSX/Expressions array
// Second parameter is for ugly hack :P
thirdArg.elements = transformChildrenArray(thirdArg, true);
}
return node;
} else if (isComputedClassNameOrComponent || isFirstArgIsCalledFunction) {
// If nested in JSX wrap in expression container
return t.JSXExpressionContainer(node);
}
switch (node.arguments.length) {
case 1:
return singleArgumentCase(firstArgument);
case 2:
return twoArgumentsCase(firstArgument, secondArg, true);
case 3:
return threeArgumentsCase(firstArgument, secondArg, thirdArg);
default:
break;
}
};
const memberExpressionToJsx = memberExpression => {
const object = t.isMemberExpression(memberExpression.object)
? memberExpressionToJsx(memberExpression.object)
: bJsxIdent(memberExpression.object.name);
const property = bJsxIdent(memberExpression.property.name);
return t.jSXMemberExpression(object, property);
};
const singleArgumentCase = firstArgument => {
const isElement = t.isStringLiteral(firstArgument);
const isReactComponent = t.isIdentifier(firstArgument);
const isMemberExpression = t.isMemberExpression(firstArgument);
let attributes = [];
if (isElement) {
const { id, className, tag } = getTagAndClassNamesAndId(
firstArgument.value
);
className &&
attributes.push(
bJsxAttr(t.JSXIdentifier("className"), t.StringLiteral(className))
);
id && attributes.push(bJsxAttr(t.JSXIdentifier("id"), t.StringLiteral(id)));
return bJsxElem({
selfClosing: true,
name: tag,
attributes
});
} else Eif (isReactComponent || isMemberExpression) {
const componentName = isMemberExpression
? memberExpressionToJsx(firstArgument)
: firstArgument.name;
return bJsxElem({
selfClosing: true,
name: componentName
});
}
};
const twoArgumentsCase = (firstArg, secondArg, thirdArgIsAbsent) => {
const jsxElem = singleArgumentCase(firstArg);
const isPropsObject = t.isObjectExpression(secondArg);
if (isPropsObject) {
const props = bJsxAttributes(secondArg);
const currentProps = jsxElem.openingElement.attributes;
jsxElem.openingElement.attributes = [...currentProps, ...props];
return jsxElem;
}
if (thirdArgIsAbsent) {
return injectChildren(jsxElem, secondArg);
} else {
jsxElem.openingElement.attributes.push(t.JSXSpreadAttribute(secondArg));
return jsxElem;
}
};
const threeArgumentsCase = (firstArg, secondArg, thirdArg) => {
const jsxElem = twoArgumentsCase(firstArg, secondArg, false);
return injectChildren(jsxElem, thirdArg);
};
let isCssModules = false;
module.exports = function() {
return {
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("asyncGenerators");
parserOpts.plugins.push("classProperties");
parserOpts.plugins.push("decorators");
parserOpts.plugins.push("doExpressions");
parserOpts.plugins.push("dynamicImport");
parserOpts.plugins.push("flow");
parserOpts.plugins.push("functionBind");
parserOpts.plugins.push("functionSent");
parserOpts.plugins.push("jsx");
parserOpts.plugins.push("objectRestSpread");
parserOpts.plugins.push("exportDefaultFrom");
parserOpts.plugins.push("exportNamespaceFrom");
},
visitor: {
Program(path) {
isHyperscriptInScope = path.node.body.find(arg => {
Iif (t.isVariableDeclaration(arg)) {
return (
arg.declarations.find(
declaration => declaration.id.name === "h"
) || false
);
}
Eif (t.isImportDeclaration(arg)) {
return (
arg.specifiers.find(specifier => specifier.local.name === "h") ||
false
);
}
});
let isReactDefaultImportInScope = false;
let reactImport = false;
let isReactImportIsInScope = false;
path.node.body.some(arg => {
if (t.isImportDeclaration(arg)) {
isReactImportIsInScope = arg.source.value === "react";
reactImport = isReactImportIsInScope ? arg : false;
isReactDefaultImportInScope = arg.specifiers.some(
it =>
t.isImportDefaultSpecifier(it) &&
it.local.name === "React" &&
isReactImportIsInScope
);
return isReactImportIsInScope;
}
return false;
});
if (
isReactImportIsInScope &&
!isReactDefaultImportInScope &&
isHyperscriptInScope
) {
reactImport.specifiers.unshift(
t.importDefaultSpecifier(t.identifier("React"))
);
} else if (!isReactImportIsInScope && isHyperscriptInScope) {
path.node.body.unshift(
t.ImportDeclaration(
[t.ImportDefaultSpecifier(t.Identifier("React"))],
t.StringLiteral("react")
)
);
}
// This is Revolut specific logic ignore it :)
isCssModules = path.node.body.find(arg => {
if (t.isVariableDeclaration(arg)) {
return (
arg.declarations.find(
declaration => declaration.id.name === "hx"
) || false
);
}
if (t.isImportDeclaration(arg)) {
return (
arg.specifiers.find(specifier => specifier.local.name === "hx") ||
false
);
}
});
},
CallExpression(path, state) {
Eif (isHyperscriptInScope) {
const { node } = path;
const isTopLevelCall =
t.isReturnStatement(path.container) ||
t.isConditionalExpression(path.container) ||
t.isArrowFunctionExpression(path.container) ||
t.isLogicalExpression(path.container) ||
t.isObjectProperty(path.container) ||
t.isVariableDeclarator(path.container) ||
t.isExpressionStatement(path.container) ||
t.isJSXExpressionContainer(path.container) ||
t.isAssignmentExpression(path.container) ||
t.isArrayExpression(path.parent);
if (isHyperscriptCall(node) && isTopLevelCall) {
let result = node;
const isRevolut = getOption(state, "revolut", false);
result = isRevolut
? revTransform(node, isTopLevelCall, !!isCssModules)
: transformHyperscriptToJsx(node, isTopLevelCall);
path.replaceWith(result);
}
}
}
}
};
};
|