@esportsplus/reactivity 0.23.0 → 0.23.2

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 (44) hide show
  1. package/build/transformer/detector.js +38 -0
  2. package/build/transformer/index.d.ts +9 -0
  3. package/build/transformer/{core/index.js → index.js} +9 -18
  4. package/build/transformer/plugins/esbuild.d.ts +1 -3
  5. package/build/transformer/plugins/esbuild.js +5 -4
  6. package/build/transformer/plugins/tsc.js +1 -1
  7. package/build/transformer/plugins/vite.d.ts +1 -3
  8. package/build/transformer/plugins/vite.js +4 -4
  9. package/build/transformer/{core/transforms/reactive-array.d.ts → transforms/array.d.ts} +1 -1
  10. package/build/transformer/transforms/array.js +93 -0
  11. package/build/transformer/{core/transforms/reactive-object.d.ts → transforms/object.d.ts} +1 -1
  12. package/build/transformer/transforms/object.js +163 -0
  13. package/build/transformer/{core/transforms/reactive-primitives.d.ts → transforms/primitives.d.ts} +1 -1
  14. package/build/transformer/transforms/primitives.js +335 -0
  15. package/build/transformer/{core/transforms → transforms}/utilities.d.ts +1 -2
  16. package/build/transformer/transforms/utilities.js +73 -0
  17. package/build/types.d.ts +1 -4
  18. package/package.json +8 -12
  19. package/readme.md +0 -24
  20. package/src/transformer/detector.ts +65 -0
  21. package/src/transformer/{core/index.ts → index.ts} +10 -25
  22. package/src/transformer/plugins/esbuild.ts +5 -6
  23. package/src/transformer/plugins/tsc.ts +1 -1
  24. package/src/transformer/plugins/vite.ts +4 -8
  25. package/src/transformer/transforms/array.ts +143 -0
  26. package/src/transformer/transforms/object.ts +251 -0
  27. package/src/transformer/transforms/primitives.ts +461 -0
  28. package/src/transformer/transforms/utilities.ts +119 -0
  29. package/src/types.ts +0 -5
  30. package/build/transformer/core/detector.js +0 -6
  31. package/build/transformer/core/index.d.ts +0 -10
  32. package/build/transformer/core/transforms/auto-dispose.d.ts +0 -3
  33. package/build/transformer/core/transforms/auto-dispose.js +0 -116
  34. package/build/transformer/core/transforms/reactive-array.js +0 -89
  35. package/build/transformer/core/transforms/reactive-object.js +0 -155
  36. package/build/transformer/core/transforms/reactive-primitives.js +0 -325
  37. package/build/transformer/core/transforms/utilities.js +0 -57
  38. package/src/transformer/core/detector.ts +0 -12
  39. package/src/transformer/core/transforms/auto-dispose.ts +0 -194
  40. package/src/transformer/core/transforms/reactive-array.ts +0 -140
  41. package/src/transformer/core/transforms/reactive-object.ts +0 -244
  42. package/src/transformer/core/transforms/reactive-primitives.ts +0 -459
  43. package/src/transformer/core/transforms/utilities.ts +0 -95
  44. /package/build/transformer/{core/detector.d.ts → detector.d.ts} +0 -0
@@ -1,13 +1,10 @@
1
- import { mightNeedTransform, transform } from '~/transformer/core';
1
+ import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
+ import { mightNeedTransform, transform } from '~/transformer';
2
3
  import type { Plugin } from 'vite';
3
- import type { TransformOptions } from '~/types';
4
4
  import ts from 'typescript';
5
5
 
6
6
 
