@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/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# @esportsplus/reactivity
|
|
2
2
|
|
|
3
|
-
Project doc bundle: [docs/index.md](./docs/index.md) — rejected dead-ends, skip reasons, completed-work index.
|
|
4
|
-
|
|
5
3
|
A fine-grained reactivity system with compile-time transformations. Write reactive code with natural JavaScript syntax while the compiler generates optimized signal-based code.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
@@ -223,7 +221,7 @@ let user = new ReactiveObject_1();
|
|
|
223
221
|
| `reactive(() => expr)` | Creates a computed value (compile-time only) |
|
|
224
222
|
| `reactive({...})` | Creates a reactive object with signals and computeds |
|
|
225
223
|
| `reactive([...])` | Creates a reactive array |
|
|
226
|
-
| `effect(fn,
|
|
224
|
+
| `effect(fn, apply?)` | Runs a function that re-executes when dependencies change. Returns a dispose function. Optional `apply(value, prev)` runs untracked after each run with `fn`'s return value |
|
|
227
225
|
| `root(fn)` | Creates an untracked scope. If `fn` accepts an argument, a dispose function is provided |
|
|
228
226
|
| `onCleanup(fn)` | Registers a cleanup function for the current effect/computed |
|
|
229
227
|
|
|
@@ -259,7 +257,7 @@ For advanced use cases, the underlying classes are exported:
|
|
|
259
257
|
|
|
260
258
|
| Class | Description |
|
|
261
259
|
|-------|-------------|
|
|
262
|
-
| `ReactiveArray<T>` | Array subclass with reactivity and event dispatching |
|
|
260
|
+
| `ReactiveArray<T>` | Array subclass with reactivity and event dispatching. `new ReactiveArray(items?: T[])` takes the initial items as one array |
|
|
263
261
|
| `ReactiveObject<T>` | Base class for reactive objects |
|
|
264
262
|
|
|
265
263
|
### Constants
|
|
@@ -283,7 +281,6 @@ Symbol constants for type identification:
|
|
|
283
281
|
| `SelectorSignal<T>` | Signal variant backing `signal.selector` per-key subscriptions |
|
|
284
282
|
| `Link` | Dependency graph link between nodes |
|
|
285
283
|
| `Reactive<T>` | Utility type for inferring reactive object/array types |
|
|
286
|
-
| `TransformResult` | Compiler transform output metadata |
|
|
287
284
|
|
|
288
285
|
## ReactiveArray
|
|
289
286
|
|
|
@@ -341,8 +338,13 @@ pnpm bench # vitest bench --run
|
|
|
341
338
|
`agent:test` (`tsc --noEmit && vitest run`) and `agent:bench` (`vitest bench --run`) are the
|
|
342
339
|
CI-facing aliases of the same build/test/bench commands.
|
|
343
340
|
|
|
341
|
+
## Changes in 0.35.0
|
|
342
|
+
|
|
343
|
+
- `new ReactiveArray(items?)` takes an array instead of variadic items. `new ReactiveArray(5)` previously produced a length-5 holey array, and spreading large arrays overflowed the call stack.
|
|
344
|
+
- `root.disposables` is removed. `reactive()` now always registers its disposal with the enclosing effect or root scope.
|
|
345
|
+
- `ReactiveArray` listener errors are still detached but are now rethrown on a microtask instead of swallowed.
|
|
346
|
+
- `TransformResult` is no longer exported.
|
|
347
|
+
|
|
344
348
|
## License
|
|
345
349
|
|
|
346
350
|
MIT
|
|
347
|
-
|
|
348
|
-
<!-- claude-code:readme-source-hash: 189167a17e9be8c6 -->
|
|
@@ -9,7 +9,7 @@ describe('ReactiveArray creation', () => {
|
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
bench('create with 10 items', () => {
|
|
12
|
-
new ReactiveArray(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
|
12
|
+
new ReactiveArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
bench('create with 100 items', () => {
|
|
@@ -19,7 +19,7 @@ describe('ReactiveArray creation', () => {
|
|
|
19
19
|
items.push(i);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
new ReactiveArray(
|
|
22
|
+
new ReactiveArray(items);
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -56,7 +56,7 @@ describe('ReactiveArray push', () => {
|
|
|
56
56
|
|
|
57
57
|
describe('ReactiveArray pop', () => {
|
|
58
58
|
bench('pop', () => {
|
|
59
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
59
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
60
60
|
|
|
61
61
|
arr.pop();
|
|
62
62
|
});
|
|
@@ -65,19 +65,19 @@ describe('ReactiveArray pop', () => {
|
|
|
65
65
|
|
|
66
66
|
describe('ReactiveArray splice', () => {
|
|
67
67
|
bench('splice remove 1', () => {
|
|
68
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
68
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
69
69
|
|
|
70
70
|
arr.splice(2, 1);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
bench('splice insert 1', () => {
|
|
74
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
74
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
75
75
|
|
|
76
76
|
arr.splice(2, 0, 99);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
bench('splice replace 1', () => {
|
|
80
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
80
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
81
81
|
|
|
82
82
|
arr.splice(2, 1, 99);
|
|
83
83
|
});
|
|
@@ -86,7 +86,7 @@ describe('ReactiveArray splice', () => {
|
|
|
86
86
|
|
|
87
87
|
describe('ReactiveArray sort', () => {
|
|
88
88
|
bench('sort 10 items', () => {
|
|
89
|
-
let arr = new ReactiveArray(5, 3, 8, 1, 9, 2, 7, 4, 6, 10);
|
|
89
|
+
let arr = new ReactiveArray([5, 3, 8, 1, 9, 2, 7, 4, 6, 10]);
|
|
90
90
|
|
|
91
91
|
arr.sort((a, b) => a - b);
|
|
92
92
|
});
|
|
@@ -98,7 +98,7 @@ describe('ReactiveArray sort', () => {
|
|
|
98
98
|
items.push(i);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
let arr = new ReactiveArray(
|
|
101
|
+
let arr = new ReactiveArray(items);
|
|
102
102
|
|
|
103
103
|
arr.sort((a, b) => a - b);
|
|
104
104
|
});
|
|
@@ -110,7 +110,7 @@ describe('ReactiveArray sort', () => {
|
|
|
110
110
|
items.push(i);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
let arr = new ReactiveArray(
|
|
113
|
+
let arr = new ReactiveArray(items);
|
|
114
114
|
|
|
115
115
|
arr.sort((a, b) => a - b);
|
|
116
116
|
});
|
|
@@ -119,13 +119,13 @@ describe('ReactiveArray sort', () => {
|
|
|
119
119
|
|
|
120
120
|
describe('ReactiveArray $set', () => {
|
|
121
121
|
bench('$set', () => {
|
|
122
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
122
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
123
123
|
|
|
124
124
|
arr.$set(2, 99);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
bench('$set same value (no-op)', () => {
|
|
128
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
128
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
129
129
|
|
|
130
130
|
arr.$set(2, 3);
|
|
131
131
|
});
|
|
@@ -169,7 +169,7 @@ describe('ReactiveArray dispose/clear', () => {
|
|
|
169
169
|
items.push(i);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
let arr = new ReactiveArray(
|
|
172
|
+
let arr = new ReactiveArray(items);
|
|
173
173
|
|
|
174
174
|
arr.dispose();
|
|
175
175
|
});
|
|
@@ -181,7 +181,7 @@ describe('ReactiveArray dispose/clear', () => {
|
|
|
181
181
|
items.push(i);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
let arr = new ReactiveArray(
|
|
184
|
+
let arr = new ReactiveArray(items);
|
|
185
185
|
|
|
186
186
|
arr.clear();
|
|
187
187
|
});
|
|
@@ -201,13 +201,13 @@ describe('ReactiveArray concat/unshift/shift/reverse', () => {
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
bench('unshift 10 items', () => {
|
|
204
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
204
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
205
205
|
|
|
206
206
|
arr.unshift(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
bench('shift', () => {
|
|
210
|
-
let arr = new ReactiveArray(1, 2, 3, 4, 5);
|
|
210
|
+
let arr = new ReactiveArray([1, 2, 3, 4, 5]);
|
|
211
211
|
|
|
212
212
|
arr.shift();
|
|
213
213
|
});
|
|
@@ -219,7 +219,7 @@ describe('ReactiveArray concat/unshift/shift/reverse', () => {
|
|
|
219
219
|
items.push(i);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
let arr = new ReactiveArray(
|
|
222
|
+
let arr = new ReactiveArray(items);
|
|
223
223
|
|
|
224
224
|
arr.reverse();
|
|
225
225
|
});
|
|
@@ -228,7 +228,7 @@ describe('ReactiveArray concat/unshift/shift/reverse', () => {
|
|
|
228
228
|
|
|
229
229
|
describe('ReactiveArray reactive length', () => {
|
|
230
230
|
bench('read $length in effect', () => {
|
|
231
|
-
let arr = new ReactiveArray(1, 2, 3);
|
|
231
|
+
let arr = new ReactiveArray([1, 2, 3]);
|
|
232
232
|
|
|
233
233
|
let stop = effect(() => {
|
|
234
234
|
arr.$length;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import type { Bindings } from './types.js';
|
|
4
|
-
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => ReplacementIntent[];
|
|
3
|
+
import type { Bindings, IsReactiveCall } from './types.js';
|
|
5
4
|
export default _default;
|
|
5
|
+
declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ReplacementIntent[];
|
package/build/compiler/array.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { ast
|
|
3
|
-
import {
|
|
4
|
-
function isReactiveCall(checker, node) {
|
|
5
|
-
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
let expr = node.expression;
|
|
9
|
-
if (checker) {
|
|
10
|
-
return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
|
|
11
|
-
}
|
|
12
|
-
return expr.text === ENTRYPOINT;
|
|
13
|
-
}
|
|
2
|
+
import { ast } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants.js';
|
|
14
4
|
function getElementTypeText(typeNode, sourceFile) {
|
|
15
5
|
if (ts.isArrayTypeNode(typeNode)) {
|
|
16
6
|
return typeNode.elementType.getText(sourceFile);
|
|
@@ -24,40 +14,8 @@ function getElementTypeText(typeNode, sourceFile) {
|
|
|
24
14
|
}
|
|
25
15
|
return null;
|
|
26
16
|
}
|
|
27
|
-
function getOperator(kind) {
|
|
28
|
-
switch (kind) {
|
|
29
|
-
case ts.SyntaxKind.PlusEqualsToken: return '+';
|
|
30
|
-
case ts.SyntaxKind.MinusEqualsToken: return '-';
|
|
31
|
-
case ts.SyntaxKind.AsteriskEqualsToken: return '*';
|
|
32
|
-
case ts.SyntaxKind.SlashEqualsToken: return '/';
|
|
33
|
-
case ts.SyntaxKind.PercentEqualsToken: return '%';
|
|
34
|
-
case ts.SyntaxKind.AsteriskAsteriskEqualsToken: return '**';
|
|
35
|
-
case ts.SyntaxKind.AmpersandEqualsToken: return '&';
|
|
36
|
-
case ts.SyntaxKind.BarEqualsToken: return '|';
|
|
37
|
-
case ts.SyntaxKind.CaretEqualsToken: return '^';
|
|
38
|
-
default: return '+';
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function isAssignmentOperator(kind) {
|
|
42
|
-
return kind === ts.SyntaxKind.EqualsToken ||
|
|
43
|
-
kind === ts.SyntaxKind.PlusEqualsToken ||
|
|
44
|
-
kind === ts.SyntaxKind.MinusEqualsToken ||
|
|
45
|
-
kind === ts.SyntaxKind.AsteriskEqualsToken ||
|
|
46
|
-
kind === ts.SyntaxKind.SlashEqualsToken ||
|
|
47
|
-
kind === ts.SyntaxKind.PercentEqualsToken ||
|
|
48
|
-
kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
|
|
49
|
-
kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
|
|
50
|
-
kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
|
|
51
|
-
kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
|
|
52
|
-
kind === ts.SyntaxKind.AmpersandEqualsToken ||
|
|
53
|
-
kind === ts.SyntaxKind.BarEqualsToken ||
|
|
54
|
-
kind === ts.SyntaxKind.CaretEqualsToken ||
|
|
55
|
-
kind === ts.SyntaxKind.BarBarEqualsToken ||
|
|
56
|
-
kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
|
|
57
|
-
kind === ts.SyntaxKind.QuestionQuestionEqualsToken;
|
|
58
|
-
}
|
|
59
17
|
function visit(ctx, node) {
|
|
60
|
-
if (isReactiveCall(
|
|
18
|
+
if (ctx.isReactiveCall(node) && node.arguments.length > 0) {
|
|
61
19
|
let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
62
20
|
if (ts.isArrayLiteralExpression(expression)) {
|
|
63
21
|
let elementType = null;
|
|
@@ -74,7 +32,7 @@ function visit(ctx, node) {
|
|
|
74
32
|
ctx.replacements.push({
|
|
75
33
|
node,
|
|
76
34
|
generate: (sf) => expression.elements.length > 0
|
|
77
|
-
? ` new ${NAMESPACE}.ReactiveArray${typeParam}(
|
|
35
|
+
? ` new ${NAMESPACE}.ReactiveArray${typeParam}(${expression.getText(sf)})`
|
|
78
36
|
: ` new ${NAMESPACE}.ReactiveArray${typeParam}()`
|
|
79
37
|
});
|
|
80
38
|
}
|
|
@@ -105,9 +63,9 @@ function visit(ctx, node) {
|
|
|
105
63
|
let name = ast.expression.name(node.expression);
|
|
106
64
|
if (name && ctx.bindings.get(name) === TYPES.Array) {
|
|
107
65
|
let expr = node.expression, parent = node.parent;
|
|
108
|
-
if (parent && ts.isBinaryExpression(parent) && parent.left === node &&
|
|
109
|
-
let op = parent.operatorToken.kind;
|
|
110
|
-
if (op ===
|
|
66
|
+
if (parent && ts.isBinaryExpression(parent) && parent.left === node && (parent.operatorToken.kind === ts.SyntaxKind.EqualsToken || COMPOUND_OPERATORS.has(parent.operatorToken.kind))) {
|
|
67
|
+
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
|
|
68
|
+
if (op === undefined) {
|
|
111
69
|
ctx.replacements.push({
|
|
112
70
|
node: parent,
|
|
113
71
|
generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
|
|
@@ -116,7 +74,7 @@ function visit(ctx, node) {
|
|
|
116
74
|
else {
|
|
117
75
|
ctx.replacements.push({
|
|
118
76
|
node: parent,
|
|
119
|
-
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${
|
|
77
|
+
generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
|
|
120
78
|
});
|
|
121
79
|
}
|
|
122
80
|
}
|
|
@@ -160,7 +118,7 @@ function visit(ctx, node) {
|
|
|
160
118
|
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
161
119
|
ts.isIdentifier(node.left)) {
|
|
162
120
|
let name = node.left.text, right = node.right;
|
|
163
|
-
while (ts.isAsExpression(right) || ts.
|
|
121
|
+
while (ts.isAsExpression(right) || ts.isTypeAssertion(right)) {
|
|
164
122
|
right = right.expression;
|
|
165
123
|
}
|
|
166
124
|
if (ctx.bindings.get(name) === TYPES.Array && ts.isArrayLiteralExpression(right)) {
|
|
@@ -172,12 +130,12 @@ function visit(ctx, node) {
|
|
|
172
130
|
});
|
|
173
131
|
}
|
|
174
132
|
}
|
|
175
|
-
|
|
133
|
+
node.forEachChild(n => visit(ctx, n));
|
|
176
134
|
}
|
|
177
|
-
export default (sourceFile, bindings,
|
|
135
|
+
export default (sourceFile, bindings, isReactiveCall) => {
|
|
178
136
|
let ctx = {
|
|
179
137
|
bindings,
|
|
180
|
-
|
|
138
|
+
isReactiveCall,
|
|
181
139
|
replacements: [],
|
|
182
140
|
sourceFile
|
|
183
141
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { ts } from '@esportsplus/typescript';
|
|
2
|
+
declare const COMPOUND_OPERATORS: Map<ts.SyntaxKind, string>;
|
|
1
3
|
declare const ENTRYPOINT = "reactive";
|
|
2
|
-
declare const ENTRYPOINT_REGEX: RegExp;
|
|
3
4
|
declare const NAMESPACE: string;
|
|
4
5
|
declare const TYPES: {
|
|
5
6
|
readonly Array: 0;
|
|
6
7
|
readonly Computed: 1;
|
|
7
|
-
readonly
|
|
8
|
-
readonly Signal: 3;
|
|
8
|
+
readonly Signal: 2;
|
|
9
9
|
};
|
|
10
10
|
type TYPES = typeof TYPES[keyof typeof TYPES];
|
|
11
|
-
export {
|
|
11
|
+
export { COMPOUND_OPERATORS, ENTRYPOINT, NAMESPACE, TYPES };
|
|
12
12
|
export { PACKAGE_NAME } from '../constants.js';
|
|
@@ -1,12 +1,28 @@
|
|
|
1
|
+
import { ts } from '@esportsplus/typescript';
|
|
1
2
|
import { uid } from '@esportsplus/typescript/compiler';
|
|
3
|
+
const COMPOUND_OPERATORS = new Map([
|
|
4
|
+
[ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
|
|
5
|
+
[ts.SyntaxKind.AmpersandEqualsToken, '&'],
|
|
6
|
+
[ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
|
|
7
|
+
[ts.SyntaxKind.AsteriskEqualsToken, '*'],
|
|
8
|
+
[ts.SyntaxKind.BarBarEqualsToken, '||'],
|
|
9
|
+
[ts.SyntaxKind.BarEqualsToken, '|'],
|
|
10
|
+
[ts.SyntaxKind.CaretEqualsToken, '^'],
|
|
11
|
+
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
|
|
12
|
+
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
|
|
13
|
+
[ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
|
|
14
|
+
[ts.SyntaxKind.MinusEqualsToken, '-'],
|
|
15
|
+
[ts.SyntaxKind.PercentEqualsToken, '%'],
|
|
16
|
+
[ts.SyntaxKind.PlusEqualsToken, '+'],
|
|
17
|
+
[ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
|
|
18
|
+
[ts.SyntaxKind.SlashEqualsToken, '/']
|
|
19
|
+
]);
|
|
2
20
|
const ENTRYPOINT = 'reactive';
|
|
3
|
-
const ENTRYPOINT_REGEX = /\breactive\b/;
|
|
4
21
|
const NAMESPACE = uid('reactivity');
|
|
5
22
|
const TYPES = {
|
|
6
23
|
Array: 0,
|
|
7
24
|
Computed: 1,
|
|
8
|
-
|
|
9
|
-
Signal: 3
|
|
25
|
+
Signal: 2
|
|
10
26
|
};
|
|
11
|
-
export {
|
|
27
|
+
export { COMPOUND_OPERATORS, ENTRYPOINT, NAMESPACE, TYPES };
|
|
12
28
|
export { PACKAGE_NAME } from '../constants.js';
|
package/build/compiler/index.js
CHANGED
|
@@ -4,11 +4,6 @@ import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME } from './constants.js';
|
|
|
4
4
|
import array from './array.js';
|
|
5
5
|
import object from './object.js';
|
|
6
6
|
import primitives from './primitives.js';
|
|
7
|
-
function findRemainingCalls(checker, sourceFile, transformedNodes) {
|
|
8
|
-
let ctx = { checker, replacements: [], sourceFile, transformedNodes };
|
|
9
|
-
visit(ctx, sourceFile);
|
|
10
|
-
return ctx.replacements;
|
|
11
|
-
}
|
|
12
7
|
function isReactiveCallExpression(checker, node) {
|
|
13
8
|
if (!ts.isCallExpression(node)) {
|
|
14
9
|
return false;
|
|
@@ -22,51 +17,43 @@ function isReactiveCallExpression(checker, node) {
|
|
|
22
17
|
}
|
|
23
18
|
return false;
|
|
24
19
|
}
|
|
25
|
-
function hasReactiveCalls(checker, node) {
|
|
26
|
-
if (isReactiveCallExpression(checker, node)) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
let found = false;
|
|
30
|
-
ts.forEachChild(node, child => {
|
|
31
|
-
if (!found && hasReactiveCalls(checker, child)) {
|
|
32
|
-
found = true;
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
return found;
|
|
36
|
-
}
|
|
37
|
-
function visit(ctx, node) {
|
|
38
|
-
if (isReactiveCallExpression(ctx.checker, node) && !ctx.transformedNodes.has(node) && !ctx.transformedNodes.has(node.expression)) {
|
|
39
|
-
ctx.replacements.push({
|
|
40
|
-
generate: () => `${NAMESPACE}.reactive(${node.arguments.map(a => a.getText(ctx.sourceFile)).join(', ')})`,
|
|
41
|
-
node
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
ts.forEachChild(node, n => visit(ctx, n));
|
|
45
|
-
}
|
|
46
20
|
export default {
|
|
47
21
|
patterns: ['reactive(', 'reactive<'],
|
|
48
22
|
transform: (ctx) => {
|
|
49
|
-
|
|
23
|
+
let checker = ctx.checker;
|
|
24
|
+
if (!checker) {
|
|
50
25
|
return {};
|
|
51
26
|
}
|
|
52
27
|
let bindings = new Map(), intents = {
|
|
53
28
|
imports: [],
|
|
54
29
|
prepend: [],
|
|
55
30
|
replacements: []
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
31
|
+
}, isReactiveCall = (node) => isReactiveCallExpression(checker, node), sourceFile = ctx.sourceFile;
|
|
32
|
+
let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
33
|
+
if (calls.length === 0) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
60
36
|
intents.replacements.push(...replacements);
|
|
61
|
-
|
|
62
|
-
intents.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
let objects = object(sourceFile, bindings, isReactiveCall);
|
|
38
|
+
intents.prepend.push(...objects.prepend);
|
|
39
|
+
intents.replacements.push(...objects.replacements);
|
|
40
|
+
intents.replacements.push(...array(sourceFile, bindings, isReactiveCall));
|
|
41
|
+
let transformed = new Set(intents.replacements.map(r => r.node));
|
|
42
|
+
for (let i = 0, n = calls.length; i < n; i++) {
|
|
43
|
+
let call = calls[i];
|
|
44
|
+
if (transformed.has(call) || transformed.has(call.expression)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
intents.replacements.push({
|
|
48
|
+
generate: () => `${NAMESPACE}.reactive(${call.arguments.map(a => a.getText(sourceFile)).join(', ')})`,
|
|
49
|
+
node: call
|
|
68
50
|
});
|
|
69
51
|
}
|
|
52
|
+
intents.imports.push({
|
|
53
|
+
namespace: NAMESPACE,
|
|
54
|
+
package: PACKAGE_NAME,
|
|
55
|
+
remove: [ENTRYPOINT]
|
|
56
|
+
});
|
|
70
57
|
return intents;
|
|
71
58
|
}
|
|
72
59
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import type { Bindings } from './types.js';
|
|
3
|
+
import type { Bindings, IsReactiveCall } from './types.js';
|
|
4
4
|
type ObjectTransformResult = {
|
|
5
5
|
prepend: string[];
|
|
6
6
|
replacements: ReplacementIntent[];
|
|
7
7
|
};
|
|
8
|
-
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => ObjectTransformResult;
|
|
9
8
|
export default _default;
|
|
9
|
+
declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ObjectTransformResult;
|
package/build/compiler/object.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { code,
|
|
3
|
-
import {
|
|
2
|
+
import { code, uid } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { NAMESPACE, TYPES } from './constants.js';
|
|
4
4
|
function analyzeProperty(prop, sourceFile) {
|
|
5
5
|
if (!ts.isPropertyAssignment(prop)) {
|
|
6
6
|
return null;
|
|
@@ -13,10 +13,10 @@ function analyzeProperty(prop, sourceFile) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
let unwrapped = prop.initializer, value = unwrapped, valueText = value.getText(sourceFile);
|
|
16
|
-
while (ts.isAsExpression(unwrapped) || ts.
|
|
16
|
+
while (ts.isAsExpression(unwrapped) || ts.isTypeAssertion(unwrapped) || ts.isParenthesizedExpression(unwrapped)) {
|
|
17
17
|
unwrapped = unwrapped.expression;
|
|
18
18
|
}
|
|
19
|
-
if (ts.isAsExpression(value) || ts.
|
|
19
|
+
if (ts.isAsExpression(value) || ts.isTypeAssertion(value)) {
|
|
20
20
|
let type = value.type;
|
|
21
21
|
if (ts.isArrayTypeNode(type) ||
|
|
22
22
|
(ts.isTypeReferenceNode(type) &&
|
|
@@ -139,34 +139,23 @@ function isStaticValue(node) {
|
|
|
139
139
|
node.kind === ts.SyntaxKind.UndefinedKeyword ||
|
|
140
140
|
(ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand));
|
|
141
141
|
}
|
|
142
|
-
function isReactiveCall(checker, node) {
|
|
143
|
-
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
let expr = node.expression;
|
|
147
|
-
if (checker) {
|
|
148
|
-
return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
|
|
149
|
-
}
|
|
150
|
-
return expr.text === ENTRYPOINT;
|
|
151
|
-
}
|
|
152
142
|
function visit(ctx, node) {
|
|
153
|
-
if (isReactiveCall(
|
|
143
|
+
if (ctx.isReactiveCall(node)) {
|
|
154
144
|
let arg = node.arguments[0];
|
|
155
145
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
156
146
|
let properties = [], props = arg.properties, varname = null;
|
|
157
147
|
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
158
148
|
varname = node.parent.name.text;
|
|
159
|
-
ctx.bindings.set(varname, TYPES.Object);
|
|
160
149
|
}
|
|
161
150
|
for (let i = 0, n = props.length; i < n; i++) {
|
|
162
151
|
let prop = props[i];
|
|
163
152
|
if (ts.isSpreadAssignment(prop)) {
|
|
164
|
-
|
|
153
|
+
node.forEachChild(n => visit(ctx, n));
|
|
165
154
|
return;
|
|
166
155
|
}
|
|
167
156
|
let analyzed = analyzeProperty(prop, ctx.sourceFile);
|
|
168
157
|
if (!analyzed) {
|
|
169
|
-
|
|
158
|
+
node.forEachChild(n => visit(ctx, n));
|
|
170
159
|
return;
|
|
171
160
|
}
|
|
172
161
|
properties.push(analyzed);
|
|
@@ -183,13 +172,13 @@ function visit(ctx, node) {
|
|
|
183
172
|
});
|
|
184
173
|
}
|
|
185
174
|
}
|
|
186
|
-
|
|
175
|
+
node.forEachChild(n => visit(ctx, n));
|
|
187
176
|
}
|
|
188
|
-
export default (sourceFile, bindings,
|
|
177
|
+
export default (sourceFile, bindings, isReactiveCall) => {
|
|
189
178
|
let ctx = {
|
|
190
179
|
bindings,
|
|
191
180
|
calls: [],
|
|
192
|
-
|
|
181
|
+
isReactiveCall,
|
|
193
182
|
sourceFile
|
|
194
183
|
};
|
|
195
184
|
visit(ctx, sourceFile);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
declare const _default: ({ root }?: {
|
|
2
2
|
root?: string;
|
|
3
3
|
}) => {
|
|
4
|
+
closeBundle: () => void;
|
|
5
|
+
closeWatcher: () => void;
|
|
4
6
|
configResolved: (config: unknown) => void;
|
|
5
|
-
enforce:
|
|
7
|
+
enforce: 'pre';
|
|
6
8
|
name: string;
|
|
7
9
|
transform: (code: string, id: string) => {
|
|
8
10
|
code: string;
|
|
9
|
-
map:
|
|
11
|
+
map: import("@esportsplus/typescript/compiler").SourceMapV3;
|
|
10
12
|
} | null;
|
|
11
13
|
watchChange: (id: string) => void;
|
|
12
14
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import type { Bindings } from './types.js';
|
|
4
|
-
|
|
3
|
+
import type { Bindings, IsReactiveCall } from './types.js';
|
|
4
|
+
type PrimitivesTransformResult = {
|
|
5
|
+
calls: ts.CallExpression[];
|
|
6
|
+
replacements: ReplacementIntent[];
|
|
7
|
+
};
|
|
5
8
|
export default _default;
|
|
9
|
+
declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): PrimitivesTransformResult;
|