@esportsplus/reactivity 0.23.0 → 0.23.1

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.
Files changed (39) hide show
  1. package/build/transformer/detector.js +38 -0
  2. package/build/transformer/{core/index.d.ts → index.d.ts} +1 -1
  3. package/build/transformer/plugins/esbuild.js +3 -2
  4. package/build/transformer/plugins/tsc.js +1 -1
  5. package/build/transformer/plugins/vite.js +2 -2
  6. package/build/transformer/transforms/auto-dispose.js +119 -0
  7. package/build/transformer/{core/transforms → transforms}/reactive-array.d.ts +1 -1
  8. package/build/transformer/transforms/reactive-array.js +93 -0
  9. package/build/transformer/{core/transforms → transforms}/reactive-object.d.ts +1 -1
  10. package/build/transformer/transforms/reactive-object.js +164 -0
  11. package/build/transformer/{core/transforms → transforms}/reactive-primitives.d.ts +1 -1
  12. package/build/transformer/transforms/reactive-primitives.js +335 -0
  13. package/build/transformer/{core/transforms → transforms}/utilities.d.ts +1 -2
  14. package/build/transformer/transforms/utilities.js +73 -0
  15. package/package.json +8 -12
  16. package/src/transformer/detector.ts +65 -0
  17. package/src/transformer/{core/index.ts → index.ts} +1 -5
  18. package/src/transformer/plugins/esbuild.ts +3 -2
  19. package/src/transformer/plugins/tsc.ts +1 -1
  20. package/src/transformer/plugins/vite.ts +2 -4
  21. package/src/transformer/transforms/auto-dispose.ts +191 -0
  22. package/src/transformer/transforms/reactive-array.ts +143 -0
  23. package/src/transformer/{core/transforms → transforms}/reactive-object.ts +101 -92
  24. package/src/transformer/transforms/reactive-primitives.ts +461 -0
  25. package/src/transformer/transforms/utilities.ts +119 -0
  26. package/build/transformer/core/detector.js +0 -6
  27. package/build/transformer/core/transforms/auto-dispose.js +0 -116
  28. package/build/transformer/core/transforms/reactive-array.js +0 -89
  29. package/build/transformer/core/transforms/reactive-object.js +0 -155
  30. package/build/transformer/core/transforms/reactive-primitives.js +0 -325
  31. package/build/transformer/core/transforms/utilities.js +0 -57
  32. package/src/transformer/core/detector.ts +0 -12
  33. package/src/transformer/core/transforms/auto-dispose.ts +0 -194
  34. package/src/transformer/core/transforms/reactive-array.ts +0 -140
  35. package/src/transformer/core/transforms/reactive-primitives.ts +0 -459
  36. package/src/transformer/core/transforms/utilities.ts +0 -95
  37. /package/build/transformer/{core/detector.d.ts → detector.d.ts} +0 -0
  38. /package/build/transformer/{core/index.js → index.js} +0 -0
  39. /package/build/transformer/{core/transforms → transforms}/auto-dispose.d.ts +0 -0
