@esportsplus/reactivity 0.26.1 → 0.27.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/compiler/array.d.ts +1 -1
- package/build/compiler/array.js +27 -7
- package/build/compiler/index.d.ts +5 -2
- package/build/compiler/index.js +44 -40
- package/build/compiler/object.d.ts +1 -1
- package/build/compiler/object.js +91 -53
- package/build/compiler/plugins/tsc.js +2 -2
- package/build/compiler/plugins/vite.js +2 -1
- package/build/constants.js +1 -1
- package/build/index.d.ts +3 -2
- package/build/index.js +3 -2
- package/build/reactive/array.js +3 -2
- package/build/reactive/index.d.ts +5 -3
- package/build/reactive/index.js +26 -5
- package/build/reactive/object.d.ts +13 -0
- package/build/reactive/object.js +86 -0
- package/build/types.d.ts +2 -2
- package/package.json +2 -2
- package/src/compiler/array.ts +44 -12
- package/src/compiler/index.ts +59 -45
- package/src/compiler/object.ts +108 -64
- package/src/compiler/plugins/tsc.ts +2 -2
- package/src/compiler/plugins/vite.ts +2 -1
- package/src/constants.ts +1 -4
- package/src/index.ts +3 -2
- package/src/reactive/array.ts +4 -2
- package/src/reactive/index.ts +36 -9
- package/src/reactive/object.ts +126 -0
- package/src/types.ts +2 -2
- package/test/debug.ts +7 -0
- package/test/vite.config.ts +2 -1
- package/build/compiler/primitives.d.ts +0 -4
- package/build/compiler/primitives.js +0 -266
- package/src/compiler/primitives.ts +0 -370
package/src/compiler/array.ts
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
|
-
import { ast, code as c, type Replacement } from '@esportsplus/typescript/compiler';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
import {
|
|
2
|
+
import { ast, code as c, type Replacement } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '~/constants';
|
|
4
4
|
import type { Bindings } from '~/types';
|
|
5
|
+
import { isReactiveCall } from '.';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface TransformContext {
|
|
9
|
+
bindings: Bindings;
|
|
10
|
+
checker?: ts.TypeChecker;
|
|
11
|
+
replacements: Replacement[];
|
|
12
|
+
sourceFile: ts.SourceFile;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
function visit(ctx: TransformContext, node: ts.Node): void {
|
|
17
|
+
if (ts.isCallExpression(node) && isReactiveCall(node, ctx.checker) && node.arguments.length > 0) {
|
|
18
|
+
let arg = node.arguments[0],
|
|
19
|
+
expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
20
|
+
|
|
21
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
22
|
+
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
23
|
+
ctx.bindings.set(node.parent.name.text, COMPILER_TYPES.Array);
|
|
24
|
+
}
|
|
5
25
|
|
|
26
|
+
ctx.replacements.push({
|
|
27
|
+
end: node.end,
|
|
28
|
+
newText: expression.elements.length > 0
|
|
29
|
+
? ` new ${COMPILER_NAMESPACE}.ReactiveArray(...${expression.getText(ctx.sourceFile)})`
|
|
30
|
+
: ` new ${COMPILER_NAMESPACE}.ReactiveArray()`,
|
|
31
|
+
start: node.pos
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
6
35
|
|
|
7
|
-
function visit(ctx: { bindings: Bindings, replacements: Replacement[], sourceFile: ts.SourceFile }, node: ts.Node): void {
|
|
8
36
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
9
37
|
if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === COMPILER_TYPES.Array) {
|
|
10
38
|
ctx.bindings.set(node.name.text, COMPILER_TYPES.Array);
|
|
@@ -61,17 +89,16 @@ function visit(ctx: { bindings: Bindings, replacements: Replacement[], sourceFil
|
|
|
61
89
|
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
62
90
|
ts.isElementAccessExpression(node.left)
|
|
63
91
|
) {
|
|
64
|
-
let
|
|
65
|
-
|
|
92
|
+
let element = node.left,
|
|
93
|
+
name = ast.getExpressionName(element.expression);
|
|
66
94
|
|
|
67
|
-
if (
|
|
68
|
-
let index =
|
|
69
|
-
obj = elemAccess.expression.getText(ctx.sourceFile),
|
|
95
|
+
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
96
|
+
let index = element.argumentExpression.getText(ctx.sourceFile),
|
|
70
97
|
value = node.right.getText(ctx.sourceFile);
|
|
71
98
|
|
|
72
99
|
ctx.replacements.push({
|
|
73
100
|
end: node.end,
|
|
74
|
-
newText: `${
|
|
101
|
+
newText: `${element.expression.getText(ctx.sourceFile)}.$set(${index}, ${value})`,
|
|
75
102
|
start: node.pos
|
|
76
103
|
});
|
|
77
104
|
}
|
|
@@ -81,15 +108,20 @@ function visit(ctx: { bindings: Bindings, replacements: Replacement[], sourceFil
|
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
|
|
84
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
111
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker): string => {
|
|
85
112
|
let code = sourceFile.getFullText(),
|
|
86
|
-
ctx = {
|
|
113
|
+
ctx: TransformContext = {
|
|
87
114
|
bindings,
|
|
115
|
+
checker,
|
|
88
116
|
replacements: [],
|
|
89
117
|
sourceFile
|
|
90
118
|
};
|
|
91
119
|
|
|
92
120
|
visit(ctx, sourceFile);
|
|
93
121
|
|
|
122
|
+
if (ctx.replacements.length === 0) {
|
|
123
|
+
return code;
|
|
124
|
+
}
|
|
125
|
+
|
|
94
126
|
return c.replace(code, ctx.replacements);
|
|
95
|
-
};
|
|
127
|
+
};
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,87 +1,101 @@
|
|
|
1
|
+
import type { PluginContext } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import {
|
|
3
|
-
import { COMPILER_ENTRYPOINT,
|
|
3
|
+
import { ast, imports } from '@esportsplus/typescript/compiler';
|
|
4
|
+
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, PACKAGE } from '~/constants';
|
|
4
5
|
import type { Bindings, TransformResult } from '~/types';
|
|
5
6
|
import array from './array';
|
|
6
7
|
import object from './object';
|
|
7
|
-
import primitives from './primitives';
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
type AnalyzedFile = {
|
|
11
|
+
hasReactiveImport: boolean;
|
|
12
|
+
};
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
15
|
+
const CONTEXT_KEY = 'reactivity:analyzed';
|
|
16
|
+
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
imported: false,
|
|
20
|
-
used: false
|
|
21
|
-
};
|
|
18
|
+
let transforms = [object, array];
|
|
22
19
|
|
|
23
|
-
visit(ctx, ts.createSourceFile('detect.ts', code, ts.ScriptTarget.Latest, false));
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
function getAnalyzedFile(context: PluginContext | undefined, filename: string): AnalyzedFile | undefined {
|
|
22
|
+
return (context?.get(CONTEXT_KEY) as Map<string, AnalyzedFile> | undefined)?.get(filename);
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
25
|
+
function hasReactiveImport(sourceFile: ts.SourceFile): boolean {
|
|
26
|
+
return imports.find(sourceFile, PACKAGE).some(i => i.specifiers.has(COMPILER_ENTRYPOINT));
|
|
27
|
+
}
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
ts.isNamedImports(node.importClause.namedBindings)
|
|
37
|
-
) {
|
|
38
|
-
let elements = node.importClause.namedBindings.elements;
|
|
29
|
+
function isReactiveCallNode(node: ts.Node): boolean {
|
|
30
|
+
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === COMPILER_ENTRYPOINT;
|
|
31
|
+
}
|
|
39
32
|
|
|
40
|
-
for (let i = 0, n = elements.length; i < n; i++) {
|
|
41
|
-
let element = elements[i];
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
}
|
|
34
|
+
const analyze = (sourceFile: ts.SourceFile, _program: ts.Program, context: PluginContext): void => {
|
|
35
|
+
if (!hasReactiveImport(sourceFile)) {
|
|
36
|
+
return;
|
|
48
37
|
}
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
ctx.used = true;
|
|
39
|
+
let files = context.get(CONTEXT_KEY) as Map<string, AnalyzedFile> | undefined;
|
|
40
|
+
|
|
41
|
+
if (!files) {
|
|
42
|
+
files = new Map();
|
|
43
|
+
context.set(CONTEXT_KEY, files);
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
files.set(sourceFile.fileName, {
|
|
47
|
+
hasReactiveImport: true
|
|
48
|
+
});
|
|
49
|
+
};
|
|
60
50
|
|
|
51
|
+
const isReactiveCall = (node: ts.CallExpression, _checker?: ts.TypeChecker): boolean => {
|
|
52
|
+
if (!ts.isIdentifier(node.expression)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return node.expression.text === COMPILER_ENTRYPOINT;
|
|
57
|
+
};
|
|
61
58
|
|
|
62
|
-
const transform = (sourceFile: ts.SourceFile): TransformResult => {
|
|
59
|
+
const transform = (sourceFile: ts.SourceFile, program: ts.Program, context?: PluginContext): TransformResult => {
|
|
63
60
|
let bindings: Bindings = new Map(),
|
|
64
61
|
changed = false,
|
|
62
|
+
checker = program.getTypeChecker(),
|
|
65
63
|
code = sourceFile.getFullText(),
|
|
66
64
|
current = sourceFile,
|
|
65
|
+
filename = sourceFile.fileName,
|
|
67
66
|
result: string;
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
// Try to get pre-analyzed data from context
|
|
69
|
+
let analyzed = getAnalyzedFile(context, filename);
|
|
70
|
+
|
|
71
|
+
// Fall back to inline check (for Vite or when context unavailable)
|
|
72
|
+
if (!analyzed) {
|
|
73
|
+
if (!hasReactiveImport(sourceFile)) {
|
|
74
|
+
return { changed: false, code, sourceFile };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (!analyzed.hasReactiveImport) {
|
|
70
78
|
return { changed: false, code, sourceFile };
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
for (let i = 0, n = transforms.length; i < n; i++) {
|
|
74
|
-
result = transforms[i](current, bindings,
|
|
82
|
+
result = transforms[i](current, bindings, checker);
|
|
75
83
|
|
|
76
84
|
if (result !== code) {
|
|
77
|
-
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
78
85
|
code = result;
|
|
79
86
|
changed = true;
|
|
87
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
if (changed) {
|
|
84
|
-
|
|
92
|
+
let remove: string[] = [];
|
|
93
|
+
|
|
94
|
+
if (!ast.hasMatch(current, isReactiveCallNode)) {
|
|
95
|
+
remove.push(COMPILER_ENTRYPOINT);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
code = imports.modify(code, current, PACKAGE, { namespace: COMPILER_NAMESPACE, remove });
|
|
85
99
|
sourceFile = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
86
100
|
}
|
|
87
101
|
|
|
@@ -89,4 +103,4 @@ const transform = (sourceFile: ts.SourceFile): TransformResult => {
|
|
|
89
103
|
};
|
|
90
104
|
|
|
91
105
|
|
|
92
|
-
export { transform };
|
|
106
|
+
export { analyze, isReactiveCall, transform };
|
package/src/compiler/object.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { code as c, type Replacement } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import {
|
|
2
|
+
import { code as c, uid, type Replacement } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '~/constants';
|
|
4
4
|
import type { Bindings } from '~/types';
|
|
5
|
+
import { isReactiveCall } from '.';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
interface AnalyzedProperty {
|
|
9
|
+
isStatic: boolean;
|
|
8
10
|
key: string;
|
|
9
11
|
type: COMPILER_TYPES;
|
|
10
12
|
valueText: string;
|
|
@@ -21,10 +23,9 @@ interface ReactiveObjectCall {
|
|
|
21
23
|
interface TransformContext {
|
|
22
24
|
bindings: Bindings;
|
|
23
25
|
calls: ReactiveObjectCall[];
|
|
26
|
+
checker?: ts.TypeChecker;
|
|
24
27
|
classCounter: number;
|
|
25
|
-
hasReactiveImport: boolean;
|
|
26
28
|
lastImportEnd: number;
|
|
27
|
-
ns: string;
|
|
28
29
|
sourceFile: ts.SourceFile;
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -43,100 +44,139 @@ function analyzeProperty(prop: ts.ObjectLiteralElementLike, sourceFile: ts.Sourc
|
|
|
43
44
|
return null;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
let
|
|
47
|
+
let unwrapped = prop.initializer,
|
|
48
|
+
value = unwrapped,
|
|
47
49
|
valueText = value.getText(sourceFile);
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
while (ts.isAsExpression(unwrapped) || ts.isTypeAssertionExpression(unwrapped) || ts.isParenthesizedExpression(unwrapped)) {
|
|
52
|
+
unwrapped = unwrapped.expression;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
if (ts.
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped)) {
|
|
56
|
+
return { isStatic: false, key, type: COMPILER_TYPES.Computed, valueText };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (ts.isArrayLiteralExpression(unwrapped)) {
|
|
60
|
+
let elements = unwrapped.elements,
|
|
61
|
+
isStatic = value === unwrapped;
|
|
56
62
|
|
|
57
63
|
for (let i = 0, n = elements.length; i < n; i++) {
|
|
58
|
-
if (
|
|
59
|
-
|
|
64
|
+
if (isStatic && !isStaticValue(elements[i])) {
|
|
65
|
+
isStatic = false;
|
|
60
66
|
}
|
|
61
|
-
|
|
62
|
-
elementsText += elements[i].getText(sourceFile);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
return { key, type: COMPILER_TYPES.Array, valueText
|
|
69
|
+
return { isStatic, key, type: COMPILER_TYPES.Array, valueText };
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
return { key, type: COMPILER_TYPES.Signal, valueText };
|
|
72
|
+
return { isStatic: isStaticValue(value), key, type: COMPILER_TYPES.Signal, valueText };
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
function buildClassCode(className: string, properties: AnalyzedProperty[]
|
|
75
|
+
function buildClassCode(className: string, properties: AnalyzedProperty[]): string {
|
|
72
76
|
let accessors: string[] = [],
|
|
73
|
-
|
|
77
|
+
body: string[] = [],
|
|
74
78
|
fields: string[] = [],
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
generics: string[] = [],
|
|
80
|
+
parameters: string[] = [],
|
|
81
|
+
setters = 0;
|
|
78
82
|
|
|
79
83
|
for (let i = 0, n = properties.length; i < n; i++) {
|
|
80
|
-
let { key, type, valueText } = properties[i]
|
|
84
|
+
let { isStatic, key, type, valueText } = properties[i],
|
|
85
|
+
generic = `T${parameters.length}`,
|
|
86
|
+
parameter = `_p${parameters.length}`;
|
|
81
87
|
|
|
82
88
|
if (type === COMPILER_TYPES.Signal) {
|
|
83
|
-
let
|
|
89
|
+
let value = `_v${setters++}`;
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
if (isStatic) {
|
|
92
|
+
accessors.push(`
|
|
93
|
+
get ${key}() {
|
|
94
|
+
return ${COMPILER_NAMESPACE}.read(this.#${key});
|
|
95
|
+
}
|
|
96
|
+
set ${key}(${value}) {
|
|
97
|
+
${COMPILER_NAMESPACE}.write(this.#${key}, ${value});
|
|
98
|
+
}
|
|
99
|
+
`);
|
|
100
|
+
fields.push(`#${key} = this[${COMPILER_NAMESPACE}.SIGNAL](${valueText});`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
accessors.push(`
|
|
104
|
+
get ${key}() {
|
|
105
|
+
return ${COMPILER_NAMESPACE}.read(this.#${key}) as ${generic};
|
|
106
|
+
}
|
|
107
|
+
set ${key}(${value}) {
|
|
108
|
+
${COMPILER_NAMESPACE}.write(this.#${key}, ${value});
|
|
109
|
+
}
|
|
110
|
+
`);
|
|
111
|
+
body.push(`this.#${key} = this[${COMPILER_NAMESPACE}.SIGNAL](${parameter});`);
|
|
112
|
+
fields.push(`#${key};`);
|
|
113
|
+
generics.push(generic);
|
|
114
|
+
parameters.push(`${parameter}: ${generic}`);
|
|
115
|
+
}
|
|
88
116
|
}
|
|
89
117
|
else if (type === COMPILER_TYPES.Array) {
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
accessors.push(`
|
|
119
|
+
get ${key}() {
|
|
120
|
+
return this.#${key};
|
|
121
|
+
}
|
|
122
|
+
`);
|
|
123
|
+
body.push(`this.#${key} = this[${COMPILER_NAMESPACE}.REACTIVE_ARRAY](${parameter});`);
|
|
124
|
+
fields.push(`#${key};`);
|
|
125
|
+
generics.push(`${generic} extends unknown[]`);
|
|
126
|
+
parameters.push(`${parameter}: ${generic}`);
|
|
92
127
|
}
|
|
93
128
|
else if (type === COMPILER_TYPES.Computed) {
|
|
94
|
-
accessors.push(`
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
accessors.push(`
|
|
130
|
+
get ${key}() {
|
|
131
|
+
return ${COMPILER_NAMESPACE}.read(this.#${key});
|
|
132
|
+
}
|
|
133
|
+
`);
|
|
134
|
+
body.push(`this.#${key} = this[${COMPILER_NAMESPACE}.COMPUTED](${parameter});`);
|
|
135
|
+
fields.push(`#${key};`);
|
|
136
|
+
generics.push(`${generic} extends ${COMPILER_NAMESPACE}.Computed<ReturnType<${generic}>>['fn']`);
|
|
137
|
+
parameters.push(`${parameter}: ${generic}`);
|
|
97
138
|
}
|
|
98
139
|
}
|
|
99
140
|
|
|
100
141
|
return `
|
|
101
|
-
class ${className} {
|
|
142
|
+
class ${className}${generics.length > 0 ? `<${generics.join(', ')}>` : ''} extends ${COMPILER_NAMESPACE}.ReactiveObject<any> {
|
|
102
143
|
${fields.join('\n')}
|
|
103
|
-
${
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
|
|
144
|
+
constructor(${parameters.join(', ')}) {
|
|
145
|
+
super(null);
|
|
146
|
+
${body.join('\n')}
|
|
107
147
|
}
|
|
148
|
+
${accessors.join('\n')}
|
|
108
149
|
}
|
|
109
150
|
`;
|
|
110
151
|
}
|
|
111
152
|
|
|
112
|
-
function
|
|
113
|
-
if (
|
|
114
|
-
|
|
153
|
+
function isStaticValue(node: ts.Node): boolean {
|
|
154
|
+
if (
|
|
155
|
+
ts.isNumericLiteral(node) ||
|
|
156
|
+
ts.isStringLiteral(node) ||
|
|
157
|
+
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
158
|
+
node.kind === ts.SyntaxKind.FalseKeyword ||
|
|
159
|
+
node.kind === ts.SyntaxKind.NullKeyword ||
|
|
160
|
+
node.kind === ts.SyntaxKind.UndefinedKeyword
|
|
161
|
+
) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
115
164
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
) {
|
|
120
|
-
let clause = node.importClause;
|
|
165
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand)) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
121
168
|
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
124
171
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
172
|
+
function visit(ctx: TransformContext, node: ts.Node): void {
|
|
173
|
+
if (ts.isImportDeclaration(node)) {
|
|
174
|
+
ctx.lastImportEnd = node.end;
|
|
133
175
|
}
|
|
134
176
|
|
|
135
177
|
if (
|
|
136
|
-
ctx.hasReactiveImport &&
|
|
137
178
|
ts.isCallExpression(node) &&
|
|
138
|
-
|
|
139
|
-
node.expression.text === 'reactive'
|
|
179
|
+
isReactiveCall(node, ctx.checker)
|
|
140
180
|
) {
|
|
141
181
|
let arg = node.arguments[0];
|
|
142
182
|
|
|
@@ -173,7 +213,7 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
173
213
|
}
|
|
174
214
|
|
|
175
215
|
ctx.calls.push({
|
|
176
|
-
className:
|
|
216
|
+
className: uid('ReactiveObject'),
|
|
177
217
|
end: node.end,
|
|
178
218
|
properties,
|
|
179
219
|
start: node.pos,
|
|
@@ -186,15 +226,14 @@ function visit(ctx: TransformContext, node: ts.Node): void {
|
|
|
186
226
|
}
|
|
187
227
|
|
|
188
228
|
|
|
189
|
-
export default (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
229
|
+
export default (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker): string => {
|
|
190
230
|
let code = sourceFile.getFullText(),
|
|
191
231
|
ctx: TransformContext = {
|
|
192
232
|
bindings,
|
|
193
233
|
calls: [],
|
|
234
|
+
checker,
|
|
194
235
|
classCounter: 0,
|
|
195
|
-
hasReactiveImport: false,
|
|
196
236
|
lastImportEnd: 0,
|
|
197
|
-
ns,
|
|
198
237
|
sourceFile
|
|
199
238
|
};
|
|
200
239
|
|
|
@@ -204,7 +243,7 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, ns: string): stri
|
|
|
204
243
|
return code;
|
|
205
244
|
}
|
|
206
245
|
|
|
207
|
-
let classes = ctx.calls.map(c => buildClassCode(c.className, c.properties
|
|
246
|
+
let classes = ctx.calls.map(c => buildClassCode(c.className, c.properties)).join('\n'),
|
|
208
247
|
replacements: Replacement[] = [];
|
|
209
248
|
|
|
210
249
|
replacements.push({
|
|
@@ -218,10 +257,15 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, ns: string): stri
|
|
|
218
257
|
|
|
219
258
|
replacements.push({
|
|
220
259
|
end: call.end,
|
|
221
|
-
newText: ` new ${call.className}(
|
|
260
|
+
newText: ` new ${call.className}(${
|
|
261
|
+
call.properties
|
|
262
|
+
.filter(({ isStatic, type }) => !isStatic || type === COMPILER_TYPES.Computed)
|
|
263
|
+
.map(p => p.valueText)
|
|
264
|
+
.join(', ')
|
|
265
|
+
})`,
|
|
222
266
|
start: call.start
|
|
223
267
|
});
|
|
224
268
|
}
|
|
225
269
|
|
|
226
270
|
return c.replace(code, replacements);
|
|
227
|
-
};
|
|
271
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
2
|
-
import { transform } from '..';
|
|
2
|
+
import { analyze, transform } from '..';
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
export default plugin.tsc(transform) as ReturnType<typeof plugin.tsc>;
|
|
5
|
+
export default plugin.tsc({ analyze, transform }) as ReturnType<typeof plugin.tsc>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { PACKAGE } from '../../constants';
|
|
2
2
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import { transform } from '..';
|
|
3
|
+
import { analyze, transform } from '..';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export default plugin.vite({
|
|
7
|
+
analyze,
|
|
7
8
|
name: PACKAGE,
|
|
8
9
|
transform
|
|
9
10
|
});
|
package/src/constants.ts
CHANGED
|
@@ -5,7 +5,7 @@ const COMPILER_ENTRYPOINT = 'reactive';
|
|
|
5
5
|
|
|
6
6
|
const COMPILER_ENTRYPOINT_REGEX = /\breactive\b/;
|
|
7
7
|
|
|
8
|
-
const COMPILER_NAMESPACE = uid(
|
|
8
|
+
const COMPILER_NAMESPACE = uid('reactivity');
|
|
9
9
|
|
|
10
10
|
const enum COMPILER_TYPES {
|
|
11
11
|
Array,
|
|
@@ -14,7 +14,6 @@ const enum COMPILER_TYPES {
|
|
|
14
14
|
Signal
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
const COMPUTED = Symbol('reactivity.computed');
|
|
19
18
|
|
|
20
19
|
const PACKAGE = '@esportsplus/reactivity';
|
|
@@ -25,7 +24,6 @@ const REACTIVE_OBJECT = Symbol('reactivity.reactive.object');
|
|
|
25
24
|
|
|
26
25
|
const SIGNAL = Symbol('reactivity.signal');
|
|
27
26
|
|
|
28
|
-
|
|
29
27
|
const STABILIZER_IDLE = 0;
|
|
30
28
|
|
|
31
29
|
const STABILIZER_RESCHEDULE = 1;
|
|
@@ -34,7 +32,6 @@ const STABILIZER_RUNNING = 2;
|
|
|
34
32
|
|
|
35
33
|
const STABILIZER_SCHEDULED = 3;
|
|
36
34
|
|
|
37
|
-
|
|
38
35
|
const STATE_NONE = 0;
|
|
39
36
|
|
|
40
37
|
const STATE_CHECK = 1 << 0;
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { isPromise } from '@esportsplus/utilities';
|
|
2
|
+
export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL } from './constants';
|
|
3
|
+
export { default as reactive, ReactiveArray, ReactiveObject } from './reactive';
|
|
3
4
|
export * from './system';
|
|
4
5
|
export * from './types';
|
package/src/reactive/array.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isArray } from '@esportsplus/utilities';
|
|
2
2
|
import { read, signal, write } from '~/system';
|
|
3
|
-
import { REACTIVE_ARRAY
|
|
3
|
+
import { REACTIVE_ARRAY } from '~/constants';
|
|
4
4
|
import type { Signal } from '~/types';
|
|
5
|
+
import { isReactiveObject } from './object';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
type Events<T> = {
|
|
@@ -45,7 +46,7 @@ type Listeners = Record<string, (Listener<any> | null)[]>;
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
function dispose(value: unknown) {
|
|
48
|
-
if (
|
|
49
|
+
if (isReactiveObject(value)) {
|
|
49
50
|
(value as { dispose(): void }).dispose();
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -83,6 +84,7 @@ class ReactiveArray<T> extends Array<T> {
|
|
|
83
84
|
this.dispatch('set', { index: i, item: value });
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
|
|
86
88
|
clear() {
|
|
87
89
|
this.dispose();
|
|
88
90
|
write(this._length, 0);
|
package/src/reactive/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { onCleanup, root } from '@esportsplus/reactivity';
|
|
2
|
+
import { isArray, isObject } from '@esportsplus/utilities';
|
|
3
3
|
import { Reactive } from '~/types';
|
|
4
|
+
import { ReactiveArray } from './array';
|
|
5
|
+
import { ReactiveObject } from './object';
|
|
6
|
+
import { PACKAGE } from '~/constants';
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
type Guard<T> =
|
|
@@ -11,14 +14,38 @@ type Guard<T> =
|
|
|
11
14
|
: never;
|
|
12
15
|
|
|
13
16
|
|
|
14
|
-
function reactive<T extends
|
|
15
|
-
function reactive<T
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function reactive<T extends unknown[]>(input: T): Reactive<T>;
|
|
18
|
+
function reactive<T extends Record<PropertyKey, unknown>>(input: Guard<T>): Reactive<T>;
|
|
19
|
+
function reactive<T extends unknown[] | Record<PropertyKey, unknown>>(input: T): Reactive<T> {
|
|
20
|
+
let dispose = false,
|
|
21
|
+
value = root(() => {
|
|
22
|
+
let response: Reactive<T> | undefined;
|
|
23
|
+
|
|
24
|
+
if (isObject(input)) {
|
|
25
|
+
response = new ReactiveObject(input) as any as Reactive<T>;
|
|
26
|
+
}
|
|
27
|
+
else if (isArray(input)) {
|
|
28
|
+
response = new ReactiveArray(...input) as any as Reactive<T>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (response) {
|
|
32
|
+
if (root.disposables) {
|
|
33
|
+
dispose = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error(`${PACKAGE}: 'reactive' received invalid input - ${JSON.stringify(input)}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (dispose) {
|
|
43
|
+
onCleanup(() => value.dispose());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return value;
|
|
20
47
|
}
|
|
21
48
|
|
|
22
49
|
|
|
23
50
|
export default reactive;
|
|
24
|
-
export { reactive, ReactiveArray };
|
|
51
|
+
export { reactive, ReactiveArray, ReactiveObject };
|