@esportsplus/reactivity 0.24.0 → 0.24.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.
- package/build/transformer/factory.d.ts +13 -0
- package/build/transformer/factory.js +36 -0
- package/build/transformer/index.d.ts +4 -6
- package/build/transformer/index.js +97 -39
- package/build/transformer/plugins/vite.js +7 -4
- package/build/transformer/transforms/array.d.ts +2 -2
- package/build/transformer/transforms/array.js +20 -22
- package/build/transformer/transforms/object.d.ts +8 -2
- package/build/transformer/transforms/object.js +87 -99
- package/build/transformer/transforms/primitives.d.ts +2 -2
- package/build/transformer/transforms/primitives.js +130 -184
- package/build/types.d.ts +1 -7
- package/package.json +1 -1
- package/readme.md +1 -17
- package/src/transformer/factory.ts +139 -0
- package/src/transformer/index.ts +171 -45
- package/src/transformer/plugins/vite.ts +13 -5
- package/src/transformer/transforms/array.ts +35 -31
- package/src/transformer/transforms/object.ts +286 -136
- package/src/transformer/transforms/primitives.ts +222 -235
- package/src/types.ts +1 -9
- package/test/vite.config.ts +1 -1
- package/build/transformer/transforms/utilities.d.ts +0 -8
- package/build/transformer/transforms/utilities.js +0 -27
- package/src/transformer/transforms/utilities.ts +0 -45
package/src/transformer/index.ts
CHANGED
|
@@ -1,67 +1,193 @@
|
|
|
1
|
-
import type { Bindings
|
|
1
|
+
import type { Bindings } from '~/types';
|
|
2
|
+
import { createArrayTransformer } from './transforms/array';
|
|
3
|
+
import { createObjectTransformer, type GeneratedClass } from './transforms/object';
|
|
4
|
+
import { createPrimitivesTransformer } from './transforms/primitives';
|
|
2
5
|
import { mightNeedTransform } from './detector';
|
|
3
|
-
import { transformReactiveArrays } from './transforms/array';
|
|
4
|
-
import { transformReactiveObjects } from './transforms/object';
|
|
5
|
-
import { transformReactivePrimitives } from './transforms/primitives';
|
|
6
6
|
import { ts } from '@esportsplus/typescript';
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
interface ExtraImport {
|
|
10
|
+
module: string;
|
|
11
|
+
specifier: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const EXTRA_IMPORTS: ExtraImport[] = [
|
|
15
|
+
{ module: '@esportsplus/reactivity/constants', specifier: 'REACTIVE_OBJECT' },
|
|
16
|
+
{ module: '@esportsplus/reactivity/reactive/array', specifier: 'ReactiveArray' }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
function addImportsTransformer(
|
|
21
|
+
neededImports: Set<string>,
|
|
22
|
+
extraImports: ExtraImport[]
|
|
23
|
+
): (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
|
|
24
|
+
return (context: ts.TransformationContext) => {
|
|
11
25
|
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
12
|
-
|
|
26
|
+
if (neededImports.size === 0) {
|
|
27
|
+
return sourceFile;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let extraSpecifiers = new Set<string>(),
|
|
31
|
+
factory = context.factory,
|
|
32
|
+
newStatements: ts.Statement[] = [],
|
|
33
|
+
reactivitySpecifiers: string[] = [];
|
|
34
|
+
|
|
35
|
+
for (let i = 0, n = extraImports.length; i < n; i++) {
|
|
36
|
+
extraSpecifiers.add(extraImports[i].specifier);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (let imp of neededImports) {
|
|
40
|
+
if (!extraSpecifiers.has(imp)) {
|
|
41
|
+
reactivitySpecifiers.push(imp);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Add @esportsplus/reactivity imports
|
|
46
|
+
if (reactivitySpecifiers.length > 0) {
|
|
47
|
+
newStatements.push(
|
|
48
|
+
factory.createImportDeclaration(
|
|
49
|
+
undefined,
|
|
50
|
+
factory.createImportClause(
|
|
51
|
+
false,
|
|
52
|
+
undefined,
|
|
53
|
+
factory.createNamedImports(
|
|
54
|
+
reactivitySpecifiers.map(s =>
|
|
55
|
+
factory.createImportSpecifier(false, undefined, factory.createIdentifier(s))
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
),
|
|
59
|
+
factory.createStringLiteral('@esportsplus/reactivity')
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
13
63
|
|
|
14
|
-
|
|
64
|
+
// Add extra imports (REACTIVE_OBJECT, ReactiveArray)
|
|
65
|
+
for (let i = 0, n = extraImports.length; i < n; i++) {
|
|
66
|
+
let extra = extraImports[i];
|
|
67
|
+
|
|
68
|
+
if (neededImports.has(extra.specifier)) {
|
|
69
|
+
newStatements.push(
|
|
70
|
+
factory.createImportDeclaration(
|
|
71
|
+
undefined,
|
|
72
|
+
factory.createImportClause(
|
|
73
|
+
false,
|
|
74
|
+
undefined,
|
|
75
|
+
factory.createNamedImports([
|
|
76
|
+
factory.createImportSpecifier(false, undefined, factory.createIdentifier(extra.specifier))
|
|
77
|
+
])
|
|
78
|
+
),
|
|
79
|
+
factory.createStringLiteral(extra.module)
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Insert new imports after existing imports
|
|
86
|
+
let insertIndex = 0,
|
|
87
|
+
statements = sourceFile.statements;
|
|
88
|
+
|
|
89
|
+
for (let i = 0, n = statements.length; i < n; i++) {
|
|
90
|
+
if (ts.isImportDeclaration(statements[i])) {
|
|
91
|
+
insertIndex = i + 1;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let updatedStatements = [
|
|
99
|
+
...statements.slice(0, insertIndex),
|
|
100
|
+
...newStatements,
|
|
101
|
+
...statements.slice(insertIndex)
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
return factory.updateSourceFile(sourceFile, updatedStatements);
|
|
15
105
|
};
|
|
16
106
|
};
|
|
17
|
-
}
|
|
107
|
+
}
|
|
18
108
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
109
|
+
function insertClassesTransformer(
|
|
110
|
+
generatedClasses: GeneratedClass[]
|
|
111
|
+
): (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
|
|
112
|
+
return (context: ts.TransformationContext) => {
|
|
113
|
+
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
114
|
+
if (generatedClasses.length === 0) {
|
|
115
|
+
return sourceFile;
|
|
116
|
+
}
|
|
25
117
|
|
|
26
|
-
|
|
27
|
-
return { code, sourceFile, transformed: false };
|
|
28
|
-
}
|
|
118
|
+
let factory = context.factory;
|
|
29
119
|
|
|
30
|
-
|
|
31
|
-
|
|
120
|
+
// Find position after imports
|
|
121
|
+
let insertIndex = 0,
|
|
122
|
+
statements = sourceFile.statements;
|
|
32
123
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
124
|
+
for (let i = 0, n = statements.length; i < n; i++) {
|
|
125
|
+
if (ts.isImportDeclaration(statements[i])) {
|
|
126
|
+
insertIndex = i + 1;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
37
132
|
|
|
38
|
-
|
|
133
|
+
let classDecls = generatedClasses.map(gc => gc.classDecl),
|
|
134
|
+
updatedStatements = [
|
|
135
|
+
...statements.slice(0, insertIndex),
|
|
136
|
+
...classDecls,
|
|
137
|
+
...statements.slice(insertIndex)
|
|
138
|
+
];
|
|
39
139
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
140
|
+
return factory.updateSourceFile(sourceFile, updatedStatements);
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
const createTransformer = (): ts.TransformerFactory<ts.SourceFile> => {
|
|
147
|
+
return (context: ts.TransformationContext) => {
|
|
148
|
+
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
149
|
+
let code = sourceFile.getFullText();
|
|
150
|
+
|
|
151
|
+
if (!mightNeedTransform(code)) {
|
|
152
|
+
return sourceFile;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let bindings: Bindings = new Map(),
|
|
156
|
+
generatedClasses: GeneratedClass[] = [],
|
|
157
|
+
neededImports = new Set<string>();
|
|
158
|
+
|
|
159
|
+
// Run object transformer first (generates classes, tracks array bindings)
|
|
160
|
+
let objectTransformer = createObjectTransformer(bindings, neededImports, generatedClasses)(context);
|
|
44
161
|
|
|
45
|
-
|
|
162
|
+
sourceFile = objectTransformer(sourceFile);
|
|
46
163
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
code = result;
|
|
50
|
-
}
|
|
164
|
+
// Run array transformer (handles array.length, array[i] = v)
|
|
165
|
+
let arrayTransformer = createArrayTransformer(bindings)(context);
|
|
51
166
|
|
|
52
|
-
|
|
53
|
-
return { code, sourceFile, transformed: false };
|
|
54
|
-
}
|
|
167
|
+
sourceFile = arrayTransformer(sourceFile);
|
|
55
168
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
169
|
+
// Run primitives transformer (handles signal/computed, reads/writes)
|
|
170
|
+
let primitivesTransformer = createPrimitivesTransformer(bindings, neededImports)(context);
|
|
171
|
+
|
|
172
|
+
sourceFile = primitivesTransformer(sourceFile);
|
|
173
|
+
|
|
174
|
+
// Insert generated classes after imports
|
|
175
|
+
let classInserter = insertClassesTransformer(generatedClasses)(context);
|
|
176
|
+
|
|
177
|
+
sourceFile = classInserter(sourceFile);
|
|
178
|
+
|
|
179
|
+
// Add missing imports
|
|
180
|
+
let importAdder = addImportsTransformer(neededImports, EXTRA_IMPORTS)(context);
|
|
181
|
+
|
|
182
|
+
sourceFile = importAdder(sourceFile);
|
|
183
|
+
|
|
184
|
+
return sourceFile;
|
|
185
|
+
};
|
|
60
186
|
};
|
|
61
187
|
};
|
|
62
188
|
|
|
63
189
|
|
|
64
|
-
export { createTransformer, mightNeedTransform
|
|
65
|
-
export {
|
|
66
|
-
export {
|
|
67
|
-
export {
|
|
190
|
+
export { createTransformer, mightNeedTransform };
|
|
191
|
+
export { createArrayTransformer } from './transforms/array';
|
|
192
|
+
export { createObjectTransformer } from './transforms/object';
|
|
193
|
+
export { createPrimitivesTransformer } from './transforms/primitives';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import {
|
|
2
|
+
import { createTransformer, mightNeedTransform } from '~/transformer';
|
|
3
3
|
import type { Plugin } from 'vite';
|
|
4
4
|
import { ts } from '@esportsplus/typescript';
|
|
5
5
|
|
|
@@ -19,14 +19,22 @@ export default (): Plugin => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
|
-
let
|
|
23
|
-
|
|
22
|
+
let printer = ts.createPrinter(),
|
|
23
|
+
sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true),
|
|
24
|
+
transformer = createTransformer(),
|
|
25
|
+
result = ts.transform(sourceFile, [transformer]),
|
|
26
|
+
transformed = result.transformed[0];
|
|
24
27
|
|
|
25
|
-
if (
|
|
28
|
+
if (transformed === sourceFile) {
|
|
29
|
+
result.dispose();
|
|
26
30
|
return null;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
let output = printer.printFile(transformed);
|
|
34
|
+
|
|
35
|
+
result.dispose();
|
|
36
|
+
|
|
37
|
+
return { code: output, map: null };
|
|
30
38
|
}
|
|
31
39
|
catch (error) {
|
|
32
40
|
console.error(`@esportsplus/reactivity: Error transforming ${id}:`, error);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Bindings } from '~/types';
|
|
2
|
-
import {
|
|
2
|
+
import { createArrayLengthCall, createArraySetCall } from '../factory';
|
|
3
3
|
import { ts } from '@esportsplus/typescript';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
interface TransformContext {
|
|
7
7
|
bindings: Bindings;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
context: ts.TransformationContext;
|
|
9
|
+
factory: ts.NodeFactory;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
|
|
@@ -42,6 +42,10 @@ function getPropertyPath(node: ts.PropertyAccessExpression): string | null {
|
|
|
42
42
|
function isAssignmentTarget(node: ts.Node): boolean {
|
|
43
43
|
let parent = node.parent;
|
|
44
44
|
|
|
45
|
+
if (!parent) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
if (
|
|
46
50
|
(ts.isBinaryExpression(parent) && parent.left === node) ||
|
|
47
51
|
ts.isPostfixUnaryExpression(parent) ||
|
|
@@ -53,7 +57,8 @@ function isAssignmentTarget(node: ts.Node): boolean {
|
|
|
53
57
|
return false;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
function visit(ctx: TransformContext, node: ts.Node):
|
|
60
|
+
function visit(ctx: TransformContext, node: ts.Node): ts.Node {
|
|
61
|
+
// Track array bindings from variable declarations
|
|
57
62
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
58
63
|
if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === 'array') {
|
|
59
64
|
ctx.bindings.set(node.name.text, 'array');
|
|
@@ -68,6 +73,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
// Track array bindings from function parameters with ReactiveArray type
|
|
71
77
|
if ((ts.isFunctionDeclaration(node) || ts.isArrowFunction(node)) && node.parameters) {
|
|
72
78
|
for (let i = 0, n = node.parameters.length; i < n; i++) {
|
|
73
79
|
let param = node.parameters[i];
|
|
@@ -83,6 +89,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
// Transform array.length → array.$length()
|
|
86
93
|
if (
|
|
87
94
|
ts.isPropertyAccessExpression(node) &&
|
|
88
95
|
node.name.text === 'length' &&
|
|
@@ -91,16 +98,14 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
91
98
|
let name = getExpressionName(node.expression);
|
|
92
99
|
|
|
93
100
|
if (name && ctx.bindings.get(name) === 'array') {
|
|
94
|
-
|
|
101
|
+
// First visit children to transform the expression if needed
|
|
102
|
+
let transformedExpr = ts.visitEachChild(node.expression, n => visit(ctx, n), ctx.context) as ts.Expression;
|
|
95
103
|
|
|
96
|
-
ctx.
|
|
97
|
-
end: node.end,
|
|
98
|
-
newText: `${objText}.$length()`,
|
|
99
|
-
start: node.pos
|
|
100
|
-
});
|
|
104
|
+
return createArrayLengthCall(ctx.factory, transformedExpr);
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
// Transform array[index] = value → array.$set(index, value)
|
|
104
109
|
if (
|
|
105
110
|
ts.isBinaryExpression(node) &&
|
|
106
111
|
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
@@ -110,34 +115,33 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
110
115
|
objName = getExpressionName(elemAccess.expression);
|
|
111
116
|
|
|
112
117
|
if (objName && ctx.bindings.get(objName) === 'array') {
|
|
113
|
-
let
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
ctx.
|
|
118
|
-
end: node.end,
|
|
119
|
-
newText: `${objText}.$set(${indexText}, ${valueText})`,
|
|
120
|
-
start: node.pos
|
|
121
|
-
});
|
|
118
|
+
let transformedArray = ts.visitEachChild(elemAccess.expression, n => visit(ctx, n), ctx.context) as ts.Expression,
|
|
119
|
+
transformedIndex = ts.visitEachChild(elemAccess.argumentExpression, n => visit(ctx, n), ctx.context) as ts.Expression,
|
|
120
|
+
transformedValue = ts.visitEachChild(node.right, n => visit(ctx, n), ctx.context) as ts.Expression;
|
|
121
|
+
|
|
122
|
+
return createArraySetCall(ctx.factory, transformedArray, transformedIndex, transformedValue);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
ts.
|
|
126
|
+
return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
const createArrayTransformer = (
|
|
131
|
+
bindings: Bindings
|
|
132
|
+
): (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile => {
|
|
133
|
+
return (context: ts.TransformationContext) => {
|
|
134
|
+
return (sourceFile: ts.SourceFile): ts.SourceFile => {
|
|
135
|
+
let ctx: TransformContext = {
|
|
136
|
+
bindings,
|
|
137
|
+
context,
|
|
138
|
+
factory: context.factory
|
|
139
|
+
};
|
|
138
140
|
|
|
139
|
-
|
|
141
|
+
return ts.visitNode(sourceFile, n => visit(ctx, n)) as ts.SourceFile;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
140
144
|
};
|
|
141
145
|
|
|
142
146
|
|
|
143
|
-
export {
|
|
147
|
+
export { createArrayTransformer };
|