@esportsplus/reactivity 0.33.0 → 0.36.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.
- package/README.md +9 -7
- package/bench/reactive/array.bench.ts +17 -17
- package/build/compiler/array.d.ts +2 -2
- package/build/compiler/array.js +12 -54
- package/build/compiler/constants.d.ts +4 -4
- package/build/compiler/constants.js +20 -4
- package/build/compiler/index.js +25 -38
- package/build/compiler/object.d.ts +2 -2
- package/build/compiler/object.js +10 -21
- package/build/compiler/plugins/vite.d.ts +4 -2
- package/build/compiler/primitives.d.ts +6 -2
- package/build/compiler/primitives.js +35 -42
- package/build/compiler/types.d.ts +3 -1
- package/build/constants.d.ts +2 -2
- package/build/constants.js +2 -2
- package/build/reactive/array.d.ts +1 -1
- package/build/reactive/array.js +26 -15
- package/build/reactive/index.js +5 -14
- package/build/reactive/object.js +1 -1
- package/build/system.d.ts +10 -13
- package/build/system.js +105 -83
- package/build/types.d.ts +2 -7
- package/package.json +5 -5
- package/pnpm-workspace.yaml +3 -0
- package/src/compiler/array.ts +14 -64
- package/src/compiler/constants.ts +21 -5
- package/src/compiler/index.ts +39 -70
- package/src/compiler/object.ts +12 -29
- package/src/compiler/primitives.ts +55 -53
- package/src/compiler/types.ts +4 -1
- package/src/constants.ts +4 -4
- package/src/reactive/array.ts +33 -17
- package/src/reactive/index.ts +5 -17
- package/src/reactive/object.ts +1 -1
- package/src/system.ts +138 -101
- package/src/types.ts +2 -9
- package/test/async-computed.test.ts +2 -2
- package/test/compiler/compiler.test.ts +40 -23
- package/test/effects.test.ts +37 -1
- package/test/lib/uncaught.ts +26 -0
- package/test/reactive/array.test.ts +169 -99
- package/test/reactive/nested.test.ts +14 -14
- package/test/reactive/objects.test.ts +1 -1
- package/test/reactive/reactive.test.ts +49 -0
- package/test/system.test.ts +130 -22
package/src/compiler/index.ts
CHANGED
|
@@ -9,27 +9,7 @@ import object from './object';
|
|
|
9
9
|
import primitives from './primitives';
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
checker: ts.TypeChecker;
|
|
14
|
-
replacements: ReplacementIntent[];
|
|
15
|
-
sourceFile: ts.SourceFile;
|
|
16
|
-
transformedNodes: Set<ts.Node>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function findRemainingCalls(
|
|
21
|
-
checker: ts.TypeChecker,
|
|
22
|
-
sourceFile: ts.SourceFile,
|
|
23
|
-
transformedNodes: Set<ts.Node>
|
|
24
|
-
): ReplacementIntent[] {
|
|
25
|
-
let ctx: FindRemainingContext = { checker, replacements: [], sourceFile, transformedNodes };
|
|
26
|
-
|
|
27
|
-
visit(ctx, sourceFile);
|
|
28
|
-
|
|
29
|
-
return ctx.replacements;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isReactiveCallExpression(checker: ts.TypeChecker, node: ts.Node): node is ts.CallExpression {
|
|
12
|
+
function isReactiveCallExpression(checker: ts.Checker, node: ts.Node): node is ts.CallExpression {
|
|
33
13
|
if (!ts.isCallExpression(node)) {
|
|
34
14
|
return false;
|
|
35
15
|
}
|
|
@@ -49,38 +29,13 @@ function isReactiveCallExpression(checker: ts.TypeChecker, node: ts.Node): node
|
|
|
49
29
|
return false;
|
|
50
30
|
}
|
|
51
31
|
|
|
52
|
-
function hasReactiveCalls(checker: ts.TypeChecker, node: ts.Node): boolean {
|
|
53
|
-
if (isReactiveCallExpression(checker, node)) {
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let found = false;
|
|
58
|
-
|
|
59
|
-
ts.forEachChild(node, child => {
|
|
60
|
-
if (!found && hasReactiveCalls(checker, child)) {
|
|
61
|
-
found = true;
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return found;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function visit(ctx: FindRemainingContext, node: ts.Node): void {
|
|
69
|
-
if (isReactiveCallExpression(ctx.checker, node) && !ctx.transformedNodes.has(node) && !ctx.transformedNodes.has(node.expression)) {
|
|
70
|
-
ctx.replacements.push({
|
|
71
|
-
generate: () => `${NAMESPACE}.reactive(${node.arguments.map(a => a.getText(ctx.sourceFile)).join(', ')})`,
|
|
72
|
-
node
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
ts.forEachChild(node, n => visit(ctx, n));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
32
|
|
|
80
33
|
export default {
|
|
81
34
|
patterns: ['reactive(', 'reactive<'],
|
|
82
35
|
transform: (ctx: TransformContext) => {
|
|
83
|
-
|
|
36
|
+
let checker = ctx.checker;
|
|
37
|
+
|
|
38
|
+
if (!checker) {
|
|
84
39
|
return {};
|
|
85
40
|
}
|
|
86
41
|
|
|
@@ -89,36 +44,50 @@ export default {
|
|
|
89
44
|
imports: [] as ImportIntent[],
|
|
90
45
|
prepend: [] as string[],
|
|
91
46
|
replacements: [] as ReplacementIntent[]
|
|
92
|
-
}
|
|
47
|
+
},
|
|
48
|
+
isReactiveCall = (node: ts.Node): node is ts.CallExpression => isReactiveCallExpression(checker, node),
|
|
49
|
+
sourceFile = ctx.sourceFile;
|
|
93
50
|
|
|
94
|
-
// Run primitives transform first (tracks bindings for signal/computed)
|
|
95
|
-
|
|
96
|
-
...primitives(ctx.sourceFile, bindings, (node: ts.Node) => isReactiveCallExpression(ctx.checker!, node))
|
|
97
|
-
);
|
|
51
|
+
// Run primitives transform first (tracks bindings for signal/computed, collects every call)
|
|
52
|
+
let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
98
53
|
|
|
99
|
-
|
|
100
|
-
|
|
54
|
+
if (calls.length === 0) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
101
57
|
|
|
102
|
-
intents.prepend.push(...prepend);
|
|
103
58
|
intents.replacements.push(...replacements);
|
|
104
59
|
|
|
60
|
+
// Run object transform
|
|
61
|
+
let objects = object(sourceFile, bindings, isReactiveCall);
|
|
62
|
+
|
|
63
|
+
intents.prepend.push(...objects.prepend);
|
|
64
|
+
intents.replacements.push(...objects.replacements);
|
|
65
|
+
|
|
105
66
|
// Run array transform separately ( avoid race conditions )
|
|
106
|
-
intents.replacements.push(...array(
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
intents.replacements.
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
67
|
+
intents.replacements.push(...array(sourceFile, bindings, isReactiveCall));
|
|
68
|
+
|
|
69
|
+
// Calls no transform claimed fall through to the runtime reactive()
|
|
70
|
+
let transformed = new Set(intents.replacements.map(r => r.node));
|
|
71
|
+
|
|
72
|
+
for (let i = 0, n = calls.length; i < n; i++) {
|
|
73
|
+
let call = calls[i];
|
|
74
|
+
|
|
75
|
+
if (transformed.has(call) || transformed.has(call.expression)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
intents.replacements.push({
|
|
80
|
+
generate: () => `${NAMESPACE}.reactive(${call.arguments.map(a => a.getText(sourceFile)).join(', ')})`,
|
|
81
|
+
node: call
|
|
119
82
|
});
|
|
120
83
|
}
|
|
121
84
|
|
|
85
|
+
intents.imports.push({
|
|
86
|
+
namespace: NAMESPACE,
|
|
87
|
+
package: PACKAGE_NAME,
|
|
88
|
+
remove: [ENTRYPOINT]
|
|
89
|
+
});
|
|
90
|
+
|
|
122
91
|
return intents;
|
|
123
92
|
}
|
|
124
93
|
};
|
package/src/compiler/object.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { code,
|
|
2
|
+
import { code, uid } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
4
|
-
import {
|
|
5
|
-
import type { Bindings } from './types';
|
|
4
|
+
import { NAMESPACE, TYPES } from './constants';
|
|
5
|
+
import type { Bindings, IsReactiveCall } from './types';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
interface AnalyzedProperty {
|
|
@@ -28,7 +28,7 @@ interface ReactiveObjectCall {
|
|
|
28
28
|
interface VisitContext {
|
|
29
29
|
bindings: Bindings;
|
|
30
30
|
calls: ReactiveObjectCall[];
|
|
31
|
-
|
|
31
|
+
isReactiveCall: IsReactiveCall;
|
|
32
32
|
sourceFile: ts.SourceFile;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -51,11 +51,11 @@ function analyzeProperty(prop: ts.ObjectLiteralElementLike, sourceFile: ts.Sourc
|
|
|
51
51
|
value = unwrapped,
|
|
52
52
|
valueText = value.getText(sourceFile);
|
|
53
53
|
|
|
54
|
-
while (ts.isAsExpression(unwrapped) || ts.
|
|
54
|
+
while (ts.isAsExpression(unwrapped) || ts.isTypeAssertion(unwrapped) || ts.isParenthesizedExpression(unwrapped)) {
|
|
55
55
|
unwrapped = unwrapped.expression;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
if (ts.isAsExpression(value) || ts.
|
|
58
|
+
if (ts.isAsExpression(value) || ts.isTypeAssertion(value)) {
|
|
59
59
|
let type = (value as ts.AsExpression).type;
|
|
60
60
|
|
|
61
61
|
if (
|
|
@@ -216,24 +216,8 @@ function isStaticValue(node: ts.Node): boolean {
|
|
|
216
216
|
(ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand));
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function isReactiveCall(checker: ts.TypeChecker | undefined, node: ts.Node): node is ts.CallExpression {
|
|
220
|
-
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
let expr = node.expression;
|
|
225
|
-
|
|
226
|
-
// Use checker to verify symbol origin (handles re-exports)
|
|
227
|
-
if (checker) {
|
|
228
|
-
return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Fallback without checker: match by name only
|
|
232
|
-
return expr.text === ENTRYPOINT;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
219
|
function visit(ctx: VisitContext, node: ts.Node): void {
|
|
236
|
-
if (isReactiveCall(
|
|
220
|
+
if (ctx.isReactiveCall(node)) {
|
|
237
221
|
let arg = node.arguments[0];
|
|
238
222
|
|
|
239
223
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
@@ -243,21 +227,20 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
243
227
|
|
|
244
228
|
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
245
229
|
varname = node.parent.name.text;
|
|
246
|
-
ctx.bindings.set(varname, TYPES.Object);
|
|
247
230
|
}
|
|
248
231
|
|
|
249
232
|
for (let i = 0, n = props.length; i < n; i++) {
|
|
250
233
|
let prop = props[i];
|
|
251
234
|
|
|
252
235
|
if (ts.isSpreadAssignment(prop)) {
|
|
253
|
-
|
|
236
|
+
node.forEachChild(n => visit(ctx, n));
|
|
254
237
|
return;
|
|
255
238
|
}
|
|
256
239
|
|
|
257
240
|
let analyzed = analyzeProperty(prop, ctx.sourceFile);
|
|
258
241
|
|
|
259
242
|
if (!analyzed) {
|
|
260
|
-
|
|
243
|
+
node.forEachChild(n => visit(ctx, n));
|
|
261
244
|
return;
|
|
262
245
|
}
|
|
263
246
|
|
|
@@ -278,15 +261,15 @@ function visit(ctx: VisitContext, node: ts.Node): void {
|
|
|
278
261
|
}
|
|
279
262
|
}
|
|
280
263
|
|
|
281
|
-
|
|
264
|
+
node.forEachChild(n => visit(ctx, n));
|
|
282
265
|
}
|
|
283
266
|
|
|
284
267
|
|
|
285
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
268
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ObjectTransformResult => {
|
|
286
269
|
let ctx: VisitContext = {
|
|
287
270
|
bindings,
|
|
288
271
|
calls: [],
|
|
289
|
-
|
|
272
|
+
isReactiveCall,
|
|
290
273
|
sourceFile
|
|
291
274
|
};
|
|
292
275
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import { NAMESPACE, TYPES } from './constants';
|
|
4
|
-
import type { Bindings } from './types';
|
|
3
|
+
import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants';
|
|
4
|
+
import type { Bindings, IsReactiveCall } from './types';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
interface ScopeBinding {
|
|
8
|
+
depth: number;
|
|
8
9
|
name: string;
|
|
9
10
|
scope: ts.Node;
|
|
10
11
|
type: TYPES;
|
|
@@ -12,31 +13,18 @@ interface ScopeBinding {
|
|
|
12
13
|
|
|
13
14
|
interface TransformContext {
|
|
14
15
|
bindings: Bindings;
|
|
15
|
-
|
|
16
|
+
calls: ts.CallExpression[];
|
|
17
|
+
isReactiveCall: IsReactiveCall;
|
|
16
18
|
replacements: ReplacementIntent[];
|
|
17
19
|
scopedBindings: ScopeBinding[];
|
|
18
20
|
sourceFile: ts.SourceFile;
|
|
19
21
|
tmpCounter: number;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[
|
|
25
|
-
|
|
26
|
-
[ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
|
|
27
|
-
[ts.SyntaxKind.AsteriskEqualsToken, '*'],
|
|
28
|
-
[ts.SyntaxKind.BarBarEqualsToken, '||'],
|
|
29
|
-
[ts.SyntaxKind.BarEqualsToken, '|'],
|
|
30
|
-
[ts.SyntaxKind.CaretEqualsToken, '^'],
|
|
31
|
-
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
|
|
32
|
-
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
|
|
33
|
-
[ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
|
|
34
|
-
[ts.SyntaxKind.MinusEqualsToken, '-'],
|
|
35
|
-
[ts.SyntaxKind.PercentEqualsToken, '%'],
|
|
36
|
-
[ts.SyntaxKind.PlusEqualsToken, '+'],
|
|
37
|
-
[ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
|
|
38
|
-
[ts.SyntaxKind.SlashEqualsToken, '/']
|
|
39
|
-
]);
|
|
24
|
+
type PrimitivesTransformResult = {
|
|
25
|
+
calls: ts.CallExpression[];
|
|
26
|
+
replacements: ReplacementIntent[];
|
|
27
|
+
};
|
|
40
28
|
|
|
41
29
|
|
|
42
30
|
function inScope(reference: ts.Node, binding: ScopeBinding): boolean {
|
|
@@ -53,9 +41,44 @@ function inScope(reference: ts.Node, binding: ScopeBinding): boolean {
|
|
|
53
41
|
return false;
|
|
54
42
|
}
|
|
55
43
|
|
|
44
|
+
function isScope(node: ts.Node): boolean {
|
|
45
|
+
return ts.isArrowFunction(node) ||
|
|
46
|
+
ts.isBlock(node) ||
|
|
47
|
+
ts.isCatchClause(node) ||
|
|
48
|
+
ts.isForInStatement(node) ||
|
|
49
|
+
ts.isForOfStatement(node) ||
|
|
50
|
+
ts.isForStatement(node) ||
|
|
51
|
+
ts.isFunctionDeclaration(node) ||
|
|
52
|
+
ts.isFunctionExpression(node) ||
|
|
53
|
+
ts.isSourceFile(node);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Innermost enclosing scope plus its nesting depth, so shadowed names resolve to the closest binding
|
|
57
|
+
function scopeOf(node: ts.Node): { depth: number; scope: ts.Node } {
|
|
58
|
+
let current: ts.Node | undefined = node.parent,
|
|
59
|
+
depth = 0,
|
|
60
|
+
scope: ts.Node = node.getSourceFile();
|
|
61
|
+
|
|
62
|
+
while (current) {
|
|
63
|
+
if (isScope(current)) {
|
|
64
|
+
if (scope === node.getSourceFile() && !ts.isSourceFile(current)) {
|
|
65
|
+
scope = current;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
depth++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
current = current.parent;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { depth, scope };
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
function visit(ctx: TransformContext, node: ts.Node): void {
|
|
57
78
|
if (ctx.isReactiveCall(node)) {
|
|
58
|
-
let call = node
|
|
79
|
+
let call = node;
|
|
80
|
+
|
|
81
|
+
ctx.calls.push(call);
|
|
59
82
|
|
|
60
83
|
if (call.arguments.length > 0) {
|
|
61
84
|
let arg = call.arguments[0],
|
|
@@ -67,7 +90,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
67
90
|
else {
|
|
68
91
|
let unwrapped = arg;
|
|
69
92
|
|
|
70
|
-
while (ts.isAsExpression(unwrapped) || ts.isParenthesizedExpression(unwrapped) || ts.
|
|
93
|
+
while (ts.isAsExpression(unwrapped) || ts.isParenthesizedExpression(unwrapped) || ts.isTypeAssertion(unwrapped)) {
|
|
71
94
|
unwrapped = unwrapped.expression;
|
|
72
95
|
}
|
|
73
96
|
|
|
@@ -80,7 +103,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
80
103
|
generate: () => `${NAMESPACE}.reactive`,
|
|
81
104
|
node: call.expression
|
|
82
105
|
});
|
|
83
|
-
|
|
106
|
+
node.forEachChild(n => visit(ctx, n));
|
|
84
107
|
return;
|
|
85
108
|
}
|
|
86
109
|
}
|
|
@@ -101,32 +124,10 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
101
124
|
}
|
|
102
125
|
|
|
103
126
|
if (varname) {
|
|
104
|
-
let
|
|
105
|
-
scope;
|
|
106
|
-
|
|
107
|
-
while (current) {
|
|
108
|
-
if (
|
|
109
|
-
ts.isArrowFunction(current) ||
|
|
110
|
-
ts.isBlock(current) ||
|
|
111
|
-
ts.isForInStatement(current) ||
|
|
112
|
-
ts.isForOfStatement(current) ||
|
|
113
|
-
ts.isForStatement(current) ||
|
|
114
|
-
ts.isFunctionDeclaration(current) ||
|
|
115
|
-
ts.isFunctionExpression(current) ||
|
|
116
|
-
ts.isSourceFile(current)
|
|
117
|
-
) {
|
|
118
|
-
scope = current;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
current = current.parent;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!scope) {
|
|
125
|
-
scope = call.getSourceFile();
|
|
126
|
-
}
|
|
127
|
+
let { depth, scope } = scopeOf(call);
|
|
127
128
|
|
|
128
129
|
ctx.bindings.set(varname, classification);
|
|
129
|
-
ctx.scopedBindings.push({ name: varname, scope, type: classification });
|
|
130
|
+
ctx.scopedBindings.push({ depth, name: varname, scope, type: classification });
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
// Replace just the 'reactive' identifier with the appropriate namespace function
|
|
@@ -148,7 +149,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
148
149
|
!(ts.isVariableDeclaration(node.parent) && node.parent.name === node)
|
|
149
150
|
) {
|
|
150
151
|
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
151
|
-
|
|
152
|
+
node.forEachChild(n => visit(ctx, n));
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
155
|
|
|
@@ -159,7 +160,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
159
160
|
for (let i = 0, n = bindings.length; i < n; i++) {
|
|
160
161
|
let b = bindings[i];
|
|
161
162
|
|
|
162
|
-
if (b.name === name && inScope(node, b)) {
|
|
163
|
+
if (b.name === name && (!binding || b.depth >= binding.depth) && inScope(node, b)) {
|
|
163
164
|
binding = b;
|
|
164
165
|
}
|
|
165
166
|
}
|
|
@@ -251,13 +252,14 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
node.forEachChild(n => visit(ctx, n));
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
|
|
258
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall:
|
|
259
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): PrimitivesTransformResult => {
|
|
259
260
|
let ctx: TransformContext = {
|
|
260
261
|
bindings,
|
|
262
|
+
calls: [],
|
|
261
263
|
isReactiveCall,
|
|
262
264
|
replacements: [],
|
|
263
265
|
scopedBindings: [],
|
|
@@ -267,5 +269,5 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: (
|
|
|
267
269
|
|
|
268
270
|
visit(ctx, sourceFile);
|
|
269
271
|
|
|
270
|
-
return ctx.replacements;
|
|
272
|
+
return { calls: ctx.calls, replacements: ctx.replacements };
|
|
271
273
|
};
|
package/src/compiler/types.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { ts } from '@esportsplus/typescript';
|
|
1
2
|
import { TYPES } from './constants';
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
type Bindings = Map<string, TYPES>;
|
|
5
6
|
|
|
7
|
+
type IsReactiveCall = (node: ts.Node) => node is ts.CallExpression;
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
export type { Bindings, IsReactiveCall };
|
package/src/constants.ts
CHANGED
|
@@ -8,6 +8,8 @@ const REACTIVE_OBJECT = Symbol('reactivity.reactive.object');
|
|
|
8
8
|
|
|
9
9
|
const SIGNAL = Symbol('reactivity.signal');
|
|
10
10
|
|
|
11
|
+
const STABILIZER_DEFERRED = 4;
|
|
12
|
+
|
|
11
13
|
const STABILIZER_IDLE = 0;
|
|
12
14
|
|
|
13
15
|
const STABILIZER_RESCHEDULE = 1;
|
|
@@ -16,8 +18,6 @@ const STABILIZER_RUNNING = 2;
|
|
|
16
18
|
|
|
17
19
|
const STABILIZER_SCHEDULED = 3;
|
|
18
20
|
|
|
19
|
-
const STATE_NONE = 0;
|
|
20
|
-
|
|
21
21
|
const STATE_CHECK = 1 << 0;
|
|
22
22
|
|
|
23
23
|
const STATE_DIRTY = 1 << 1;
|
|
@@ -40,6 +40,6 @@ export {
|
|
|
40
40
|
PACKAGE_NAME,
|
|
41
41
|
REACTIVE_ARRAY, REACTIVE_OBJECT,
|
|
42
42
|
SIGNAL,
|
|
43
|
-
STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
|
|
44
|
-
STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP,
|
|
43
|
+
STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
|
|
44
|
+
STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING
|
|
45
45
|
};
|
package/src/reactive/array.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isArray } from '@esportsplus/utilities';
|
|
2
|
-
import { REACTIVE_ARRAY } from '~/constants';
|
|
2
|
+
import { PACKAGE_NAME, REACTIVE_ARRAY } from '~/constants';
|
|
3
3
|
import { read, signal, write } from '~/system';
|
|
4
4
|
import type { Signal } from '~/types';
|
|
5
5
|
import { isReactiveObject } from './object';
|
|
@@ -63,9 +63,16 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
63
63
|
listeners: Listeners<T> = {};
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
constructor(
|
|
67
|
-
super(
|
|
68
|
-
|
|
66
|
+
constructor(items?: readonly T[]) {
|
|
67
|
+
super();
|
|
68
|
+
|
|
69
|
+
let n = items ? items.length : 0;
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < n; i++) {
|
|
72
|
+
this[i] = items![i];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this._length = signal(n);
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
|
|
@@ -103,6 +110,7 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
this.dispatch('set', { index: i, item: value });
|
|
113
|
+
dispose(prev);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
|
|
@@ -147,7 +155,8 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
147
155
|
return;
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
let dirty = false
|
|
158
|
+
let dirty = false,
|
|
159
|
+
errors: unknown[] | null = null;
|
|
151
160
|
|
|
152
161
|
for (let i = 0, n = listeners.length; i < n; i++) {
|
|
153
162
|
let listener = listeners[i];
|
|
@@ -164,9 +173,10 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
164
173
|
listeners[i] = null;
|
|
165
174
|
}
|
|
166
175
|
}
|
|
167
|
-
catch {
|
|
176
|
+
catch (e) {
|
|
168
177
|
dirty = true;
|
|
169
178
|
listeners[i] = null;
|
|
179
|
+
(errors ??= []).push(e);
|
|
170
180
|
}
|
|
171
181
|
}
|
|
172
182
|
|
|
@@ -175,6 +185,16 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
175
185
|
listeners.pop();
|
|
176
186
|
}
|
|
177
187
|
}
|
|
188
|
+
|
|
189
|
+
if (errors !== null) {
|
|
190
|
+
let error = errors.length === 1
|
|
191
|
+
? errors[0]
|
|
192
|
+
: new AggregateError(errors, `${PACKAGE_NAME}: dispatch produced multiple errors`);
|
|
193
|
+
|
|
194
|
+
queueMicrotask(() => {
|
|
195
|
+
throw error;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
178
198
|
}
|
|
179
199
|
|
|
180
200
|
dispose() {
|
|
@@ -278,29 +298,25 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
278
298
|
|
|
279
299
|
super.sort(fn);
|
|
280
300
|
|
|
281
|
-
let buckets = new Map<T, number[]>(),
|
|
301
|
+
let buckets = new Map<T, { indices: number[]; next: number }>(),
|
|
282
302
|
order = new Array(n);
|
|
283
303
|
|
|
284
304
|
for (let i = 0; i < n; i++) {
|
|
285
305
|
let value = before[i],
|
|
286
|
-
|
|
306
|
+
bucket = buckets.get(value);
|
|
287
307
|
|
|
288
|
-
if (
|
|
289
|
-
buckets.set(value, [i]);
|
|
308
|
+
if (bucket === undefined) {
|
|
309
|
+
buckets.set(value, { indices: [i], next: 0 });
|
|
290
310
|
}
|
|
291
311
|
else {
|
|
292
|
-
|
|
312
|
+
bucket.indices.push(i);
|
|
293
313
|
}
|
|
294
314
|
}
|
|
295
315
|
|
|
296
316
|
for (let i = 0; i < n; i++) {
|
|
297
|
-
let
|
|
317
|
+
let bucket = buckets.get(this[i])!;
|
|
298
318
|
|
|
299
|
-
order[i] =
|
|
300
|
-
|
|
301
|
-
if (list.length > 1) {
|
|
302
|
-
list.pop();
|
|
303
|
-
}
|
|
319
|
+
order[i] = bucket.indices[bucket.next++];
|
|
304
320
|
}
|
|
305
321
|
|
|
306
322
|
this.dispatch('sort', { order });
|
package/src/reactive/index.ts
CHANGED
|
@@ -18,31 +18,19 @@ function reactive<T extends unknown[]>(input: T): Reactive<T>;
|
|
|
18
18
|
function reactive<T extends Record<PropertyKey, unknown>>(input: Guard<T>): Reactive<T>;
|
|
19
19
|
function reactive<T>(input: T): Reactive<T>;
|
|
20
20
|
function reactive<T>(input: T): Reactive<T> {
|
|
21
|
-
let
|
|
22
|
-
value = root(() => {
|
|
23
|
-
let response: Reactive<T> | undefined;
|
|
24
|
-
|
|
21
|
+
let value = root(() => {
|
|
25
22
|
if (isObject(input)) {
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
else if (isArray(input)) {
|
|
29
|
-
response = new ReactiveArray(...input) as unknown as Reactive<T>;
|
|
23
|
+
return new ReactiveObject(input) as unknown as Reactive<T>;
|
|
30
24
|
}
|
|
31
25
|
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
dispose = true;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return response;
|
|
26
|
+
if (isArray(input)) {
|
|
27
|
+
return new ReactiveArray(input) as unknown as Reactive<T>;
|
|
38
28
|
}
|
|
39
29
|
|
|
40
30
|
throw new Error(`${PACKAGE_NAME}: 'reactive' received invalid input - ${JSON.stringify(input)}`);
|
|
41
31
|
});
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
onCleanup(() => (value as unknown as { dispose: VoidFunction }).dispose());
|
|
45
|
-
}
|
|
33
|
+
onCleanup(() => (value as unknown as { dispose: VoidFunction }).dispose());
|
|
46
34
|
|
|
47
35
|
return value;
|
|
48
36
|
}
|
package/src/reactive/object.ts
CHANGED
|
@@ -67,7 +67,7 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U> {
|
|
70
|
-
let node = new ReactiveArray(
|
|
70
|
+
let node = new ReactiveArray(value);
|
|
71
71
|
|
|
72
72
|
(this.disposers ??= []).push( () => node.dispose() );
|
|
73
73
|
|