@@ -1,459 +0,0 @@
1
- import type { BindingType, Bindings } from '~/types';
2
- import { uid } from '@esportsplus/typescript/transformer';
3
- import ts from 'typescript';
4
- import { addMissingImports, applyReplacements, Replacement } from './utilities';
5
-
6
-
7
- interface ComputedArgRange {
8
- end: number;
9
- start: number;
10
- }
11
-
12
- interface ScopeBinding {
13
- name: string;
14
- scope: ts.Node;
15
- type: BindingType;
16
- }
17
-
18
-
19
- function findEnclosingScope(node: ts.Node): ts.Node {
20
- let current = node.parent;
21
-
22
- while (current) {
23
- if (
24
- ts.isBlock(current) ||
25
- ts.isSourceFile(current) ||
26
- ts.isFunctionDeclaration(current) ||
27
- ts.isFunctionExpression(current) ||
28
- ts.isArrowFunction(current) ||
29
- ts.isForStatement(current) ||
30
- ts.isForInStatement(current) ||
31
- ts.isForOfStatement(current)
32
- ) {
33
- return current;
34
- }
35
-
36
- current = current.parent;
37
- }
38
-
39
- return node.getSourceFile();
40
- }
41
-
42
- function findBinding(bindings: ScopeBinding[], name: string, node: ts.Node): ScopeBinding | undefined {
43
- for (let i = 0, n = bindings.length; i < n; i++) {
44
- let b = bindings[i];
45
-
46
- if (b.name === name && isInScope(node, b)) {
47
- return b;
48
- }
49
- }
50
-
51
- return undefined;
52
- }
53
-
54
- function isInComputedRange(ranges: ComputedArgRange[], start: number, end: number): boolean {
55
- for (let i = 0, n = ranges.length; i < n; i++) {
56
- let r = ranges[i];
57
-
58
- if (start >= r.start && end <= r.end) {
59
- return true;
60
- }
61
- }
62
-
63
- return false;
64
- }
65
-
66
- function isInScope(reference: ts.Node, binding: ScopeBinding): boolean {
67
- let current: ts.Node | undefined = reference;
68
-
69
- while (current) {
70
- if (current === binding.scope) {
71
- return true;
72
- }
73
-
74
- current = current.parent;
75
- }
76
-
77
- return false;
78
- }
79
-
80
- function classifyReactiveArg(arg: ts.Expression): 'computed' | 'signal' | null {
81
- if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
82
- return 'computed';
83
- }
84
-
85
- if (ts.isObjectLiteralExpression(arg) || ts.isArrayLiteralExpression(arg)) {
86
- return null;
87
- }
88
-
89
- return 'signal';
90
- }
91
-
92
- function isInDeclarationInit(node: ts.Node): boolean {
93
- let parent = node.parent;
94
-
95
- if (ts.isVariableDeclaration(parent) && parent.initializer === node) {
96
- return true;
97
- }
98
-
99
- return false;
100
- }
101
-
102
- function isReactiveReassignment(node: ts.Node): boolean {
103
- let parent = node.parent;
104
-
105
- if (ts.isBinaryExpression(parent) &&
106
- parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
107
- parent.right === node &&
108
- ts.isCallExpression(node) &&
109
- ts.isIdentifier((node as ts.CallExpression).expression) &&
110
- ((node as ts.CallExpression).expression as ts.Identifier).text === 'reactive') {
111
- return true;
112
- }
113
-
114
- return false;
115
- }
116
-
117
- function isWriteContext(node: ts.Identifier): 'simple' | 'compound' | 'increment' | false {
118
- let parent = node.parent;
119
-
120
- if (ts.isBinaryExpression(parent) && parent.left === node) {
121
- let op = parent.operatorToken.kind;
122
-
123
- if (op === ts.SyntaxKind.EqualsToken) {
124
- return 'simple';
125
- }
126
-
127
- if (op >= ts.SyntaxKind.PlusEqualsToken && op <= ts.SyntaxKind.CaretEqualsToken) {
128
- return 'compound';
129
- }
130
-
131
- if (
132
- op === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
133
- op === ts.SyntaxKind.BarBarEqualsToken ||
134
- op === ts.SyntaxKind.QuestionQuestionEqualsToken
135
- ) {
136
- return 'compound';
137
- }
138
- }
139
-
140
- if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
141
- let op = parent.operator;
142
-
143
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
144
- return 'increment';
145
- }
146
- }
147
-
148
- return false;
149
- }
150
-
151
- function getCompoundOperator(kind: ts.SyntaxKind): string {
152
- if (kind === ts.SyntaxKind.PlusEqualsToken) {
153
- return '+';
154
- }
155
- else if (kind === ts.SyntaxKind.MinusEqualsToken) {
156
- return '-';
157
- }
158
- else if (kind === ts.SyntaxKind.AsteriskEqualsToken) {
159
- return '*';
160
- }
161
- else if (kind === ts.SyntaxKind.SlashEqualsToken) {
162
- return '/';
163
- }
164
- else if (kind === ts.SyntaxKind.PercentEqualsToken) {
165
- return '%';
166
- }
167
- else if (kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
168
- return '**';
169
- }
170
- else if (kind === ts.SyntaxKind.AmpersandEqualsToken) {
171
- return '&';
172
- }
173
- else if (kind === ts.SyntaxKind.BarEqualsToken) {
174
- return '|';
175
- }
176
- else if (kind === ts.SyntaxKind.CaretEqualsToken) {
177
- return '^';
178
- }
179
- else if (kind === ts.SyntaxKind.LessThanLessThanEqualsToken) {
180
- return '<<';
181
- }
182
- else if (kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken) {
183
- return '>>';
184
- }
185
- else if (kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
186
- return '>>>';
187
- }
188
- else if (kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken) {
189
- return '&&';
190
- }
191
- else if (kind === ts.SyntaxKind.BarBarEqualsToken) {
192
- return '||';
193
- }
194
- else if (kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
195
- return '??';
196
- }
197
- else {
198
- return '+';
199
- }
200
- }
201
-
202
- function transformComputedArg(
203
- arg: ts.Expression,
204
- scopedBindings: ScopeBinding[],
205
- sourceFile: ts.SourceFile,
206
- neededImports: Set<string>
207
- ): string {
208
- let argStart = arg.getStart(sourceFile),
209
- innerReplacements: Replacement[] = [],
210
- text = arg.getText(sourceFile);
211
-
212
- function visitArg(node: ts.Node): void {
213
- // Only transform identifiers that are signal bindings
214
- if (ts.isIdentifier(node)) {
215
- // Skip if it's a property name in property access (obj.prop - skip prop)
216
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
217
- ts.forEachChild(node, visitArg);
218
- return;
219
- }
220
-
221
- // Skip if it's the function name in a call expression
222
- if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
223
- ts.forEachChild(node, visitArg);
224
- return;
225
- }
226
-
227
- let binding = findBinding(scopedBindings, node.text, node);
228
-
229
- if (binding) {
230
- neededImports.add('read');
231
-
232
- innerReplacements.push({
233
- end: node.end - argStart,
234
- newText: `read(${node.text})`,
235
- start: node.getStart(sourceFile) - argStart
236
- });
237
- }
238
- }
239
-
240
- ts.forEachChild(node, visitArg);
241
- }
242
-
243
- visitArg(arg);
244
-
245
- return applyReplacements(text, innerReplacements);
246
- }
247
-
248
-
249
- const transformReactivePrimitives = (
250
- sourceFile: ts.SourceFile,
251
- bindings: Bindings
252
- ): string => {
253
- let code = sourceFile.getFullText(),
254
- computedArgRanges: ComputedArgRange[] = [],
255
- hasReactiveImport = false,
256
- neededImports = new Set<string>(),
257
- replacements: Replacement[] = [],
258
- scopedBindings: ScopeBinding[] = [];
259
-
260
- // Single-pass visitor: detect imports, bindings, and usages together
261
- function visit(node: ts.Node): void {
262
- if (
263
- ts.isImportDeclaration(node) &&
264
- ts.isStringLiteral(node.moduleSpecifier) &&
265
- node.moduleSpecifier.text.includes('@esportsplus/reactivity')
266
- ) {
267
- let clause = node.importClause;
268
-
269
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
270
- for (let i = 0, n = clause.namedBindings.elements.length; i < n; i++) {
271
- if (clause.namedBindings.elements[i].name.text === 'reactive') {
272
- hasReactiveImport = true;
273
- break;
274
- }
275
- }
276
- }
277
- }
278
-
279
- if (
280
- hasReactiveImport &&
281
- ts.isCallExpression(node) &&
282
- ts.isIdentifier(node.expression) &&
283
- node.expression.text === 'reactive' &&
284
- node.arguments.length > 0
285
- ) {
286
-
287
- let arg = node.arguments[0],
288
- classification = classifyReactiveArg(arg);
289
-
290
- if (classification) {
291
- let varName: string | null = null;
292
-
293
- if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
294
- varName = node.parent.name.text;
295
- }
296
- else if (
297
- ts.isBinaryExpression(node.parent) &&
298
- node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
299
- ts.isIdentifier(node.parent.left)
300
- ) {
301
- varName = node.parent.left.text;
302
- }
303
-
304
- if (varName) {
305
- let scope = findEnclosingScope(node);
306
-
307
- scopedBindings.push({ name: varName, scope, type: classification });
308
- bindings.set(varName, classification);
309
- }
310
-
311
- if (classification === 'computed') {
312
- // Track range to skip identifiers inside when visitor continues
313
- computedArgRanges.push({
314
- end: arg.end,
315
- start: arg.getStart(sourceFile)
316
- });
317
-
318
- // Transform signal references inside the computed arg
319
- let argText = transformComputedArg(arg, scopedBindings, sourceFile, neededImports);
320
-
321
- replacements.push({
322
- end: node.end,
323
- newText: `computed(${argText})`,
324
- start: node.pos
325
- });
326
-
327
- neededImports.add('computed');
328
- }
329
- else {
330
- let argText = arg.getText(sourceFile);
331
-
332
- replacements.push({
333
- end: node.end,
334
- newText: `signal(${argText})`,
335
- start: node.pos
336
- });
337
-
338
- neededImports.add('signal');
339
- }
340
- }
341
- }
342
-
343
- // Transform identifier usages
344
- if (ts.isIdentifier(node) && !isInDeclarationInit(node.parent)) {
345
- // Skip if it's a property name in property access (obj.prop - skip prop)
346
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
347
- ts.forEachChild(node, visit);
348
- return;
349
- }
350
-
351
- let nodeStart = node.getStart(sourceFile);
352
-
353
- // Skip if inside a computed arg we already transformed
354
- let insideComputedArg = isInComputedRange(computedArgRanges, nodeStart, node.end);
355
-
356
- if (insideComputedArg) {
357
- ts.forEachChild(node, visit);
358
- return;
359
- }
360
-
361
- let binding = findBinding(scopedBindings, node.text, node),
362
- name = node.text;
363
-
364
- if (binding) {
365
- if (
366
- !isReactiveReassignment(node.parent) &&
367
- !(ts.isTypeOfExpression(node.parent) && node.parent.expression === node)
368
- ) {
369
- let writeCtx = isWriteContext(node);
370
-
371
- if (writeCtx) {
372
- if (binding.type !== 'computed') {
373
- neededImports.add('set');
374
-
375
- let parent = node.parent;
376
-
377
- if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
378
- let valueText = parent.right.getText(sourceFile);
379
-
380
- replacements.push({
381
- end: parent.end,
382
- newText: `set(${name}, ${valueText})`,
383
- start: parent.pos
384
- });
385
- }
386
- else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
387
- let op = getCompoundOperator(parent.operatorToken.kind),
388
- valueText = parent.right.getText(sourceFile);
389
-
390
- replacements.push({
391
- end: parent.end,
392
- newText: `set(${name}, ${name}.value ${op} ${valueText})`,
393
- start: parent.pos
394
- });
395
- }
396
- else if (writeCtx === 'increment') {
397
- let isPrefix = ts.isPrefixUnaryExpression(parent),
398
- op = (parent as ts.PrefixUnaryExpression | ts.PostfixUnaryExpression).operator,
399
- delta = op === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1';
400
-
401
- if (ts.isExpressionStatement(parent.parent)) {
402
- replacements.push({
403
- end: parent.end,
404
- newText: `set(${name}, ${name}.value ${delta})`,
405
- start: parent.pos
406
- });
407
- }
408
- else if (isPrefix) {
409
- replacements.push({
410
- end: parent.end,
411
- newText: `(set(${name}, ${name}.value ${delta}), ${name}.value)`,
412
- start: parent.pos
413
- });
414
- }
415
- else {
416
- let tmp = uid('tmp');
417
-
418
- replacements.push({
419
- end: parent.end,
420
- newText: `((${tmp}) => (set(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
421
- start: parent.pos
422
- });
423
- }
424
- }
425
- }
426
- }
427
- else {
428
- neededImports.add('read');
429
-
430
- replacements.push({
431
- end: node.end,
432
- newText: `read(${name})`,
433
- start: node.pos
434
- });
435
- }
436
- }
437
- }
438
- }
439
-
440
- ts.forEachChild(node, visit);
441
- }
442
-
443
- visit(sourceFile);
444
-
445
- if (replacements.length === 0) {
446
- return code;
447
- }
448
-
449
- let result = applyReplacements(code, replacements);
450
-
451
- if (neededImports.size > 0) {
452
- result = addMissingImports(result, neededImports);
453
- }
454
-
455
- return result;
456
- };
457
-
458
-
459
- export { transformReactivePrimitives };
@@ -1,95 +0,0 @@
1
- import type { Replacement } from '@esportsplus/typescript/transformer';
2
- import { applyReplacements } from '@esportsplus/typescript/transformer';
3
-
4
-
5
- type ExtraImport = {
6
- module: string;
7
- specifier: string;
8
- }
9
-
10
-
11
- const BRACES_CONTENT_REGEX = /\{([^}]*)\}/;
12
-
13
- const REACTIVITY_IMPORT_REGEX = /(import\s*\{[^}]*\}\s*from\s*['"]@esportsplus\/reactivity['"])/;
14
-
15
-
16
- const addMissingImports = (code: string, needed: Set<string>, extraImports?: ExtraImport[]): string => {
17
- let reactivityImportMatch = code.match(REACTIVITY_IMPORT_REGEX);
18
-
19
- if (!reactivityImportMatch) {
20
- return code;
21
- }
22
-
23
- let bracesMatch = reactivityImportMatch[1].match(BRACES_CONTENT_REGEX),
24
- existing = new Set<string>(),
25
- existingImport = reactivityImportMatch[1],
26
- extraSpecifiers = new Set<string>(),
27
- toAdd: string[] = [];
28
-
29
- if (bracesMatch?.[1]) {
30
- let parts = bracesMatch[1].split(',');
31
-
32
- for (let i = 0, n = parts.length; i < n; i++) {
33
- let trimmed = parts[i].trim();
34
-
35
- if (trimmed) {
36
- existing.add(trimmed);
37
- }
38
- }
39
- }
40
-
41
- if (extraImports) {
42
- for (let i = 0, n = extraImports.length; i < n; i++) {
43
- extraSpecifiers.add(extraImports[i].specifier);
44
- }
45
- }
46
-
47
- for (let imp of needed) {
48
- if (!extraSpecifiers.has(imp) && !existing.has(imp)) {
49
- toAdd.push(imp);
50
- }
51
- }
52
-
53
- if (toAdd.length > 0) {
54
- let combined: string[] = [];
55
-
56
- for (let item of existing) {
57
- if (item) {
58
- combined.push(item);
59
- }
60
- }
61
-
62
- for (let i = 0, n = toAdd.length; i < n; i++) {
63
- if (toAdd[i]) {
64
- combined.push(toAdd[i]);
65
- }
66
- }
67
-
68
- combined.sort();
69
-
70
- code = code.replace(
71
- existingImport,
72
- existingImport.replace(BRACES_CONTENT_REGEX, `{ ${combined.join(', ')} }`)
73
- );
74
- }
75
-
76
- if (extraImports) {
77
- for (let i = 0, n = extraImports.length; i < n; i++) {
78
- let extra = extraImports[i];
79
-
80
- if (needed.has(extra.specifier) && !code.includes(extra.module)) {
81
- let insertPos = code.indexOf('import');
82
-
83
- code = code.substring(0, insertPos) +
84
- `import { ${extra.specifier} } from '${extra.module}';\n` +
85
- code.substring(insertPos);
86
- }
87
- }
88
- }
89
-
90
- return code;
91
- };
92
-
93
-
94
- export { addMissingImports, applyReplacements };
95
- export type { ExtraImport, Replacement };
File without changes