@esportsplus/reactivity 0.40.0 → 0.41.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 -5
- package/bench/compiler.bench.ts +115 -0
- package/bench/kairo.bench.ts +1 -1
- package/bench/reactive/array.bench.ts +1 -0
- package/bench/reactive/reactive-object.bench.ts +3 -2
- package/build/compiler/array.js +28 -25
- package/build/compiler/bindings.d.ts +3 -1
- package/build/compiler/bindings.js +32 -9
- package/build/compiler/index.d.ts +3 -2
- package/build/compiler/index.js +21 -13
- package/build/compiler/object.js +12 -13
- package/build/compiler/plugins/vite.d.ts +13 -4
- package/build/compiler/primitives.js +9 -9
- package/build/reactive/index.d.ts +1 -1
- package/build/reactive/object.d.ts +3 -3
- package/build/reactive/object.js +1 -1
- package/oxlintrc.json +3 -0
- package/package.json +15 -7
- package/pnpm-workspace.yaml +4 -7
- package/src/compiler/array.ts +30 -27
- package/src/compiler/bindings.ts +49 -10
- package/src/compiler/index.ts +37 -20
- package/src/compiler/object.ts +14 -16
- package/src/compiler/primitives.ts +13 -10
- package/src/reactive/array.ts +5 -3
- package/src/reactive/index.ts +3 -3
- package/src/reactive/object.ts +4 -4
- package/src/system.ts +2 -0
- package/test/compiler/__snapshots__/output.test.ts.snap +157 -0
- package/test/compiler/compiler.test.ts +51 -9
- package/test/compiler/composition.test.ts +110 -0
- package/test/compiler/coverage.test.ts +22 -22
- package/test/compiler/origins.test.ts +156 -0
- package/test/compiler/output.test.ts +116 -0
- package/test/ownership.test.ts +1 -1
- package/test/reactive/array.test.ts +8 -7
- package/test/reactive/reactive.test.ts +4 -4
- package/test/system.test.ts +3 -4
- package/test/untrack.test.ts +1 -1
- package/tsconfig.build.json +3 -0
- package/tsconfig.json +2 -2
- package/vitest.config.ts +2 -2
- package/bench/tsconfig.json +0 -16
- package/test/tsconfig.json +0 -16
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import vite from '~/compiler/plugins/vite';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Whole compiled modules, pinned byte for byte: every lowering form the compiler emits, each one
|
|
9
|
+
// alone in its range so no output depends on how nested rewrites compose
|
|
10
|
+
const FILES: Record<string, string> = {
|
|
11
|
+
'app.ts': [
|
|
12
|
+
"export { reactive as make } from '@esportsplus/reactivity';",
|
|
13
|
+
"export * from './state';",
|
|
14
|
+
"export { default as total } from './state';"
|
|
15
|
+
].join('\n'),
|
|
16
|
+
'consumer.ts': [
|
|
17
|
+
"import { count as renamed, later, make, total } from './app';",
|
|
18
|
+
"import * as app from './app';",
|
|
19
|
+
'export let a = make(0);',
|
|
20
|
+
'export let b = () => renamed + total + later;',
|
|
21
|
+
'export let c = () => app.count + app.list.length;',
|
|
22
|
+
"export let d = () => app['count'];",
|
|
23
|
+
'export let e = () => { app.list[0] = 1; };'
|
|
24
|
+
].join('\n'),
|
|
25
|
+
'forms.ts': [
|
|
26
|
+
"import { reactive } from '@esportsplus/reactivity';",
|
|
27
|
+
'declare function total(): number;',
|
|
28
|
+
'declare let list: number[];',
|
|
29
|
+
'declare let n: number;',
|
|
30
|
+
'let count = reactive(0);',
|
|
31
|
+
'let doubled = reactive(() => count * 2);',
|
|
32
|
+
'let typed = reactive(n);',
|
|
33
|
+
'let fn = reactive(total);',
|
|
34
|
+
'let runtime = reactive(list);',
|
|
35
|
+
'let obj = reactive({ a: 1, b: \'x\', items: [1, 2], sum: () => 3, neg: -1, source: list, flag: true });',
|
|
36
|
+
'let shorthand = reactive({ list });',
|
|
37
|
+
'let hinted = reactive<{ a: number; items: number[] }>({ a: 1, items: [] });',
|
|
38
|
+
'let spread = reactive({ ...{ a: 1 }, b: 2 });',
|
|
39
|
+
'let method = reactive({ m() { return 1; } });',
|
|
40
|
+
'let arr = reactive([1, 2, 3]);',
|
|
41
|
+
'let empty = reactive([] as string[]);',
|
|
42
|
+
'let declared: number[] = reactive([4]);',
|
|
43
|
+
'count = 5;',
|
|
44
|
+
'count += 2;',
|
|
45
|
+
'count ||= 3;',
|
|
46
|
+
'count++;',
|
|
47
|
+
'--count;',
|
|
48
|
+
'let p = count++;',
|
|
49
|
+
'let q = ++count;',
|
|
50
|
+
'let o = { count };',
|
|
51
|
+
'let t = typeof count;',
|
|
52
|
+
'arr[0] = 4;',
|
|
53
|
+
'arr[1] += 2;',
|
|
54
|
+
'arr[2]++;',
|
|
55
|
+
'let r = ++arr[0];',
|
|
56
|
+
'let size = arr.length;',
|
|
57
|
+
'arr.length = 1;',
|
|
58
|
+
'arr.length += 1;',
|
|
59
|
+
'arr.length++;',
|
|
60
|
+
'arr = [5, 6];',
|
|
61
|
+
'arr = [];',
|
|
62
|
+
'export { count, doubled, typed, fn, runtime, obj, shorthand, hinted, spread, method, empty, declared, p, q, o, t, r, size };',
|
|
63
|
+
'export default reactive(0);'
|
|
64
|
+
].join('\n'),
|
|
65
|
+
'state.ts': [
|
|
66
|
+
"import { reactive } from '@esportsplus/reactivity';",
|
|
67
|
+
'export let count = reactive(0);',
|
|
68
|
+
'export let list = reactive([1, 2]);',
|
|
69
|
+
'export let later: number;',
|
|
70
|
+
'later = reactive(1);',
|
|
71
|
+
'export default reactive(() => 2);'
|
|
72
|
+
].join('\n')
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
let fixtures: string[] = [];
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
function compile(name: string): string | null {
|
|
80
|
+
let directory = mkdtempSync(join(process.cwd(), '.fixture-output-')).replace(/\\/g, '/');
|
|
81
|
+
|
|
82
|
+
fixtures.push(directory);
|
|
83
|
+
|
|
84
|
+
for (let [file, code] of Object.entries(FILES)) {
|
|
85
|
+
writeFileSync(join(directory, file), code);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
writeFileSync(join(directory, 'tsconfig.json'), JSON.stringify({
|
|
89
|
+
compilerOptions: { module: 'esnext', moduleResolution: 'bundler', strict: true, target: 'esnext', types: [] },
|
|
90
|
+
files: Object.keys(FILES).map(file => './' + file)
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
let plugin = vite({ root: directory });
|
|
94
|
+
|
|
95
|
+
plugin.configResolved({ command: 'build', root: directory });
|
|
96
|
+
|
|
97
|
+
return plugin.transform.handler(FILES[name], directory + '/' + name)?.code ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
for (let i = 0, n = fixtures.length; i < n; i++) {
|
|
103
|
+
rmSync(fixtures[i], { force: true, recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fixtures = [];
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
describe('compiled output', () => {
|
|
111
|
+
for (let name of Object.keys(FILES)) {
|
|
112
|
+
it(name, () => {
|
|
113
|
+
expect(compile(name)).toMatchSnapshot();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
package/test/ownership.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { computed, dispose, effect, flush, hasOwner, onCleanup,
|
|
2
|
+
import { computed, owner as current, dispose, effect, flush, hasOwner, onCleanup, read, root, signal, untrack, write } from '~/system';
|
|
3
3
|
import type { Computed } from '~/system';
|
|
4
4
|
import { captureUncaught } from './lib/uncaught';
|
|
5
5
|
|
|
@@ -54,7 +54,7 @@ describe('ReactiveArray', () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it('does not overflow the stack for a large array', () => {
|
|
57
|
-
let arr = new ReactiveArray(
|
|
57
|
+
let arr = new ReactiveArray(Array.from({ length: 500000 }, () => 0));
|
|
58
58
|
|
|
59
59
|
expect(arr.length).toBe(500000);
|
|
60
60
|
expect(arr.$length).toBe(500000);
|
|
@@ -463,7 +463,7 @@ describe('ReactiveArray', () => {
|
|
|
463
463
|
arr.on('splice', (e) => { events.push(e); });
|
|
464
464
|
arr.splice(1, 1, 20, 30);
|
|
465
465
|
|
|
466
|
-
expect(events).toEqual([{
|
|
466
|
+
expect(events).toEqual([{ deleteCount: 1, items: [20, 30], start: 1 }]);
|
|
467
467
|
});
|
|
468
468
|
|
|
469
469
|
it('no event when nothing changes', () => {
|
|
@@ -499,7 +499,7 @@ describe('ReactiveArray', () => {
|
|
|
499
499
|
|
|
500
500
|
expect([...removed]).toEqual([4]);
|
|
501
501
|
expect([...arr]).toEqual([1, 2, 3, 5]);
|
|
502
|
-
expect(events).toEqual([{
|
|
502
|
+
expect(events).toEqual([{ deleteCount: 1, items: [], start: 3 }]);
|
|
503
503
|
});
|
|
504
504
|
|
|
505
505
|
it('splice dispatches the count actually removed, not the requested count', () => {
|
|
@@ -510,7 +510,7 @@ describe('ReactiveArray', () => {
|
|
|
510
510
|
arr.splice(1, 100);
|
|
511
511
|
|
|
512
512
|
expect([...arr]).toEqual([1]);
|
|
513
|
-
expect(events).toEqual([{
|
|
513
|
+
expect(events).toEqual([{ deleteCount: 2, items: [], start: 1 }]);
|
|
514
514
|
});
|
|
515
515
|
|
|
516
516
|
it('splice clamps an out-of-range or fractional start the way Array.prototype.splice does', () => {
|
|
@@ -524,9 +524,9 @@ describe('ReactiveArray', () => {
|
|
|
524
524
|
|
|
525
525
|
expect([...arr]).toEqual([0, 9, 1, 2, 3, 4]);
|
|
526
526
|
expect(events).toEqual([
|
|
527
|
-
{
|
|
528
|
-
{
|
|
529
|
-
{
|
|
527
|
+
{ deleteCount: 0, items: [4], start: 3 },
|
|
528
|
+
{ deleteCount: 0, items: [0], start: 0 },
|
|
529
|
+
{ deleteCount: 0, items: [9], start: 1 }
|
|
530
530
|
]);
|
|
531
531
|
});
|
|
532
532
|
});
|
|
@@ -652,6 +652,7 @@ describe('ReactiveArray', () => {
|
|
|
652
652
|
arr.sort((a, b) => a - b);
|
|
653
653
|
|
|
654
654
|
expect([...arr]).toEqual([1, 2, 2]);
|
|
655
|
+
expect(order).toEqual([1, 0, 2]);
|
|
655
656
|
});
|
|
656
657
|
|
|
657
658
|
it('order maps duplicate values stably (first stays first)', () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { computed, effect, onCleanup, read,
|
|
2
|
+
import { computed, effect, onCleanup, read, signal, write } from '~/system';
|
|
3
3
|
import { SIGNAL } from '~/constants';
|
|
4
|
-
import {
|
|
4
|
+
import { isReactiveObject, ReactiveObject } from '~/reactive/object';
|
|
5
5
|
import reactive from '~/reactive/index';
|
|
6
6
|
import { ReactiveArray } from '~/reactive/array';
|
|
7
7
|
import { tick, waitFor } from '../lib/wait-for';
|
|
@@ -348,7 +348,7 @@ describe('isReactiveObject', () => {
|
|
|
348
348
|
describe('reactive()', () => {
|
|
349
349
|
describe('objects', () => {
|
|
350
350
|
it('creates reactive object', () => {
|
|
351
|
-
let obj = reactive({ name: 'John'
|
|
351
|
+
let obj = reactive({ age: 25, name: 'John' });
|
|
352
352
|
|
|
353
353
|
expect((obj as any).name).toBe('John');
|
|
354
354
|
expect((obj as any).age).toBe(25);
|
|
@@ -515,7 +515,7 @@ describe('integration', () => {
|
|
|
515
515
|
|
|
516
516
|
it('large dependency chain', async () => {
|
|
517
517
|
let s = signal(0),
|
|
518
|
-
chain: ReturnType<typeof computed
|
|
518
|
+
chain: ReturnType<typeof computed<number>>[] = [computed(() => read(s))];
|
|
519
519
|
|
|
520
520
|
for (let i = 1; i < 50; i++) {
|
|
521
521
|
let prev = chain[i - 1];
|
package/test/system.test.ts
CHANGED
|
@@ -584,8 +584,7 @@ describe('root', () => {
|
|
|
584
584
|
stop = dispose;
|
|
585
585
|
|
|
586
586
|
try {
|
|
587
|
-
root((
|
|
588
|
-
inner;
|
|
587
|
+
root((_inner) => {
|
|
589
588
|
throw new Error('reactivity: inner boom');
|
|
590
589
|
});
|
|
591
590
|
}
|
|
@@ -1224,7 +1223,7 @@ describe('edge cases', () => {
|
|
|
1224
1223
|
|
|
1225
1224
|
it('heap auto-resizes for computed chain deeper than 64', async () => {
|
|
1226
1225
|
let s = signal(0),
|
|
1227
|
-
chain: ReturnType<typeof computed
|
|
1226
|
+
chain: ReturnType<typeof computed<number>>[] = [computed(() => read(s) + 1)];
|
|
1228
1227
|
|
|
1229
1228
|
for (let i = 1; i < 80; i++) {
|
|
1230
1229
|
let prev = chain[i - 1];
|
|
@@ -1416,7 +1415,7 @@ describe('edge cases', () => {
|
|
|
1416
1415
|
});
|
|
1417
1416
|
|
|
1418
1417
|
it('link pool handles >1000 dependencies with disposal and reuse', async () => {
|
|
1419
|
-
let signals: ReturnType<typeof signal
|
|
1418
|
+
let signals: ReturnType<typeof signal<number>>[] = [],
|
|
1420
1419
|
stops: (() => void)[] = [];
|
|
1421
1420
|
|
|
1422
1421
|
for (let i = 0; i < 1100; i++) {
|
package/test/untrack.test.ts
CHANGED
package/tsconfig.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "@esportsplus/typescript/tsconfig.package.json"
|
|
3
|
-
}
|
|
2
|
+
"extends": "@esportsplus/typescript/tsconfig.package.dev.json"
|
|
3
|
+
}
|
package/vitest.config.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { resolve } from 'path';
|
|
|
5
5
|
export default defineConfig({
|
|
6
6
|
resolve: {
|
|
7
7
|
alias: {
|
|
8
|
-
'
|
|
9
|
-
'
|
|
8
|
+
'@esportsplus/reactivity': resolve(import.meta.dirname, 'src/index.ts'),
|
|
9
|
+
'~': resolve(import.meta.dirname, 'src')
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
test: {
|
package/bench/tsconfig.json
DELETED