@esportsplus/reactivity 0.24.5 → 0.25.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/build/reactive/index.d.ts +9 -4
- package/build/transformer/index.d.ts +6 -4
- package/build/transformer/index.js +39 -106
- package/build/transformer/plugins/vite.js +4 -7
- package/build/transformer/transforms/array.d.ts +2 -2
- package/build/transformer/transforms/array.js +22 -23
- package/build/transformer/transforms/object.d.ts +3 -9
- package/build/transformer/transforms/object.js +99 -89
- package/build/transformer/transforms/primitives.d.ts +3 -3
- package/build/transformer/transforms/primitives.js +193 -142
- package/build/transformer/transforms/utilities.d.ts +8 -0
- package/build/transformer/transforms/utilities.js +27 -0
- package/build/types.d.ts +7 -6
- package/package.json +1 -1
- package/src/reactive/index.ts +24 -6
- package/src/transformer/index.ts +45 -187
- package/src/transformer/plugins/vite.ts +5 -13
- package/src/transformer/transforms/array.ts +31 -40
- package/src/transformer/transforms/object.ts +137 -294
- package/src/transformer/transforms/primitives.ts +245 -242
- package/src/transformer/transforms/utilities.ts +45 -0
- package/src/types.ts +9 -8
- package/test/vite.config.ts +1 -1
- package/build/transformer/factory.d.ts +0 -13
- package/build/transformer/factory.js +0 -36
- package/src/transformer/factory.ts +0 -141
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { Prettify } from '@esportsplus/utilities';
|
|
1
2
|
import { REACTIVE_OBJECT } from '../constants.js';
|
|
2
3
|
import { ReactiveArray } from './array.js';
|
|
3
4
|
declare const READONLY: unique symbol;
|
|
4
|
-
type ReactiveObject<T extends Record<PropertyKey, unknown
|
|
5
|
+
type Infer<T> = T extends (...args: unknown[]) => Promise<infer R> ? R | undefined : T extends (...args: any[]) => infer R ? R : T extends (infer U)[] ? ReactiveArray<U> : T extends ReactiveObject<any> ? T : T extends Record<PropertyKey, unknown> ? {
|
|
6
|
+
[K in keyof T]: T[K];
|
|
7
|
+
} : T;
|
|
8
|
+
type ReactiveObject<T> = T extends Record<PropertyKey, unknown> ? Prettify<{
|
|
9
|
+
[K in keyof T]: Infer<T[K]>;
|
|
10
|
+
} & {
|
|
5
11
|
[REACTIVE_OBJECT]: true;
|
|
6
|
-
dispose
|
|
7
|
-
};
|
|
12
|
+
dispose: VoidFunction;
|
|
13
|
+
}> : T extends (infer U)[] ? ReactiveArray<U> : never;
|
|
8
14
|
type ReactiveObjectGuard<T> = T extends {
|
|
9
15
|
dispose: any;
|
|
10
16
|
} ? {
|
|
@@ -15,7 +21,6 @@ declare function reactive<T extends () => unknown>(_input: T): ReturnType<T> & {
|
|
|
15
21
|
};
|
|
16
22
|
declare function reactive<T extends Record<PropertyKey, any>>(_input: ReactiveObjectGuard<T>): ReactiveObject<T>;
|
|
17
23
|
declare function reactive<T>(_input: T[]): ReactiveArray<T>;
|
|
18
|
-
declare function reactive<T>(_input: T): T;
|
|
19
24
|
export default reactive;
|
|
20
25
|
export { reactive, ReactiveArray };
|
|
21
26
|
export type { ReactiveObject };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { TransformResult } from '../types.js';
|
|
1
2
|
import { mightNeedTransform } from './detector.js';
|
|
2
3
|
import { ts } from '@esportsplus/typescript';
|
|
3
4
|
declare const createTransformer: () => ts.TransformerFactory<ts.SourceFile>;
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
5
|
+
declare const transform: (sourceFile: ts.SourceFile) => TransformResult;
|
|
6
|
+
export { createTransformer, mightNeedTransform, transform };
|
|
7
|
+
export { transformReactiveArrays } from './transforms/array.js';
|
|
8
|
+
export { transformReactiveObjects } from './transforms/object.js';
|
|
9
|
+
export { transformReactivePrimitives } from './transforms/primitives.js';
|
|
@@ -1,113 +1,46 @@
|
|
|
1
|
-
import { uid } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import { createArrayTransformer } from './transforms/array.js';
|
|
3
|
-
import { createObjectTransformer } from './transforms/object.js';
|
|
4
|
-
import { createPrimitivesTransformer } from './transforms/primitives.js';
|
|
5
1
|
import { mightNeedTransform } from './detector.js';
|
|
2
|
+
import { transformReactiveArrays } from './transforms/array.js';
|
|
3
|
+
import { transformReactiveObjects } from './transforms/object.js';
|
|
4
|
+
import { transformReactivePrimitives } from './transforms/primitives.js';
|
|
6
5
|
import { ts } from '@esportsplus/typescript';
|
|
7
|
-
function addImportsTransformer(neededImports, ns) {
|
|
8
|
-
return (context) => {
|
|
9
|
-
return (sourceFile) => {
|
|
10
|
-
if (neededImports.size === 0) {
|
|
11
|
-
return sourceFile;
|
|
12
|
-
}
|
|
13
|
-
let factory = context.factory, needsArray = false, needsConstants = false, needsReactivity = false, newStatements = [];
|
|
14
|
-
for (let imp of neededImports) {
|
|
15
|
-
if (imp === 'ReactiveArray') {
|
|
16
|
-
needsArray = true;
|
|
17
|
-
}
|
|
18
|
-
else if (imp === 'REACTIVE_OBJECT') {
|
|
19
|
-
needsConstants = true;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
needsReactivity = true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (needsReactivity) {
|
|
26
|
-
newStatements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(ns.reactivity))), factory.createStringLiteral('@esportsplus/reactivity')));
|
|
27
|
-
}
|
|
28
|
-
if (needsArray) {
|
|
29
|
-
newStatements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(ns.array))), factory.createStringLiteral('@esportsplus/reactivity/reactive/array')));
|
|
30
|
-
}
|
|
31
|
-
if (needsConstants) {
|
|
32
|
-
newStatements.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(ns.constants))), factory.createStringLiteral('@esportsplus/reactivity/constants')));
|
|
33
|
-
}
|
|
34
|
-
let filteredStatements = [], insertIndex = 0, statements = sourceFile.statements;
|
|
35
|
-
for (let i = 0, n = statements.length; i < n; i++) {
|
|
36
|
-
let stmt = statements[i];
|
|
37
|
-
if (ts.isImportDeclaration(stmt)) {
|
|
38
|
-
insertIndex = filteredStatements.length + 1;
|
|
39
|
-
if (ts.isStringLiteral(stmt.moduleSpecifier)) {
|
|
40
|
-
let module = stmt.moduleSpecifier.text;
|
|
41
|
-
if (module === '@esportsplus/reactivity' ||
|
|
42
|
-
module === '@esportsplus/reactivity/reactive/array' ||
|
|
43
|
-
module === '@esportsplus/reactivity/constants') {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
filteredStatements.push(stmt);
|
|
49
|
-
}
|
|
50
|
-
let updatedStatements = [
|
|
51
|
-
...filteredStatements.slice(0, insertIndex),
|
|
52
|
-
...newStatements,
|
|
53
|
-
...filteredStatements.slice(insertIndex)
|
|
54
|
-
];
|
|
55
|
-
return factory.updateSourceFile(sourceFile, updatedStatements);
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function insertClassesTransformer(generatedClasses) {
|
|
60
|
-
return (context) => {
|
|
61
|
-
return (sourceFile) => {
|
|
62
|
-
if (generatedClasses.length === 0) {
|
|
63
|
-
return sourceFile;
|
|
64
|
-
}
|
|
65
|
-
let factory = context.factory;
|
|
66
|
-
let insertIndex = 0, statements = sourceFile.statements;
|
|
67
|
-
for (let i = 0, n = statements.length; i < n; i++) {
|
|
68
|
-
if (ts.isImportDeclaration(statements[i])) {
|
|
69
|
-
insertIndex = i + 1;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
let classDecls = generatedClasses.map(gc => gc.classDecl), updatedStatements = [
|
|
76
|
-
...statements.slice(0, insertIndex),
|
|
77
|
-
...classDecls,
|
|
78
|
-
...statements.slice(insertIndex)
|
|
79
|
-
];
|
|
80
|
-
return factory.updateSourceFile(sourceFile, updatedStatements);
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
6
|
const createTransformer = () => {
|
|
85
|
-
return (
|
|
7
|
+
return () => {
|
|
86
8
|
return (sourceFile) => {
|
|
87
|
-
let
|
|
88
|
-
|
|
89
|
-
return sourceFile;
|
|
90
|
-
}
|
|
91
|
-
let bindings = new Map(), generatedClasses = [], neededImports = new Set(), ns = {
|
|
92
|
-
array: uid('ra'),
|
|
93
|
-
constants: uid('rc'),
|
|
94
|
-
reactivity: uid('r')
|
|
95
|
-
};
|
|
96
|
-
let objectTransformer = createObjectTransformer(bindings, neededImports, generatedClasses, ns)(context);
|
|
97
|
-
sourceFile = objectTransformer(sourceFile);
|
|
98
|
-
let arrayTransformer = createArrayTransformer(bindings)(context);
|
|
99
|
-
sourceFile = arrayTransformer(sourceFile);
|
|
100
|
-
let primitivesTransformer = createPrimitivesTransformer(bindings, neededImports, ns)(context);
|
|
101
|
-
sourceFile = primitivesTransformer(sourceFile);
|
|
102
|
-
let classInserter = insertClassesTransformer(generatedClasses)(context);
|
|
103
|
-
sourceFile = classInserter(sourceFile);
|
|
104
|
-
let importAdder = addImportsTransformer(neededImports, ns)(context);
|
|
105
|
-
sourceFile = importAdder(sourceFile);
|
|
106
|
-
return sourceFile;
|
|
9
|
+
let result = transform(sourceFile);
|
|
10
|
+
return result.transformed ? result.sourceFile : sourceFile;
|
|
107
11
|
};
|
|
108
12
|
};
|
|
109
13
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
14
|
+
const transform = (sourceFile) => {
|
|
15
|
+
let bindings = new Map(), code = sourceFile.getFullText(), current = sourceFile, original = code, result;
|
|
16
|
+
if (!mightNeedTransform(code)) {
|
|
17
|
+
return { code, sourceFile, transformed: false };
|
|
18
|
+
}
|
|
19
|
+
result = transformReactiveObjects(current, bindings);
|
|
20
|
+
if (result !== code) {
|
|
21
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
22
|
+
code = result;
|
|
23
|
+
}
|
|
24
|
+
result = transformReactiveArrays(current, bindings);
|
|
25
|
+
if (result !== code) {
|
|
26
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
27
|
+
code = result;
|
|
28
|
+
}
|
|
29
|
+
result = transformReactivePrimitives(current, bindings);
|
|
30
|
+
if (result !== code) {
|
|
31
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
32
|
+
code = result;
|
|
33
|
+
}
|
|
34
|
+
if (code === original) {
|
|
35
|
+
return { code, sourceFile, transformed: false };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
code,
|
|
39
|
+
sourceFile: current,
|
|
40
|
+
transformed: true
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export { createTransformer, mightNeedTransform, transform };
|
|
44
|
+
export { transformReactiveArrays } from './transforms/array.js';
|
|
45
|
+
export { transformReactiveObjects } from './transforms/object.js';
|
|
46
|
+
export { transformReactivePrimitives } from './transforms/primitives.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
|
|
2
|
-
import {
|
|
2
|
+
import { mightNeedTransform, transform } from '../../transformer/index.js';
|
|
3
3
|
import { ts } from '@esportsplus/typescript';
|
|
4
4
|
export default () => {
|
|
5
5
|
return {
|
|
@@ -13,14 +13,11 @@ export default () => {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
try {
|
|
16
|
-
let
|
|
17
|
-
if (transformed
|
|
18
|
-
result.dispose();
|
|
16
|
+
let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile);
|
|
17
|
+
if (!result.transformed) {
|
|
19
18
|
return null;
|
|
20
19
|
}
|
|
21
|
-
|
|
22
|
-
result.dispose();
|
|
23
|
-
return { code: output, map: null };
|
|
20
|
+
return { code: result.code, map: null };
|
|
24
21
|
}
|
|
25
22
|
catch (error) {
|
|
26
23
|
console.error(`@esportsplus/reactivity: Error transforming ${id}:`, error);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Bindings } from '../../types.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
declare const
|
|
4
|
-
export {
|
|
3
|
+
declare const transformReactiveArrays: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
|
|
4
|
+
export { transformReactiveArrays };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { applyReplacements } from './utilities.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
3
|
function getExpressionName(node) {
|
|
4
4
|
if (ts.isIdentifier(node)) {
|
|
@@ -23,9 +23,6 @@ function getPropertyPath(node) {
|
|
|
23
23
|
}
|
|
24
24
|
function isAssignmentTarget(node) {
|
|
25
25
|
let parent = node.parent;
|
|
26
|
-
if (!parent) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
26
|
if ((ts.isBinaryExpression(parent) && parent.left === node) ||
|
|
30
27
|
ts.isPostfixUnaryExpression(parent) ||
|
|
31
28
|
ts.isPrefixUnaryExpression(parent)) {
|
|
@@ -34,9 +31,6 @@ function isAssignmentTarget(node) {
|
|
|
34
31
|
return false;
|
|
35
32
|
}
|
|
36
33
|
function visit(ctx, node) {
|
|
37
|
-
if (ts.isImportDeclaration(node)) {
|
|
38
|
-
return node;
|
|
39
|
-
}
|
|
40
34
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
41
35
|
if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === 'array') {
|
|
42
36
|
ctx.bindings.set(node.name.text, 'array');
|
|
@@ -64,8 +58,12 @@ function visit(ctx, node) {
|
|
|
64
58
|
!isAssignmentTarget(node)) {
|
|
65
59
|
let name = getExpressionName(node.expression);
|
|
66
60
|
if (name && ctx.bindings.get(name) === 'array') {
|
|
67
|
-
let
|
|
68
|
-
|
|
61
|
+
let objText = node.expression.getText(ctx.sourceFile);
|
|
62
|
+
ctx.replacements.push({
|
|
63
|
+
end: node.end,
|
|
64
|
+
newText: `${objText}.$length()`,
|
|
65
|
+
start: node.pos
|
|
66
|
+
});
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
if (ts.isBinaryExpression(node) &&
|
|
@@ -73,22 +71,23 @@ function visit(ctx, node) {
|
|
|
73
71
|
ts.isElementAccessExpression(node.left)) {
|
|
74
72
|
let elemAccess = node.left, objName = getExpressionName(elemAccess.expression);
|
|
75
73
|
if (objName && ctx.bindings.get(objName) === 'array') {
|
|
76
|
-
let
|
|
77
|
-
|
|
74
|
+
let indexText = elemAccess.argumentExpression.getText(ctx.sourceFile), objText = elemAccess.expression.getText(ctx.sourceFile), valueText = node.right.getText(ctx.sourceFile);
|
|
75
|
+
ctx.replacements.push({
|
|
76
|
+
end: node.end,
|
|
77
|
+
newText: `${objText}.$set(${indexText}, ${valueText})`,
|
|
78
|
+
start: node.pos
|
|
79
|
+
});
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
|
-
|
|
82
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
81
83
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
context,
|
|
88
|
-
factory: context.factory
|
|
89
|
-
};
|
|
90
|
-
return ts.visitNode(sourceFile, n => visit(ctx, n));
|
|
91
|
-
};
|
|
84
|
+
const transformReactiveArrays = (sourceFile, bindings) => {
|
|
85
|
+
let code = sourceFile.getFullText(), ctx = {
|
|
86
|
+
bindings,
|
|
87
|
+
replacements: [],
|
|
88
|
+
sourceFile
|
|
92
89
|
};
|
|
90
|
+
visit(ctx, sourceFile);
|
|
91
|
+
return applyReplacements(code, ctx.replacements);
|
|
93
92
|
};
|
|
94
|
-
export {
|
|
93
|
+
export { transformReactiveArrays };
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type { Bindings
|
|
1
|
+
import type { Bindings } from '../../types.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
className: string;
|
|
6
|
-
needsImports: Set<string>;
|
|
7
|
-
}
|
|
8
|
-
declare const createObjectTransformer: (bindings: Bindings, neededImports: Set<string>, generatedClasses: GeneratedClass[], ns: Namespaces) => (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile;
|
|
9
|
-
export { createObjectTransformer };
|
|
10
|
-
export type { GeneratedClass };
|
|
3
|
+
declare const transformReactiveObjects: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
|
|
4
|
+
export { transformReactiveObjects };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { uid } from '@esportsplus/typescript/transformer';
|
|
2
|
+
import { addMissingImports, applyReplacements } from './utilities.js';
|
|
2
3
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
|
|
4
|
+
const CLASS_NAME_REGEX = /class (\w+)/;
|
|
5
|
+
const EXTRA_IMPORTS = [
|
|
6
|
+
{ module: '@esportsplus/reactivity/constants', specifier: 'REACTIVE_OBJECT' },
|
|
7
|
+
{ module: '@esportsplus/reactivity/reactive/array', specifier: 'ReactiveArray' }
|
|
8
|
+
];
|
|
9
|
+
function analyzeProperty(prop, sourceFile) {
|
|
4
10
|
if (!ts.isPropertyAssignment(prop)) {
|
|
5
11
|
return null;
|
|
6
12
|
}
|
|
@@ -11,71 +17,51 @@ function analyzeProperty(prop) {
|
|
|
11
17
|
else {
|
|
12
18
|
return null;
|
|
13
19
|
}
|
|
14
|
-
let value = prop.initializer;
|
|
20
|
+
let value = prop.initializer, valueText = value.getText(sourceFile);
|
|
15
21
|
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
16
|
-
return { key, type: 'computed',
|
|
22
|
+
return { key, type: 'computed', valueText };
|
|
17
23
|
}
|
|
18
24
|
if (ts.isArrayLiteralExpression(value)) {
|
|
19
|
-
return {
|
|
25
|
+
return { key, type: 'array', valueText };
|
|
20
26
|
}
|
|
21
|
-
return { key, type: 'signal',
|
|
27
|
+
return { key, type: 'signal', valueText };
|
|
22
28
|
}
|
|
23
|
-
function
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
members.push(factory.createPropertyDeclaration(undefined, factory.createComputedPropertyName(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.constants), 'REACTIVE_OBJECT')), undefined, undefined, factory.createTrue()));
|
|
27
|
-
let disposeStatements = [];
|
|
29
|
+
function buildClassCode(className, properties) {
|
|
30
|
+
let accessors = [], disposeStatements = [], fields = [];
|
|
31
|
+
fields.push(`[REACTIVE_OBJECT] = true;`);
|
|
28
32
|
for (let i = 0, n = properties.length; i < n; i++) {
|
|
29
|
-
let
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
members.push(factory.createPropertyDeclaration(undefined, privateName, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'signal'), undefined, [prop.value])));
|
|
36
|
-
members.push(factory.createGetAccessorDeclaration(undefined, factory.createIdentifier(prop.key), [], undefined, factory.createBlock([
|
|
37
|
-
factory.createReturnStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'read'), undefined, [factory.createPropertyAccessExpression(factory.createThis(), privateName)]))
|
|
38
|
-
], true)));
|
|
39
|
-
members.push(factory.createSetAccessorDeclaration(undefined, factory.createIdentifier(prop.key), [factory.createParameterDeclaration(undefined, undefined, paramName)], factory.createBlock([
|
|
40
|
-
factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'set'), undefined, [
|
|
41
|
-
factory.createPropertyAccessExpression(factory.createThis(), privateName),
|
|
42
|
-
factory.createIdentifier(paramName)
|
|
43
|
-
]))
|
|
44
|
-
], true)));
|
|
33
|
+
let { key, type, valueText } = properties[i];
|
|
34
|
+
if (type === 'signal') {
|
|
35
|
+
let param = uid('v');
|
|
36
|
+
fields.push(`#${key} = signal(${valueText});`);
|
|
37
|
+
accessors.push(`get ${key}() { return read(this.#${key}); }`);
|
|
38
|
+
accessors.push(`set ${key}(${param}) { set(this.#${key}, ${param}); }`);
|
|
45
39
|
}
|
|
46
|
-
else if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
ctx.bindings.set(`${varName}.${prop.key}`, 'array');
|
|
51
|
-
}
|
|
52
|
-
disposeStatements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createThis(), prop.key), 'dispose'), undefined, [])));
|
|
40
|
+
else if (type === 'array') {
|
|
41
|
+
let elements = valueText.slice(1, -1);
|
|
42
|
+
fields.push(`${key} = new ReactiveArray(${elements});`);
|
|
43
|
+
disposeStatements.push(`this.${key}.dispose();`);
|
|
53
44
|
}
|
|
54
|
-
else if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let privateName = factory.createPrivateIdentifier(`#${prop.key}`);
|
|
59
|
-
members.push(factory.createPropertyDeclaration(undefined, privateName, undefined, factory.createUnionTypeNode([
|
|
60
|
-
factory.createTypeReferenceNode('Computed', [
|
|
61
|
-
factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
|
|
62
|
-
]),
|
|
63
|
-
factory.createLiteralTypeNode(factory.createNull())
|
|
64
|
-
]), factory.createNull()));
|
|
65
|
-
members.push(factory.createGetAccessorDeclaration(undefined, factory.createIdentifier(prop.key), [], undefined, factory.createBlock([
|
|
66
|
-
factory.createReturnStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'read'), undefined, [
|
|
67
|
-
factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createThis(), privateName), ts.SyntaxKind.QuestionQuestionEqualsToken, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'computed'), undefined, [prop.value]))
|
|
68
|
-
]))
|
|
69
|
-
], true)));
|
|
70
|
-
disposeStatements.push(factory.createIfStatement(factory.createPropertyAccessExpression(factory.createThis(), privateName), factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(ctx.ns.reactivity), 'dispose'), undefined, [factory.createPropertyAccessExpression(factory.createThis(), privateName)]))));
|
|
45
|
+
else if (type === 'computed') {
|
|
46
|
+
fields.push(`#${key}: Computed<unknown> | null = null;`);
|
|
47
|
+
accessors.push(`get ${key}() { return read(this.#${key} ??= computed(${valueText})); }`);
|
|
48
|
+
disposeStatements.push(`if (this.#${key}) dispose(this.#${key});`);
|
|
71
49
|
}
|
|
72
50
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
51
|
+
return `
|
|
52
|
+
class ${className} {
|
|
53
|
+
${fields.join('\n')}
|
|
54
|
+
${accessors.join('\n')}
|
|
55
|
+
|
|
56
|
+
dispose() {
|
|
57
|
+
${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
76
61
|
}
|
|
77
62
|
function visit(ctx, node) {
|
|
78
63
|
if (ts.isImportDeclaration(node)) {
|
|
64
|
+
ctx.lastImportEnd = node.end;
|
|
79
65
|
if (ts.isStringLiteral(node.moduleSpecifier) &&
|
|
80
66
|
node.moduleSpecifier.text.includes('@esportsplus/reactivity')) {
|
|
81
67
|
let clause = node.importClause;
|
|
@@ -89,65 +75,89 @@ function visit(ctx, node) {
|
|
|
89
75
|
}
|
|
90
76
|
}
|
|
91
77
|
}
|
|
92
|
-
return node;
|
|
93
78
|
}
|
|
94
79
|
if (ctx.hasReactiveImport &&
|
|
95
80
|
ts.isCallExpression(node) &&
|
|
96
81
|
ts.isIdentifier(node.expression) &&
|
|
97
82
|
node.expression.text === 'reactive') {
|
|
98
83
|
let arg = node.arguments[0];
|
|
99
|
-
if (arg && ts.isArrayLiteralExpression(arg)) {
|
|
100
|
-
let varName = null;
|
|
101
|
-
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
102
|
-
varName = node.parent.name.text;
|
|
103
|
-
ctx.bindings.set(varName, 'array');
|
|
104
|
-
}
|
|
105
|
-
ctx.neededImports.add('ReactiveArray');
|
|
106
|
-
return ctx.factory.createNewExpression(ctx.factory.createPropertyAccessExpression(ctx.factory.createIdentifier(ctx.ns.array), 'ReactiveArray'), undefined, [...arg.elements]);
|
|
107
|
-
}
|
|
108
84
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
109
85
|
let varName = null;
|
|
110
86
|
if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
111
87
|
varName = node.parent.name.text;
|
|
112
88
|
ctx.bindings.set(varName, 'object');
|
|
113
89
|
}
|
|
114
|
-
let
|
|
90
|
+
let needsImports = new Set(), properties = [];
|
|
91
|
+
needsImports.add('REACTIVE_OBJECT');
|
|
92
|
+
let props = arg.properties;
|
|
115
93
|
for (let i = 0, n = props.length; i < n; i++) {
|
|
116
94
|
let prop = props[i];
|
|
117
95
|
if (ts.isSpreadAssignment(prop)) {
|
|
118
|
-
|
|
96
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
97
|
+
return;
|
|
119
98
|
}
|
|
120
|
-
let analyzed = analyzeProperty(prop);
|
|
99
|
+
let analyzed = analyzeProperty(prop, ctx.sourceFile);
|
|
121
100
|
if (!analyzed) {
|
|
122
|
-
|
|
101
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
102
|
+
return;
|
|
123
103
|
}
|
|
124
104
|
properties.push(analyzed);
|
|
105
|
+
if (analyzed.type === 'signal') {
|
|
106
|
+
needsImports.add('read');
|
|
107
|
+
needsImports.add('set');
|
|
108
|
+
needsImports.add('signal');
|
|
109
|
+
}
|
|
110
|
+
else if (analyzed.type === 'array') {
|
|
111
|
+
needsImports.add('ReactiveArray');
|
|
112
|
+
if (varName) {
|
|
113
|
+
ctx.bindings.set(`${varName}.${analyzed.key}`, 'array');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (analyzed.type === 'computed') {
|
|
117
|
+
needsImports.add('computed');
|
|
118
|
+
needsImports.add('dispose');
|
|
119
|
+
needsImports.add('read');
|
|
120
|
+
}
|
|
125
121
|
}
|
|
126
|
-
|
|
127
|
-
ctx.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
needsImports
|
|
122
|
+
needsImports.forEach(imp => ctx.allNeededImports.add(imp));
|
|
123
|
+
ctx.calls.push({
|
|
124
|
+
end: node.end,
|
|
125
|
+
generatedClass: buildClassCode(uid('ReactiveObject'), properties),
|
|
126
|
+
needsImports,
|
|
127
|
+
start: node.pos,
|
|
128
|
+
varName
|
|
131
129
|
});
|
|
132
|
-
return ctx.factory.createNewExpression(ctx.factory.createIdentifier(className), undefined, []);
|
|
133
130
|
}
|
|
134
131
|
}
|
|
135
|
-
|
|
132
|
+
ts.forEachChild(node, n => visit(ctx, n));
|
|
136
133
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
hasReactiveImport: false,
|
|
146
|
-
neededImports,
|
|
147
|
-
ns
|
|
148
|
-
};
|
|
149
|
-
return ts.visitNode(sourceFile, n => visit(ctx, n));
|
|
150
|
-
};
|
|
134
|
+
const transformReactiveObjects = (sourceFile, bindings) => {
|
|
135
|
+
let code = sourceFile.getFullText(), ctx = {
|
|
136
|
+
allNeededImports: new Set(),
|
|
137
|
+
bindings,
|
|
138
|
+
calls: [],
|
|
139
|
+
hasReactiveImport: false,
|
|
140
|
+
lastImportEnd: 0,
|
|
141
|
+
sourceFile
|
|
151
142
|
};
|
|
143
|
+
visit(ctx, sourceFile);
|
|
144
|
+
if (ctx.calls.length === 0) {
|
|
145
|
+
return code;
|
|
146
|
+
}
|
|
147
|
+
let replacements = [];
|
|
148
|
+
replacements.push({
|
|
149
|
+
end: ctx.lastImportEnd,
|
|
150
|
+
newText: code.substring(0, ctx.lastImportEnd) + '\n' + ctx.calls.map(c => c.generatedClass).join('\n') + '\n',
|
|
151
|
+
start: 0
|
|
152
|
+
});
|
|
153
|
+
for (let i = 0, n = ctx.calls.length; i < n; i++) {
|
|
154
|
+
let call = ctx.calls[i], classMatch = call.generatedClass.match(CLASS_NAME_REGEX);
|
|
155
|
+
replacements.push({
|
|
156
|
+
end: call.end,
|
|
157
|
+
newText: ` new ${classMatch ? classMatch[1] : 'ReactiveObject'}()`,
|
|
158
|
+
start: call.start
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return addMissingImports(applyReplacements(code, replacements), ctx.allNeededImports, EXTRA_IMPORTS);
|
|
152
162
|
};
|
|
153
|
-
export {
|
|
163
|
+
export { transformReactiveObjects };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Bindings
|
|
1
|
+
import type { Bindings } from '../../types.js';
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
declare const
|
|
4
|
-
export {
|
|
3
|
+
declare const transformReactivePrimitives: (sourceFile: ts.SourceFile, bindings: Bindings) => string;
|
|
4
|
+
export { transformReactivePrimitives };
|