@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
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { Bindings } from '../types.js';
|
|
3
|
-
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
3
|
+
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => string;
|
|
4
4
|
export default _default;
|
package/build/compiler/array.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import { ast, code as c } from '@esportsplus/typescript/compiler';
|
|
2
1
|
import { ts } from '@esportsplus/typescript';
|
|
3
|
-
import {
|
|
2
|
+
import { ast, code as c } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '../constants.js';
|
|
4
|
+
import { isReactiveCall } from './index.js';
|
|
4
5
|
function visit(ctx, node) {
|
|
6
|
+
if (ts.isCallExpression(node) && isReactiveCall(node, ctx.checker) && node.arguments.length > 0) {
|
|
7
|
+
let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
|
|
8
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
9
|
+
if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
|
|
10
|
+
ctx.bindings.set(node.parent.name.text, COMPILER_TYPES.Array);
|
|
11
|
+
}
|
|
12
|
+
ctx.replacements.push({
|
|
13
|
+
end: node.end,
|
|
14
|
+
newText: expression.elements.length > 0
|
|
15
|
+
? ` new ${COMPILER_NAMESPACE}.ReactiveArray(...${expression.getText(ctx.sourceFile)})`
|
|
16
|
+
: ` new ${COMPILER_NAMESPACE}.ReactiveArray()`,
|
|
17
|
+
start: node.pos
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
5
21
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
6
22
|
if (ts.isIdentifier(node.initializer) && ctx.bindings.get(node.initializer.text) === COMPILER_TYPES.Array) {
|
|
7
23
|
ctx.bindings.set(node.name.text, COMPILER_TYPES.Array);
|
|
@@ -42,24 +58,28 @@ function visit(ctx, node) {
|
|
|
42
58
|
if (ts.isBinaryExpression(node) &&
|
|
43
59
|
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
44
60
|
ts.isElementAccessExpression(node.left)) {
|
|
45
|
-
let
|
|
46
|
-
if (
|
|
47
|
-
let index =
|
|
61
|
+
let element = node.left, name = ast.getExpressionName(element.expression);
|
|
62
|
+
if (name && ctx.bindings.get(name) === COMPILER_TYPES.Array) {
|
|
63
|
+
let index = element.argumentExpression.getText(ctx.sourceFile), value = node.right.getText(ctx.sourceFile);
|
|
48
64
|
ctx.replacements.push({
|
|
49
65
|
end: node.end,
|
|
50
|
-
newText: `${
|
|
66
|
+
newText: `${element.expression.getText(ctx.sourceFile)}.$set(${index}, ${value})`,
|
|
51
67
|
start: node.pos
|
|
52
68
|
});
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
ts.forEachChild(node, n => visit(ctx, n));
|
|
56
72
|
}
|
|
57
|
-
export default (sourceFile, bindings,
|
|
73
|
+
export default (sourceFile, bindings, checker) => {
|
|
58
74
|
let code = sourceFile.getFullText(), ctx = {
|
|
59
75
|
bindings,
|
|
76
|
+
checker,
|
|
60
77
|
replacements: [],
|
|
61
78
|
sourceFile
|
|
62
79
|
};
|
|
63
80
|
visit(ctx, sourceFile);
|
|
81
|
+
if (ctx.replacements.length === 0) {
|
|
82
|
+
return code;
|
|
83
|
+
}
|
|
64
84
|
return c.replace(code, ctx.replacements);
|
|
65
85
|
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { PluginContext } from '@esportsplus/typescript/compiler';
|
|
1
2
|
import { ts } from '@esportsplus/typescript';
|
|
2
3
|
import type { TransformResult } from '../types.js';
|
|
3
|
-
declare const
|
|
4
|
-
|
|
4
|
+
declare const analyze: (sourceFile: ts.SourceFile, _program: ts.Program, context: PluginContext) => void;
|
|
5
|
+
declare const isReactiveCall: (node: ts.CallExpression, _checker?: ts.TypeChecker) => boolean;
|
|
6
|
+
declare const transform: (sourceFile: ts.SourceFile, program: ts.Program, context?: PluginContext) => TransformResult;
|
|
7
|
+
export { analyze, isReactiveCall, transform };
|
package/build/compiler/index.js
CHANGED
|
@@ -1,61 +1,65 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import {
|
|
3
|
-
import { COMPILER_ENTRYPOINT,
|
|
2
|
+
import { ast, imports } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_ENTRYPOINT, COMPILER_NAMESPACE, PACKAGE } from '../constants.js';
|
|
4
4
|
import array from './array.js';
|
|
5
5
|
import object from './object.js';
|
|
6
|
-
|
|
7
|
-
let transforms = [object, array
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
visit(ctx, ts.createSourceFile('detect.ts', code, ts.ScriptTarget.Latest, false));
|
|
17
|
-
return ctx.imported && ctx.used;
|
|
6
|
+
const CONTEXT_KEY = 'reactivity:analyzed';
|
|
7
|
+
let transforms = [object, array];
|
|
8
|
+
function getAnalyzedFile(context, filename) {
|
|
9
|
+
return context?.get(CONTEXT_KEY)?.get(filename);
|
|
10
|
+
}
|
|
11
|
+
function hasReactiveImport(sourceFile) {
|
|
12
|
+
return imports.find(sourceFile, PACKAGE).some(i => i.specifiers.has(COMPILER_ENTRYPOINT));
|
|
13
|
+
}
|
|
14
|
+
function isReactiveCallNode(node) {
|
|
15
|
+
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === COMPILER_ENTRYPOINT;
|
|
18
16
|
}
|
|
19
|
-
|
|
20
|
-
if (
|
|
17
|
+
const analyze = (sourceFile, _program, context) => {
|
|
18
|
+
if (!hasReactiveImport(sourceFile)) {
|
|
21
19
|
return;
|
|
22
20
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
let files = context.get(CONTEXT_KEY);
|
|
22
|
+
if (!files) {
|
|
23
|
+
files = new Map();
|
|
24
|
+
context.set(CONTEXT_KEY, files);
|
|
25
|
+
}
|
|
26
|
+
files.set(sourceFile.fileName, {
|
|
27
|
+
hasReactiveImport: true
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
const isReactiveCall = (node, _checker) => {
|
|
31
|
+
if (!ts.isIdentifier(node.expression)) {
|
|
32
|
+
return false;
|
|
34
33
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
return node.expression.text === COMPILER_ENTRYPOINT;
|
|
35
|
+
};
|
|
36
|
+
const transform = (sourceFile, program, context) => {
|
|
37
|
+
let bindings = new Map(), changed = false, checker = program.getTypeChecker(), code = sourceFile.getFullText(), current = sourceFile, filename = sourceFile.fileName, result;
|
|
38
|
+
let analyzed = getAnalyzedFile(context, filename);
|
|
39
|
+
if (!analyzed) {
|
|
40
|
+
if (!hasReactiveImport(sourceFile)) {
|
|
41
|
+
return { changed: false, code, sourceFile };
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
const transform = (sourceFile) => {
|
|
43
|
-
let bindings = new Map(), changed = false, code = sourceFile.getFullText(), current = sourceFile, result;
|
|
44
|
-
if (!contains(code)) {
|
|
44
|
+
else if (!analyzed.hasReactiveImport) {
|
|
45
45
|
return { changed: false, code, sourceFile };
|
|
46
46
|
}
|
|
47
47
|
for (let i = 0, n = transforms.length; i < n; i++) {
|
|
48
|
-
result = transforms[i](current, bindings,
|
|
48
|
+
result = transforms[i](current, bindings, checker);
|
|
49
49
|
if (result !== code) {
|
|
50
|
-
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
51
50
|
code = result;
|
|
52
51
|
changed = true;
|
|
52
|
+
current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
if (changed) {
|
|
56
|
-
|
|
56
|
+
let remove = [];
|
|
57
|
+
if (!ast.hasMatch(current, isReactiveCallNode)) {
|
|
58
|
+
remove.push(COMPILER_ENTRYPOINT);
|
|
59
|
+
}
|
|
60
|
+
code = imports.modify(code, current, PACKAGE, { namespace: COMPILER_NAMESPACE, remove });
|
|
57
61
|
sourceFile = ts.createSourceFile(sourceFile.fileName, code, sourceFile.languageVersion, true);
|
|
58
62
|
}
|
|
59
63
|
return { changed, code, sourceFile };
|
|
60
64
|
};
|
|
61
|
-
export { transform };
|
|
65
|
+
export { analyze, isReactiveCall, transform };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
2
|
import type { Bindings } from '../types.js';
|
|
3
|
-
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings,
|
|
3
|
+
declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => string;
|
|
4
4
|
export default _default;
|
package/build/compiler/object.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { code as c } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import {
|
|
2
|
+
import { code as c, uid } from '@esportsplus/typescript/compiler';
|
|
3
|
+
import { COMPILER_NAMESPACE, COMPILER_TYPES } from '../constants.js';
|
|
4
|
+
import { isReactiveCall } from './index.js';
|
|
4
5
|
function analyzeProperty(prop, sourceFile) {
|
|
5
6
|
if (!ts.isPropertyAssignment(prop)) {
|
|
6
7
|
return null;
|
|
@@ -12,75 +13,110 @@ function analyzeProperty(prop, sourceFile) {
|
|
|
12
13
|
else {
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
let unwrapped = prop.initializer, value = unwrapped, valueText = value.getText(sourceFile);
|
|
17
|
+
while (ts.isAsExpression(unwrapped) || ts.isTypeAssertionExpression(unwrapped) || ts.isParenthesizedExpression(unwrapped)) {
|
|
18
|
+
unwrapped = unwrapped.expression;
|
|
18
19
|
}
|
|
19
|
-
if (ts.
|
|
20
|
-
|
|
20
|
+
if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped)) {
|
|
21
|
+
return { isStatic: false, key, type: COMPILER_TYPES.Computed, valueText };
|
|
22
|
+
}
|
|
23
|
+
if (ts.isArrayLiteralExpression(unwrapped)) {
|
|
24
|
+
let elements = unwrapped.elements, isStatic = value === unwrapped;
|
|
21
25
|
for (let i = 0, n = elements.length; i < n; i++) {
|
|
22
|
-
if (
|
|
23
|
-
|
|
26
|
+
if (isStatic && !isStaticValue(elements[i])) {
|
|
27
|
+
isStatic = false;
|
|
24
28
|
}
|
|
25
|
-
elementsText += elements[i].getText(sourceFile);
|
|
26
29
|
}
|
|
27
|
-
return { key, type: COMPILER_TYPES.Array, valueText
|
|
30
|
+
return { isStatic, key, type: COMPILER_TYPES.Array, valueText };
|
|
28
31
|
}
|
|
29
|
-
return { key, type: COMPILER_TYPES.Signal, valueText };
|
|
32
|
+
return { isStatic: isStaticValue(value), key, type: COMPILER_TYPES.Signal, valueText };
|
|
30
33
|
}
|
|
31
|
-
function buildClassCode(className, properties
|
|
32
|
-
let accessors = [],
|
|
33
|
-
fields.push(`[${ns}.REACTIVE_OBJECT] = true;`);
|
|
34
|
+
function buildClassCode(className, properties) {
|
|
35
|
+
let accessors = [], body = [], fields = [], generics = [], parameters = [], setters = 0;
|
|
34
36
|
for (let i = 0, n = properties.length; i < n; i++) {
|
|
35
|
-
let { key, type, valueText } = properties[i]
|
|
37
|
+
let { isStatic, key, type, valueText } = properties[i], generic = `T${parameters.length}`, parameter = `_p${parameters.length}`;
|
|
36
38
|
if (type === COMPILER_TYPES.Signal) {
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
let value = `_v${setters++}`;
|
|
40
|
+
if (isStatic) {
|
|
41
|
+
accessors.push(`
|
|
42
|
+
get ${key}() {
|
|
43
|
+
return ${COMPILER_NAMESPACE}.read(this.#${key});
|
|
44
|
+
}
|
|
45
|
+
set ${key}(${value}) {
|
|
46
|
+
${COMPILER_NAMESPACE}.write(this.#${key}, ${value});
|
|
47
|
+
}
|
|
48
|
+
`);
|
|
49
|
+
fields.push(`#${key} = this[${COMPILER_NAMESPACE}.SIGNAL](${valueText});`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
accessors.push(`
|
|
53
|
+
get ${key}() {
|
|
54
|
+
return ${COMPILER_NAMESPACE}.read(this.#${key}) as ${generic};
|
|
55
|
+
}
|
|
56
|
+
set ${key}(${value}) {
|
|
57
|
+
${COMPILER_NAMESPACE}.write(this.#${key}, ${value});
|
|
58
|
+
}
|
|
59
|
+
`);
|
|
60
|
+
body.push(`this.#${key} = this[${COMPILER_NAMESPACE}.SIGNAL](${parameter});`);
|
|
61
|
+
fields.push(`#${key};`);
|
|
62
|
+
generics.push(generic);
|
|
63
|
+
parameters.push(`${parameter}: ${generic}`);
|
|
64
|
+
}
|
|
41
65
|
}
|
|
42
66
|
else if (type === COMPILER_TYPES.Array) {
|
|
43
|
-
|
|
44
|
-
|
|
67
|
+
accessors.push(`
|
|
68
|
+
get ${key}() {
|
|
69
|
+
return this.#${key};
|
|
70
|
+
}
|
|
71
|
+
`);
|
|
72
|
+
body.push(`this.#${key} = this[${COMPILER_NAMESPACE}.REACTIVE_ARRAY](${parameter});`);
|
|
73
|
+
fields.push(`#${key};`);
|
|
74
|
+
generics.push(`${generic} extends unknown[]`);
|
|
75
|
+
parameters.push(`${parameter}: ${generic}`);
|
|
45
76
|
}
|
|
46
77
|
else if (type === COMPILER_TYPES.Computed) {
|
|
47
|
-
accessors.push(`
|
|
48
|
-
|
|
49
|
-
|
|
78
|
+
accessors.push(`
|
|
79
|
+
get ${key}() {
|
|
80
|
+
return ${COMPILER_NAMESPACE}.read(this.#${key});
|
|
81
|
+
}
|
|
82
|
+
`);
|
|
83
|
+
body.push(`this.#${key} = this[${COMPILER_NAMESPACE}.COMPUTED](${parameter});`);
|
|
84
|
+
fields.push(`#${key};`);
|
|
85
|
+
generics.push(`${generic} extends ${COMPILER_NAMESPACE}.Computed<ReturnType<${generic}>>['fn']`);
|
|
86
|
+
parameters.push(`${parameter}: ${generic}`);
|
|
50
87
|
}
|
|
51
88
|
}
|
|
52
89
|
return `
|
|
53
|
-
class ${className} {
|
|
90
|
+
class ${className}${generics.length > 0 ? `<${generics.join(', ')}>` : ''} extends ${COMPILER_NAMESPACE}.ReactiveObject<any> {
|
|
54
91
|
${fields.join('\n')}
|
|
55
|
-
${
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
|
|
92
|
+
constructor(${parameters.join(', ')}) {
|
|
93
|
+
super(null);
|
|
94
|
+
${body.join('\n')}
|
|
59
95
|
}
|
|
96
|
+
${accessors.join('\n')}
|
|
60
97
|
}
|
|
61
98
|
`;
|
|
62
99
|
}
|
|
100
|
+
function isStaticValue(node) {
|
|
101
|
+
if (ts.isNumericLiteral(node) ||
|
|
102
|
+
ts.isStringLiteral(node) ||
|
|
103
|
+
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
104
|
+
node.kind === ts.SyntaxKind.FalseKeyword ||
|
|
105
|
+
node.kind === ts.SyntaxKind.NullKeyword ||
|
|
106
|
+
node.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
63
114
|
function visit(ctx, node) {
|
|
64
115
|
if (ts.isImportDeclaration(node)) {
|
|
65
116
|
ctx.lastImportEnd = node.end;
|
|
66
|
-
if (ts.isStringLiteral(node.moduleSpecifier) &&
|
|
67
|
-
node.moduleSpecifier.text.includes(PACKAGE)) {
|
|
68
|
-
let clause = node.importClause;
|
|
69
|
-
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
70
|
-
let elements = clause.namedBindings.elements;
|
|
71
|
-
for (let i = 0, n = elements.length; i < n; i++) {
|
|
72
|
-
if (elements[i].name.text === 'reactive') {
|
|
73
|
-
ctx.hasReactiveImport = true;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
117
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
ts.isIdentifier(node.expression) &&
|
|
83
|
-
node.expression.text === 'reactive') {
|
|
118
|
+
if (ts.isCallExpression(node) &&
|
|
119
|
+
isReactiveCall(node, ctx.checker)) {
|
|
84
120
|
let arg = node.arguments[0];
|
|
85
121
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
86
122
|
let properties = [], props = arg.properties, varName = null;
|
|
@@ -105,7 +141,7 @@ function visit(ctx, node) {
|
|
|
105
141
|
}
|
|
106
142
|
}
|
|
107
143
|
ctx.calls.push({
|
|
108
|
-
className:
|
|
144
|
+
className: uid('ReactiveObject'),
|
|
109
145
|
end: node.end,
|
|
110
146
|
properties,
|
|
111
147
|
start: node.pos,
|
|
@@ -115,21 +151,20 @@ function visit(ctx, node) {
|
|
|
115
151
|
}
|
|
116
152
|
ts.forEachChild(node, n => visit(ctx, n));
|
|
117
153
|
}
|
|
118
|
-
export default (sourceFile, bindings,
|
|
154
|
+
export default (sourceFile, bindings, checker) => {
|
|
119
155
|
let code = sourceFile.getFullText(), ctx = {
|
|
120
156
|
bindings,
|
|
121
157
|
calls: [],
|
|
158
|
+
checker,
|
|
122
159
|
classCounter: 0,
|
|
123
|
-
hasReactiveImport: false,
|
|
124
160
|
lastImportEnd: 0,
|
|
125
|
-
ns,
|
|
126
161
|
sourceFile
|
|
127
162
|
};
|
|
128
163
|
visit(ctx, sourceFile);
|
|
129
164
|
if (ctx.calls.length === 0) {
|
|
130
165
|
return code;
|
|
131
166
|
}
|
|
132
|
-
let classes = ctx.calls.map(c => buildClassCode(c.className, c.properties
|
|
167
|
+
let classes = ctx.calls.map(c => buildClassCode(c.className, c.properties)).join('\n'), replacements = [];
|
|
133
168
|
replacements.push({
|
|
134
169
|
end: ctx.lastImportEnd,
|
|
135
170
|
newText: code.substring(0, ctx.lastImportEnd) + '\n' + classes + '\n',
|
|
@@ -139,7 +174,10 @@ export default (sourceFile, bindings, ns) => {
|
|
|
139
174
|
let call = ctx.calls[i];
|
|
140
175
|
replacements.push({
|
|
141
176
|
end: call.end,
|
|
142
|
-
newText: ` new ${call.className}(
|
|
177
|
+
newText: ` new ${call.className}(${call.properties
|
|
178
|
+
.filter(({ isStatic, type }) => !isStatic || type === COMPILER_TYPES.Computed)
|
|
179
|
+
.map(p => p.valueText)
|
|
180
|
+
.join(', ')})`,
|
|
143
181
|
start: call.start
|
|
144
182
|
});
|
|
145
183
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
2
|
-
import { transform } from '../index.js';
|
|
3
|
-
export default plugin.tsc(transform);
|
|
2
|
+
import { analyze, transform } from '../index.js';
|
|
3
|
+
export default plugin.tsc({ analyze, transform });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PACKAGE } from '../../constants.js';
|
|
2
2
|
import { plugin } from '@esportsplus/typescript/compiler';
|
|
3
|
-
import { transform } from '../index.js';
|
|
3
|
+
import { analyze, transform } from '../index.js';
|
|
4
4
|
export default plugin.vite({
|
|
5
|
+
analyze,
|
|
5
6
|
name: PACKAGE,
|
|
6
7
|
transform
|
|
7
8
|
});
|
package/build/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { uid } from '@esportsplus/typescript/compiler';
|
|
2
2
|
const COMPILER_ENTRYPOINT = 'reactive';
|
|
3
3
|
const COMPILER_ENTRYPOINT_REGEX = /\breactive\b/;
|
|
4
|
-
const COMPILER_NAMESPACE = uid(
|
|
4
|
+
const COMPILER_NAMESPACE = uid('reactivity');
|
|
5
5
|
var COMPILER_TYPES;
|
|
6
6
|
(function (COMPILER_TYPES) {
|
|
7
7
|
COMPILER_TYPES[COMPILER_TYPES["Array"] = 0] = "Array";
|
package/build/index.d.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.js';
|
|
3
|
+
export { default as reactive, ReactiveArray, ReactiveObject } from './reactive/index.js';
|
|
3
4
|
export * from './system.js';
|
|
4
5
|
export * from './types.js';
|
package/build/index.js
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.js';
|
|
3
|
+
export { default as reactive, ReactiveArray, ReactiveObject } from './reactive/index.js';
|
|
3
4
|
export * from './system.js';
|
|
4
5
|
export * from './types.js';
|
package/build/reactive/array.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { isArray } from '@esportsplus/utilities';
|
|
2
2
|
import { read, signal, write } from '../system.js';
|
|
3
|
-
import { REACTIVE_ARRAY
|
|
3
|
+
import { REACTIVE_ARRAY } from '../constants.js';
|
|
4
|
+
import { isReactiveObject } from './object.js';
|
|
4
5
|
function dispose(value) {
|
|
5
|
-
if (value
|
|
6
|
+
if (isReactiveObject(value)) {
|
|
6
7
|
value.dispose();
|
|
7
8
|
}
|
|
8
9
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { ReactiveArray } from './array.js';
|
|
2
1
|
import { Reactive } from '../types.js';
|
|
2
|
+
import { ReactiveArray } from './array.js';
|
|
3
|
+
import { ReactiveObject } from './object.js';
|
|
3
4
|
type Guard<T> = T extends Record<PropertyKey, unknown> ? T extends {
|
|
4
5
|
dispose: any;
|
|
5
6
|
} ? {
|
|
6
7
|
never: '[ dispose ] is a reserved key';
|
|
7
8
|
} : T : never;
|
|
8
|
-
declare function reactive<T extends
|
|
9
|
+
declare function reactive<T extends unknown[]>(input: T): Reactive<T>;
|
|
10
|
+
declare function reactive<T extends Record<PropertyKey, unknown>>(input: Guard<T>): Reactive<T>;
|
|
9
11
|
export default reactive;
|
|
10
|
-
export { reactive, ReactiveArray };
|
|
12
|
+
export { reactive, ReactiveArray, ReactiveObject };
|
package/build/reactive/index.js
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
import { onCleanup, root } from '@esportsplus/reactivity';
|
|
2
|
+
import { isArray, isObject } from '@esportsplus/utilities';
|
|
1
3
|
import { ReactiveArray } from './array.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { ReactiveObject } from './object.js';
|
|
5
|
+
import { PACKAGE } from '../constants.js';
|
|
6
|
+
function reactive(input) {
|
|
7
|
+
let dispose = false, value = root(() => {
|
|
8
|
+
let response;
|
|
9
|
+
if (isObject(input)) {
|
|
10
|
+
response = new ReactiveObject(input);
|
|
11
|
+
}
|
|
12
|
+
else if (isArray(input)) {
|
|
13
|
+
response = new ReactiveArray(...input);
|
|
14
|
+
}
|
|
15
|
+
if (response) {
|
|
16
|
+
if (root.disposables) {
|
|
17
|
+
dispose = true;
|
|
18
|
+
}
|
|
19
|
+
return response;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`${PACKAGE}: 'reactive' received invalid input - ${JSON.stringify(input)}`);
|
|
22
|
+
});
|
|
23
|
+
if (dispose) {
|
|
24
|
+
onCleanup(() => value.dispose());
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
6
27
|
}
|
|
7
28
|
export default reactive;
|
|
8
|
-
export { reactive, ReactiveArray };
|
|
29
|
+
export { reactive, ReactiveArray, ReactiveObject };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Computed, Signal } from '../types.js';
|
|
2
|
+
import { COMPUTED, REACTIVE_ARRAY, SIGNAL } from '../constants.js';
|
|
3
|
+
import { ReactiveArray } from './array.js';
|
|
4
|
+
declare class ReactiveObject<T extends Record<PropertyKey, unknown>> {
|
|
5
|
+
protected disposers: VoidFunction[] | null;
|
|
6
|
+
constructor(data: T | null);
|
|
7
|
+
protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U>;
|
|
8
|
+
protected [COMPUTED]<T extends Computed<ReturnType<T>>['fn']>(value: T): Computed<ReturnType<T>> | Signal<ReturnType<T> | undefined>;
|
|
9
|
+
protected [SIGNAL]<T>(value: T): Signal<T>;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
}
|
|
12
|
+
declare const isReactiveObject: (value: any) => value is ReactiveObject<any>;
|
|
13
|
+
export { isReactiveObject, ReactiveObject };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defineProperty, isArray, isPromise } from '@esportsplus/utilities';
|
|
2
|
+
import { computed, dispose, effect, read, root, signal, write } from '../system.js';
|
|
3
|
+
import { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL } from '../constants.js';
|
|
4
|
+
import { ReactiveArray } from './array.js';
|
|
5
|
+
class ReactiveObject {
|
|
6
|
+
disposers = null;
|
|
7
|
+
constructor(data) {
|
|
8
|
+
if (data == null) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (let key in data) {
|
|
12
|
+
let value = data[key], type = typeof value;
|
|
13
|
+
if (type === 'function') {
|
|
14
|
+
let node = this[COMPUTED](value);
|
|
15
|
+
defineProperty(this, key, {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: () => read(node)
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (value == null || type !== 'object') {
|
|
22
|
+
}
|
|
23
|
+
else if (isArray(value)) {
|
|
24
|
+
defineProperty(this, key, {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
value: this[REACTIVE_ARRAY](value)
|
|
27
|
+
});
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let node = signal(value);
|
|
31
|
+
defineProperty(this, key, {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get() {
|
|
34
|
+
return read(node);
|
|
35
|
+
},
|
|
36
|
+
set(v) {
|
|
37
|
+
write(node, v);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
[REACTIVE_ARRAY](value) {
|
|
43
|
+
let node = new ReactiveArray(...value);
|
|
44
|
+
(this.disposers ??= []).push(() => node.dispose());
|
|
45
|
+
return node;
|
|
46
|
+
}
|
|
47
|
+
[COMPUTED](value) {
|
|
48
|
+
return root(() => {
|
|
49
|
+
let node = computed(value);
|
|
50
|
+
if (isPromise(node.value)) {
|
|
51
|
+
let factory = node, version = 0;
|
|
52
|
+
node = signal(undefined);
|
|
53
|
+
(this.disposers ??= []).push(effect(() => {
|
|
54
|
+
let id = ++version;
|
|
55
|
+
read(factory).then((v) => {
|
|
56
|
+
if (id !== version) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
write(node, v);
|
|
60
|
+
});
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
(this.disposers ??= []).push(() => dispose(node));
|
|
65
|
+
}
|
|
66
|
+
return node;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
[SIGNAL](value) {
|
|
70
|
+
return signal(value);
|
|
71
|
+
}
|
|
72
|
+
dispose() {
|
|
73
|
+
let disposers = this.disposers, disposer;
|
|
74
|
+
if (!disposers) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
while (disposer = disposers.pop()) {
|
|
78
|
+
disposer();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
Object.defineProperty(ReactiveObject.prototype, REACTIVE_OBJECT, { value: true });
|
|
83
|
+
const isReactiveObject = (value) => {
|
|
84
|
+
return typeof value === 'object' && value !== null && value[REACTIVE_OBJECT] === true;
|
|
85
|
+
};
|
|
86
|
+
export { isReactiveObject, ReactiveObject };
|
package/build/types.d.ts
CHANGED
|
@@ -29,8 +29,8 @@ type Reactive<T> = T extends (...args: unknown[]) => Promise<infer R> ? (R | und
|
|
|
29
29
|
readonly [READONLY]: true;
|
|
30
30
|
} : T extends (...args: any[]) => infer R ? R & {
|
|
31
31
|
readonly [READONLY]: true;
|
|
32
|
-
} : T extends (infer U)[] ? U[] & Pick<ReactiveArray<U>, 'clear' | 'on' | 'once'> : T extends Record<PropertyKey, unknown> ? {
|
|
33
|
-
[K in keyof T]: T[K]
|
|
32
|
+
} : T extends (infer U)[] ? U[] & Pick<ReactiveArray<U>, 'clear' | 'dispose' | 'on' | 'once'> : T extends Record<PropertyKey, unknown> ? {
|
|
33
|
+
[K in keyof T]: Reactive<T[K]>;
|
|
34
34
|
} & {
|
|
35
35
|
dispose: VoidFunction;
|
|
36
36
|
} : T;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"@esportsplus/utilities": "^0.27.2"
|
|
5
5
|
},
|
|
6
6
|
"devDependencies": {
|
|
7
|
-
"@esportsplus/typescript": "^0.
|
|
7
|
+
"@esportsplus/typescript": "^0.24.1",
|
|
8
8
|
"@types/node": "^25.0.3",
|
|
9
9
|
"vite": "^7.3.0"
|
|
10
10
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"type": "module",
|
|
33
33
|
"types": "build/index.d.ts",
|
|
34
|
-
"version": "0.
|
|
34
|
+
"version": "0.27.2",
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsc",
|
|
37
37
|
"build:test": "pnpm build && vite build --config test/vite.config.ts",
|