@litsx/babel-preset-litsx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,549 @@
1
+ import jsxSyntaxPlugin from "@babel/plugin-syntax-jsx";
2
+ import {
3
+ createTypeResolver,
4
+ ensureTypescriptModule,
5
+ extractProperties,
6
+ setPropertyBabelTypes,
7
+ } from "./transform-litsx-properties.js";
8
+ import {
9
+ assertStaticHoistsStayTopLevel,
10
+ processStaticHoists,
11
+ setStaticHoistsBabelTypes,
12
+ } from "./transform-litsx-static-hoists.js";
13
+ import {
14
+ collectNativeClassNameWarnings,
15
+ createHandlerClassMember,
16
+ processHandlers,
17
+ setHandlersBabelTypes,
18
+ } from "./transform-litsx-handlers.js";
19
+ import {
20
+ handlePotentialComponentExport,
21
+ maybeTransformWrappedVariableDeclarator,
22
+ setWrapperUtilsBabelTypes,
23
+ } from "./transform-litsx-wrapper-utils.js";
24
+ import {
25
+ createComponentInstanceRefSyncStatement,
26
+ hasRefProp,
27
+ lowerForwardedElementRefs,
28
+ setRefsBabelTypes,
29
+ } from "./transform-litsx-refs.js";
30
+ import {
31
+ buildClassMembers,
32
+ createComponentClass,
33
+ setClassGenerationBabelTypes,
34
+ } from "./transform-litsx-class-generation.js";
35
+ import {
36
+ replaceParamReferences,
37
+ setParamRewriteBabelTypes,
38
+ transformJSXExpressions,
39
+ } from "./transform-litsx-param-rewrites.js";
40
+ import {
41
+ finalizeProgram,
42
+ setProgramBabelTypes,
43
+ } from "./transform-litsx-program.js";
44
+
45
+ let t;
46
+
47
+ export function createTransformFunctionToClassPlugin(defaultPluginOptions = {}) {
48
+ return function transformFunctionToClassPlugin(_api, pluginOptions = {}) {
49
+ ensureTypescriptModule();
50
+ t = _api.types;
51
+ setPropertyBabelTypes(t);
52
+ setStaticHoistsBabelTypes(t);
53
+ setHandlersBabelTypes(t);
54
+ setWrapperUtilsBabelTypes(t);
55
+ setRefsBabelTypes(t);
56
+ setClassGenerationBabelTypes(t);
57
+ setParamRewriteBabelTypes(t);
58
+ setProgramBabelTypes(t);
59
+ const resolvedPluginOptions = {
60
+ ...defaultPluginOptions,
61
+ ...pluginOptions,
62
+ };
63
+
64
+ const getWrapperMetadata =
65
+ typeof resolvedPluginOptions.getWrapperMetadata === "function"
66
+ ? resolvedPluginOptions.getWrapperMetadata
67
+ : null;
68
+
69
+ return {
70
+ name: "transform-function-to-class",
71
+ inherits: jsxSyntaxPlugin.default || jsxSyntaxPlugin,
72
+ pre() {
73
+ this.__litsxTransformCount = 0;
74
+ this.__litsxNeedsCss = false;
75
+ this.__litsxNeedsUnsafeCss = false;
76
+ this.__litsxNeedsStaticHoistsMixin = false;
77
+ this.__litsxNeedsLightDomMixin = false;
78
+ this.__litsxNeedsCallbackRef = false;
79
+ this.__litsxWarnings = [];
80
+ this.__litsxResolvedPluginOptions = resolvedPluginOptions;
81
+ this.__litsxTypeResolver = fileLikelyNeedsTypeResolver(this)
82
+ ? createTypeResolver(
83
+ this.file?.opts?.filename,
84
+ this.file?.code,
85
+ resolvedPluginOptions
86
+ )
87
+ : undefined;
88
+ },
89
+ post() {
90
+ if (!this.file) return;
91
+ this.file.metadata ||= {};
92
+ this.file.metadata.litsxWarnings = this.__litsxWarnings || [];
93
+ },
94
+ visitor: {
95
+ Program: {
96
+ exit(programPath) {
97
+ finalizeProgram(programPath, this);
98
+ },
99
+ },
100
+ ExportNamedDeclaration(exportPath) {
101
+ handlePotentialComponentExport({
102
+ exportPath,
103
+ state: this,
104
+ transformFunction,
105
+ isInsideFunctionOrClass,
106
+ updateTransformState,
107
+ getWrapperMetadata,
108
+ });
109
+ },
110
+ ExportDefaultDeclaration(exportPath) {
111
+ handlePotentialComponentExport({
112
+ exportPath,
113
+ state: this,
114
+ isDefault: true,
115
+ transformFunction,
116
+ isInsideFunctionOrClass,
117
+ updateTransformState,
118
+ getWrapperMetadata,
119
+ });
120
+ },
121
+ VariableDeclarator(varPath) {
122
+ if (
123
+ varPath.findParent(
124
+ (p) => p.isExportNamedDeclaration?.() || p.isExportDefaultDeclaration?.()
125
+ )
126
+ ) {
127
+ return;
128
+ }
129
+
130
+ const initPath = varPath.get("init");
131
+
132
+ if (
133
+ maybeTransformWrappedVariableDeclarator({
134
+ varPath,
135
+ resolvedPluginOptions,
136
+ state: this,
137
+ transformFunction,
138
+ updateTransformState,
139
+ getWrapperMetadata,
140
+ })
141
+ ) {
142
+ return;
143
+ }
144
+
145
+ if (initPath && initPath.isArrowFunctionExpression() && !isInsideFunctionOrClass(varPath)) {
146
+ const programPath = varPath.findParent((p) => p.isProgram());
147
+ const elementCandidates = collectElementCandidates(initPath, programPath);
148
+ const classNode = transformFunction(
149
+ initPath,
150
+ programPath,
151
+ varPath.node.id.name,
152
+ {
153
+ ...resolvedPluginOptions,
154
+ typeResolver: getTypeResolverForFunction(initPath, this),
155
+ warn: (warning) => {
156
+ this.__litsxWarnings.push(warning);
157
+ },
158
+ }
159
+ );
160
+
161
+ if (!classNode) return;
162
+
163
+ if (elementCandidates.size) {
164
+ classNode._litsxElementCandidates &&= new Set(classNode._litsxElementCandidates);
165
+ const elementSet = classNode._litsxElementCandidates ||= new Set();
166
+ elementCandidates.forEach((candidate) => elementSet.add(candidate));
167
+ }
168
+
169
+ const declarationPath = varPath.parentPath;
170
+ if (!declarationPath.isVariableDeclaration()) return;
171
+
172
+ varPath.scope.removeBinding(varPath.node.id.name);
173
+ declarationPath.replaceWith(classNode);
174
+ declarationPath.requeue();
175
+ updateTransformState(this, classNode);
176
+ }
177
+ },
178
+ FunctionDeclaration(funcPath) {
179
+ if (!isInsideFunctionOrClass(funcPath)) {
180
+ const programPath = funcPath.findParent((p) => p.isProgram());
181
+ const elementCandidates = collectElementCandidates(funcPath, programPath);
182
+ const classNode = transformFunction(
183
+ funcPath,
184
+ programPath,
185
+ undefined,
186
+ {
187
+ ...resolvedPluginOptions,
188
+ typeResolver: getTypeResolverForFunction(funcPath, this),
189
+ warn: (warning) => {
190
+ this.__litsxWarnings.push(warning);
191
+ },
192
+ }
193
+ );
194
+
195
+ if (!classNode) return;
196
+
197
+ if (funcPath.node.id) {
198
+ funcPath.scope.removeBinding(funcPath.node.id.name);
199
+ }
200
+ if (elementCandidates.size) {
201
+ classNode._litsxElementCandidates &&= new Set(classNode._litsxElementCandidates);
202
+ const elementSet = classNode._litsxElementCandidates ||= new Set();
203
+ elementCandidates.forEach((candidate) => elementSet.add(candidate));
204
+ }
205
+ funcPath.replaceWith(classNode);
206
+ funcPath.requeue();
207
+ updateTransformState(this, classNode);
208
+ }
209
+ },
210
+ },
211
+ };
212
+ };
213
+ }
214
+
215
+ export default createTransformFunctionToClassPlugin();
216
+
217
+ function getOrCreateModuleStaticHoistSymbol(programPath, hoistName) {
218
+ let symbolMap = programPath.getData("__litsxStaticHoistSymbols");
219
+ if (!symbolMap) {
220
+ symbolMap = new Map();
221
+ programPath.setData("__litsxStaticHoistSymbols", symbolMap);
222
+ }
223
+
224
+ if (symbolMap.has(hoistName)) {
225
+ return symbolMap.get(hoistName);
226
+ }
227
+
228
+ const symbolId = programPath.scope.generateUidIdentifier(`litsx_static_${hoistName}`);
229
+ const declaration = t.variableDeclaration("const", [
230
+ t.variableDeclarator(
231
+ symbolId,
232
+ t.callExpression(t.identifier("Symbol"), [t.stringLiteral(`litsx.static.${hoistName}`)])
233
+ ),
234
+ ]);
235
+
236
+ const entry = { symbolId, declaration };
237
+ symbolMap.set(hoistName, entry);
238
+ return entry;
239
+ }
240
+
241
+
242
+ function updateTransformState(state, classNode) {
243
+ if (!state || !classNode) {
244
+ return;
245
+ }
246
+
247
+ state.__litsxTransformCount = (state.__litsxTransformCount || 0) + 1;
248
+ state.__litsxNeedsCss ||= Boolean(classNode._needsCss);
249
+ state.__litsxNeedsUnsafeCss ||= Boolean(classNode._needsUnsafeCss);
250
+ state.__litsxNeedsStaticHoistsMixin ||= Boolean(
251
+ classNode._needsStaticHoistsMixin
252
+ );
253
+ state.__litsxNeedsLightDomMixin ||= Boolean(
254
+ classNode._needsLightDomMixin
255
+ );
256
+ state.__litsxNeedsCallbackRef ||= Boolean(
257
+ classNode._needsCallbackRef
258
+ );
259
+ }
260
+
261
+ // Verifica si el nodo está dentro de otra función o clase
262
+ function isInsideFunctionOrClass(path) {
263
+ return path.findParent(
264
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression() || p.isClassDeclaration()
265
+ );
266
+ }
267
+
268
+ function getOrCreateTypeResolver(state) {
269
+ if (state.__litsxTypeResolver !== undefined) {
270
+ return state.__litsxTypeResolver;
271
+ }
272
+
273
+ state.__litsxTypeResolver = createTypeResolver(
274
+ state.file?.opts?.filename,
275
+ state.file?.code,
276
+ state.__litsxResolvedPluginOptions
277
+ );
278
+ return state.__litsxTypeResolver;
279
+ }
280
+
281
+ function fileLikelyNeedsTypeResolver(state) {
282
+ const filename = state?.file?.opts?.filename || "";
283
+ if (/\.(?:[cm]?ts|tsx)$/i.test(filename)) {
284
+ return true;
285
+ }
286
+
287
+ const source = state?.file?.code || "";
288
+ return /\b(?:type|interface|enum)\b/.test(source);
289
+ }
290
+
291
+ function functionNeedsTypeResolver(functionPath, state) {
292
+ const params = functionPath.get("params");
293
+ if (!Array.isArray(params) || params.length === 0) {
294
+ return false;
295
+ }
296
+
297
+ if (fileLikelyNeedsTypeResolver(state)) {
298
+ return true;
299
+ }
300
+
301
+ return params.some((paramPath) => containsTypeResolutionSyntax(paramPath));
302
+ }
303
+
304
+ function containsTypeResolutionSyntax(path) {
305
+ if (!path?.node) {
306
+ return false;
307
+ }
308
+
309
+ if (
310
+ path.isIdentifier?.() ||
311
+ path.isObjectPattern?.() ||
312
+ path.isArrayPattern?.() ||
313
+ path.isAssignmentPattern?.()
314
+ ) {
315
+ if (path.node.typeAnnotation) {
316
+ return true;
317
+ }
318
+ }
319
+
320
+ if (path.isAssignmentPattern?.()) {
321
+ return containsTypeResolutionSyntax(path.get("left"));
322
+ }
323
+
324
+ if (path.isObjectPattern?.()) {
325
+ return path.get("properties").some((propertyPath) => {
326
+ if (propertyPath.isRestElement()) {
327
+ return containsTypeResolutionSyntax(propertyPath.get("argument"));
328
+ }
329
+ if (propertyPath.isObjectProperty()) {
330
+ return containsTypeResolutionSyntax(propertyPath.get("value"));
331
+ }
332
+ return false;
333
+ });
334
+ }
335
+
336
+ if (path.isArrayPattern?.()) {
337
+ return path.get("elements").some((elementPath) => {
338
+ if (!elementPath?.node) return false;
339
+ return containsTypeResolutionSyntax(elementPath);
340
+ });
341
+ }
342
+
343
+ return false;
344
+ }
345
+
346
+ function getTypeResolverForFunction(functionPath, state) {
347
+ if (!functionNeedsTypeResolver(functionPath, state)) {
348
+ return null;
349
+ }
350
+
351
+ return getOrCreateTypeResolver(state);
352
+ }
353
+
354
+ function transformFunction(functionPath, programPath, className, options = {}) {
355
+ const { node } = functionPath;
356
+ const forwardRefOptions = options.forwardRef || null;
357
+ let resolvedName = className;
358
+ if (!resolvedName && node && node.id && t.isIdentifier(node.id)) {
359
+ resolvedName = node.id.name;
360
+ }
361
+ if (!resolvedName) {
362
+ resolvedName = "AnonymousComponent";
363
+ }
364
+
365
+ className = resolvedName;
366
+
367
+ const {
368
+ properties: propertiesStatic,
369
+ propertyNames,
370
+ bindings,
371
+ defaults,
372
+ nestedInitializers,
373
+ } = extractProperties(
374
+ functionPath,
375
+ programPath,
376
+ options
377
+ );
378
+
379
+ assertStaticHoistsStayTopLevel(functionPath);
380
+ collectNativeClassNameWarnings(functionPath, options.warn, options);
381
+
382
+ let returnStatement;
383
+ functionPath.traverse({
384
+ ReturnStatement(returnPath) {
385
+ if (t.isJSXElement(returnPath.node.argument)) {
386
+ returnStatement = returnPath.node;
387
+ transformJSXExpressions(returnPath, bindings);
388
+ }
389
+ },
390
+ });
391
+
392
+ if (!returnStatement) return;
393
+
394
+ const capturedPropAliasStatements = replaceParamReferences(functionPath, bindings, propertyNames);
395
+
396
+ const usedNames = new Set([
397
+ ...Object.keys(functionPath.scope.bindings || {}),
398
+ "render",
399
+ "properties",
400
+ "constructor",
401
+ ]);
402
+
403
+ const handlerInfos = processHandlers(functionPath, usedNames);
404
+
405
+ const renderStatements = t.isBlockStatement(node.body)
406
+ ? [...node.body.body]
407
+ : [t.returnStatement(node.body)];
408
+
409
+ const resolvedRefPropName = forwardRefOptions?.propName ||
410
+ (propertyNames.has("ref") || hasRefProp(functionPath) ? "ref" : null);
411
+ let needsCallbackRef = false;
412
+
413
+ if (resolvedRefPropName) {
414
+ renderStatements.unshift(
415
+ ...lowerForwardedElementRefs(functionPath, resolvedRefPropName)
416
+ );
417
+ needsCallbackRef = renderStatements.some(
418
+ (statement) =>
419
+ t.isExpressionStatement(statement) &&
420
+ t.isCallExpression(statement.expression) &&
421
+ t.isIdentifier(statement.expression.callee, { name: "useCallbackRef" })
422
+ ) || needsCallbackRef;
423
+ }
424
+
425
+ if (resolvedRefPropName && !forwardRefOptions) {
426
+ renderStatements.unshift(createComponentInstanceRefSyncStatement());
427
+ needsCallbackRef = true;
428
+ }
429
+
430
+ if (capturedPropAliasStatements.length > 0) {
431
+ renderStatements.unshift(...capturedPropAliasStatements);
432
+ }
433
+
434
+ if (nestedInitializers.length > 0) {
435
+ const initializerStatements = nestedInitializers.map(({ pattern, root, defaultValue }) =>
436
+ createNestedInitializerStatement(pattern, root, defaultValue, t)
437
+ );
438
+ renderStatements.unshift(...initializerStatements);
439
+ }
440
+
441
+ const classMembers = [];
442
+
443
+ const {
444
+ lightDomRequested,
445
+ hoistMembers,
446
+ hoistSymbolDeclarations,
447
+ needsStaticHoistsMixin,
448
+ needsCss,
449
+ needsUnsafeCss,
450
+ } = processStaticHoists({
451
+ functionPath,
452
+ node,
453
+ renderStatements,
454
+ programPath,
455
+ propertiesStatic,
456
+ classMembers,
457
+ options,
458
+ getOrCreateModuleStaticHoistSymbol,
459
+ });
460
+
461
+ buildClassMembers({
462
+ classMembers,
463
+ defaults,
464
+ renderStatements,
465
+ handlerInfos,
466
+ createHandlerClassMember,
467
+ });
468
+
469
+ return createComponentClass({
470
+ className,
471
+ classMembers,
472
+ hoistMembers,
473
+ hoistSymbolDeclarations,
474
+ needsStaticHoistsMixin,
475
+ lightDomRequested,
476
+ needsCss,
477
+ needsUnsafeCss,
478
+ needsCallbackRef,
479
+ });
480
+ }
481
+
482
+ function ensureClassIdentifier(classNode, fallbackName) {
483
+ if (classNode.id && t.isIdentifier(classNode.id)) {
484
+ return classNode.id;
485
+ }
486
+
487
+ const safeName =
488
+ typeof fallbackName === "string" && fallbackName
489
+ ? fallbackName
490
+ : "AnonymousComponent";
491
+ const identifier = t.identifier(safeName);
492
+ classNode.id = identifier;
493
+ return identifier;
494
+ }
495
+
496
+ function createThisMemberExpression(propName) {
497
+ return t.memberExpression(t.thisExpression(), t.identifier(propName));
498
+ }
499
+
500
+ function createNestedInitializerStatement(pattern, root, defaultValue, t) {
501
+ const rootAccess = createThisMemberExpression(root);
502
+ let sourceExpression = rootAccess;
503
+
504
+ if (defaultValue) {
505
+ sourceExpression = t.logicalExpression(
506
+ "??",
507
+ t.cloneNode(rootAccess),
508
+ t.cloneNode(defaultValue)
509
+ );
510
+ }
511
+
512
+ return t.variableDeclaration("const", [
513
+ t.variableDeclarator(t.cloneNode(pattern), sourceExpression),
514
+ ]);
515
+ }
516
+
517
+ function collectElementCandidates(functionPath, programPath) {
518
+ const candidates = new Set();
519
+ if (!programPath) return candidates;
520
+
521
+ const importNames = new Set();
522
+ programPath.get("body").forEach((nodePath) => {
523
+ if (!nodePath.isImportDeclaration()) return;
524
+ nodePath.node.specifiers.forEach((specifier) => {
525
+ if (specifier.local) {
526
+ importNames.add(specifier.local.name);
527
+ }
528
+ });
529
+ });
530
+
531
+ functionPath.traverse({
532
+ JSXOpeningElement(path) {
533
+ if (!path.node.name || path.node.name.type !== "JSXIdentifier") return;
534
+ const originalName = path.node.name.name;
535
+ if (!importNames.has(originalName)) return;
536
+
537
+ path.node.__scopedOriginal = originalName;
538
+ candidates.add(originalName);
539
+ },
540
+ JSXClosingElement(path) {
541
+ if (!path.node.name || path.node.name.type !== "JSXIdentifier") return;
542
+ const originalName = path.node.name.name;
543
+ if (!importNames.has(originalName)) return;
544
+ path.node.__scopedOriginal = originalName;
545
+ },
546
+ });
547
+
548
+ return candidates;
549
+ }
@@ -0,0 +1,9 @@
1
+ import { createUseRefTransform } from "@litsx/babel-plugin-shared-hooks";
2
+
3
+ export default createUseRefTransform({
4
+ importSource: "litsx",
5
+ hookNames: ["useRef"],
6
+ pluginName: "transform-litsx-dom-refs",
7
+ pendingPropertyKey: "_litsxPendingElements",
8
+ onlyManagedDomRefs: true,
9
+ });