7
- const TRANSFORM_PATTERN = /\.[tj]sx?$/;
8
-
9
-
10
- export default (options?: TransformOptions): Plugin => {
7
+ export default (): Plugin => {
11
8
  return {
12
9
  enforce: 'pre',
13
10
  name: '@esportsplus/reactivity/plugin-vite',
@@ -23,7 +20,7 @@ export default (options?: TransformOptions): Plugin => {
23
20
 
24
21
  try {
25
22
  let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true),
26
- result = transform(sourceFile, options);
23
+ result = transform(sourceFile);
27
24
 
28
25
  if (!result.transformed) {
29
26
  return null;
@@ -38,4 +35,3 @@ export default (options?: TransformOptions): Plugin => {
38
35
  }
39
36
  };
40
37
  };
41
- export type { TransformOptions as PluginOptions };
@@ -0,0 +1,143 @@
1
+ import type { Bindings } from '~/types';
2
+ import { applyReplacements, Replacement } from './utilities';
3
+ import ts from 'typescript';
4
+
5
+
6
+ interface TransformContext {
7
+ bindings: Bindings;
8
+ replacements: Replacement[];
9
+ sourceFile: ts.SourceFile;
10
+ }
11
+
12
+
13
+ function getExpressionName(node: ts.Expression): string | null {
14
+ if (ts.isIdentifier(node)) {
15
+ return node.text;
16
+ }
17
+
18
+ if (ts.isPropertyAccessExpression(node)) {
19
+ return getPropertyPath(node);
20
+ }
21
+
22
+ return null;
23
+ }
24
+
25
+ function getPropertyPath(node: ts.PropertyAccessExpression): string | null {
26
+ let current: ts.Node = node,
27
+ parts: string[] = [];
28
+
29
+ while (ts.isPropertyAccessExpression(current)) {
30
+ parts.unshift(current.name.text);
31
+ current = current.expression;
32
+ }
33
+
34
+ if (ts.isIdentifier(current)) {
35
+ parts.unshift(current.text);
36
+ return parts.join('.');
37
+ }
38
+
39
+ return null;
40
+ }
41
+
42
+ function isAssignmentTarget(node: ts.Node): boolean {
43
+ let parent = node.parent;
44
+
45
+ if (
46
+ (ts.isBinaryExpression(parent) && parent.left === node) ||
47
+ ts.isPostfixUnaryExpression(parent) ||
48
+ ts.isPrefixUnaryExpression(parent)
49
+ ) {
50
+ return true;
51
+ }
52
+
53
+ return false;
54
+ }
55
+
56
+ function visit(ctx: TransformContext, node: ts.Node): void {
57
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
58
+ if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === 'array') {
59
+ ctx.bindings.set(node.name.text, 'array');
60
+ }
61
+
62
+ if (ts.isPropertyAccessExpression(node.initializer)) {
63
+ let path = getPropertyPath(node.initializer);
64
+
65
+ if (path && ctx.bindings.get(path) === 'array') {
66
+ ctx.bindings.set(node.name.text, 'array');
67
+ }
68
+ }
69
+ }
70
+
71
+ if ((ts.isFunctionDeclaration(node) || ts.isArrowFunction(node)) && node.parameters) {
72
+ for (let i = 0, n = node.parameters.length; i < n; i++) {
73
+ let param = node.parameters[i];
74
+
75
+ if (
76
+ (ts.isIdentifier(param.name) && param.type) &&
77
+ ts.isTypeReferenceNode(param.type) &&
78
+ ts.isIdentifier(param.type.typeName) &&
79
+ param.type.typeName.text === 'ReactiveArray'
80
+ ) {
81
+ ctx.bindings.set(param.name.text, 'array');
82
+ }
83
+ }
84
+ }
85
+
86
+ if (
87
+ ts.isPropertyAccessExpression(node) &&
88
+ node.name.text === 'length' &&
89
+ !isAssignmentTarget(node)
90
+ ) {
91
+ let name = getExpressionName(node.expression);
92
+
93
+ if (name && ctx.bindings.get(name) === 'array') {
94
+ let objText = node.expression.getText(ctx.sourceFile);
95
+
96
+ ctx.replacements.push({
97
+ end: node.end,
98
+ newText: `${objText}.$length()`,
99
+ start: node.pos
100
+ });
101
+ }
102
+ }
103
+
104
+ if (
105
+ ts.isBinaryExpression(node) &&
106
+ node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
107
+ ts.isElementAccessExpression(node.left)
108
+ ) {
109
+ let elemAccess = node.left,
110
+ objName = getExpressionName(elemAccess.expression);
111
+
112
+ if (objName && ctx.bindings.get(objName) === 'array') {
113
+ let indexText = elemAccess.argumentExpression.getText(ctx.sourceFile),
114
+ objText = elemAccess.expression.getText(ctx.sourceFile),
115
+ valueText = node.right.getText(ctx.sourceFile);
116
+
117
+ ctx.replacements.push({
118
+ end: node.end,
119
+ newText: `${objText}.$set(${indexText}, ${valueText})`,
120
+ start: node.pos
121
+ });
122
+ }
123
+ }
124
+
125
+ ts.forEachChild(node, n => visit(ctx, n));
126
+ }
127
+
128
+
129
+ const transformReactiveArrays = (sourceFile: ts.SourceFile, bindings: Bindings): string => {
130
+ let code = sourceFile.getFullText(),
131
+ ctx: TransformContext = {
132
+ bindings,
133
+ replacements: [],
134
+ sourceFile
135
+ };
136
+
137
+ visit(ctx, sourceFile);
138
+
139
+ return applyReplacements(code, ctx.replacements);
140
+ };
141
+
142
+
143
+ export { transformReactiveArrays };
@@ -0,0 +1,251 @@
1
+ import { uid } from '@esportsplus/typescript/transformer';
2
+ import type { Bindings } from '~/types';
3
+ import { addMissingImports, applyReplacements, ExtraImport, Replacement } from './utilities';
4
+ import ts from 'typescript';
5
+
6
+
7
+ const CLASS_NAME_REGEX = /class (\w+)/;
8
+
9
+ const EXTRA_IMPORTS: ExtraImport[] = [
10
+ { module: '@esportsplus/reactivity/constants', specifier: 'REACTIVE_OBJECT' },
11
+ { module: '@esportsplus/reactivity/reactive/array', specifier: 'ReactiveArray' }
12
+ ];
13
+
14
+
15
+ interface AnalyzedProperty {
16
+ key: string;
17
+ type: 'array' | 'computed' | 'signal';
18
+ valueText: string;
19
+ }
20
+
21
+ interface ReactiveObjectCall {
22
+ end: number;
23
+ generatedClass: string;
24
+ needsImports: Set<string>;
25
+ start: number;
26
+ varName: string | null;
27
+ }
28
+
29
+ interface TransformContext {
30
+ allNeededImports: Set<string>;
31
+ bindings: Bindings;
32
+ calls: ReactiveObjectCall[];
33
+ hasReactiveImport: boolean;
34
+ lastImportEnd: number;
35
+ sourceFile: ts.SourceFile;
36
+ }
37
+
38
+
39
+ function analyzeProperty(prop: ts.ObjectLiteralElementLike, sourceFile: ts.SourceFile): AnalyzedProperty | null {
40
+ if (!ts.isPropertyAssignment(prop)) {
41
+ return null;
42
+ }
43
+
44
+ let key: string;
45
+
46
+ if (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) {
47
+ key = prop.name.text;
48
+ }
49
+ else {
50
+ return null;
51
+ }
52
+
53
+ let value = prop.initializer,
54
+ valueText = value.getText(sourceFile);
55
+
56
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
57
+ return { key, type: 'computed', valueText };
58
+ }
59
+
60
+ if (ts.isArrayLiteralExpression(value)) {
61
+ return { key, type: 'array', valueText };
62
+ }
63
+
64
+ return { key, type: 'signal', valueText };
65
+ }
66
+
67
+ function buildClassCode(className: string, properties: AnalyzedProperty[]): string {
68
+ let accessors: string[] = [],
69
+ disposeStatements: string[] = [],
70
+ fields: string[] = [];
71
+
72
+ fields.push(`[REACTIVE_OBJECT] = true;`);
73
+
74
+ for (let i = 0, n = properties.length; i < n; i++) {
75
+ let { key, type, valueText } = properties[i];
76
+
77
+ if (type === 'signal') {
78
+ let param = uid('v');
79
+
80
+ fields.push(`#${key} = signal(${valueText});`);
81
+ accessors.push(`get ${key}() { return read(this.#${key}); }`);
82
+ accessors.push(`set ${key}(${param}) { set(this.#${key}, ${param}); }`);
83
+ }
84
+ else if (type === 'array') {
85
+ let elements = valueText.slice(1, -1);
86
+
87
+ fields.push(`${key} = new ReactiveArray(${elements});`);
88
+ disposeStatements.push(`this.${key}.dispose();`);
89
+ }
90
+ else if (type === 'computed') {
91
+ fields.push(`#${key}: Computed<unknown> | null = null;`);
92
+ accessors.push(`get ${key}() { return read(this.#${key} ??= computed(${valueText})); }`);
93
+ disposeStatements.push(`if (this.#${key}) dispose(this.#${key});`);
94
+ }
95
+ }
96
+
97
+ return `
98
+ class ${className} {
99
+ ${fields.join('\n')}
100
+ ${accessors.join('\n')}
101
+
102
+ dispose() {
103
+ ${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
104
+ }
105
+ }
106
+ `;
107
+ }
108
+
109
+ function visit(ctx: TransformContext, node: ts.Node): void {
110
+ if (ts.isImportDeclaration(node)) {
111
+ ctx.lastImportEnd = node.end;
112
+
113
+ if (
114
+ ts.isStringLiteral(node.moduleSpecifier) &&
115
+ node.moduleSpecifier.text.includes('@esportsplus/reactivity')
116
+ ) {
117
+ let clause = node.importClause;
118
+
119
+ if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
120
+ let elements = clause.namedBindings.elements;
121
+
122
+ for (let i = 0, n = elements.length; i < n; i++) {
123
+ if (elements[i].name.text === 'reactive') {
124
+ ctx.hasReactiveImport = true;
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ if (
133
+ ctx.hasReactiveImport &&
134
+ ts.isCallExpression(node) &&
135
+ ts.isIdentifier(node.expression) &&
136
+ node.expression.text === 'reactive'
137
+ ) {
138
+ let arg = node.arguments[0];
139
+
140
+ if (arg && ts.isObjectLiteralExpression(arg)) {
141
+ let varName: string | null = null;
142
+
143
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
144
+ varName = node.parent.name.text;
145
+ ctx.bindings.set(varName, 'object');
146
+ }
147
+
148
+ let needsImports = new Set<string>(),
149
+ properties: AnalyzedProperty[] = [];
150
+
151
+ needsImports.add('REACTIVE_OBJECT');
152
+
153
+ let props = arg.properties;
154
+
155
+ for (let i = 0, n = props.length; i < n; i++) {
156
+ let prop = props[i];
157
+
158
+ if (ts.isSpreadAssignment(prop)) {
159
+ ts.forEachChild(node, n => visit(ctx, n));
160
+ return;
161
+ }
162
+
163
+ let analyzed = analyzeProperty(prop, ctx.sourceFile);
164
+
165
+ if (!analyzed) {
166
+ ts.forEachChild(node, n => visit(ctx, n));
167
+ return;
168
+ }
169
+
170
+ properties.push(analyzed);
171
+
172
+ if (analyzed.type === 'signal') {
173
+ needsImports.add('read');
174
+ needsImports.add('set');
175
+ needsImports.add('signal');
176
+ }
177
+ else if (analyzed.type === 'array') {
178
+ needsImports.add('ReactiveArray');
179
+
180
+ if (varName) {
181
+ ctx.bindings.set(`${varName}.${analyzed.key}`, 'array');
182
+ }
183
+ }
184
+ else if (analyzed.type === 'computed') {
185
+ needsImports.add('computed');
186
+ needsImports.add('dispose');
187
+ needsImports.add('read');
188
+ }
189
+ }
190
+
191
+ needsImports.forEach(imp => ctx.allNeededImports.add(imp));
192
+
193
+ ctx.calls.push({
194
+ end: node.end,
195
+ generatedClass: buildClassCode(uid('ReactiveObject'), properties),
196
+ needsImports,
197
+ start: node.pos,
198
+ varName
199
+ });
200
+ }
201
+ }
202
+
203
+ ts.forEachChild(node, n => visit(ctx, n));
204
+ }
205
+
206
+
207
+ const transformReactiveObjects = (sourceFile: ts.SourceFile, bindings: Bindings): string => {
208
+ let code = sourceFile.getFullText(),
209
+ ctx: TransformContext = {
210
+ allNeededImports: new Set<string>(),
211
+ bindings,
212
+ calls: [],
213
+ hasReactiveImport: false,
214
+ lastImportEnd: 0,
215
+ sourceFile
216
+ };
217
+
218
+ visit(ctx, sourceFile);
219
+
220
+ if (ctx.calls.length === 0) {
221
+ return code;
222
+ }
223
+
224
+ let replacements: Replacement[] = [];
225
+
226
+ replacements.push({
227
+ end: ctx.lastImportEnd,
228
+ newText: code.substring(0, ctx.lastImportEnd) + '\n' + ctx.calls.map(c => c.generatedClass).join('\n') + '\n',
229
+ start: 0
230
+ });
231
+
232
+ for (let i = 0, n = ctx.calls.length; i < n; i++) {
233
+ let call = ctx.calls[i],
234
+ classMatch = call.generatedClass.match(CLASS_NAME_REGEX);
235
+
236
+ replacements.push({
237
+ end: call.end,
238
+ newText: ` new ${classMatch ? classMatch[1] : 'ReactiveObject'}()`,
239
+ start: call.start
240
+ });
241
+ }
242
+
243
+ return addMissingImports(
244
+ applyReplacements(code, replacements),
245
+ ctx.allNeededImports,
246
+ EXTRA_IMPORTS
247
+ );
248
+ };
249
+
250
+
251
+ export { transformReactiveObjects };