@alloy-js/core 0.17.0 → 0.18.0-dev.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/dist/src/binder.d.ts +2 -1
- package/dist/src/binder.d.ts.map +1 -1
- package/dist/src/binder.js +2 -2
- package/dist/src/code.d.ts +1 -1
- package/dist/src/code.d.ts.map +1 -1
- package/dist/src/components/Block.d.ts +1 -1
- package/dist/src/components/Block.d.ts.map +1 -1
- package/dist/src/components/Declaration.d.ts +1 -1
- package/dist/src/components/Declaration.d.ts.map +1 -1
- package/dist/src/components/Declaration.js +1 -1
- package/dist/src/components/For.d.ts +1 -1
- package/dist/src/components/For.d.ts.map +1 -1
- package/dist/src/components/For.js +1 -1
- package/dist/src/components/Indent.d.ts +1 -1
- package/dist/src/components/Indent.d.ts.map +1 -1
- package/dist/src/components/List.d.ts +1 -1
- package/dist/src/components/List.d.ts.map +1 -1
- package/dist/src/components/List.js +2 -1
- package/dist/src/components/MemberDeclaration.d.ts +2 -2
- package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
- package/dist/src/components/MemberDeclaration.js +2 -1
- package/dist/src/components/MemberScope.d.ts +2 -2
- package/dist/src/components/MemberScope.d.ts.map +1 -1
- package/dist/src/components/Output.d.ts +1 -1
- package/dist/src/components/Output.d.ts.map +1 -1
- package/dist/src/components/Output.js +1 -1
- package/dist/src/components/Prose.d.ts +1 -1
- package/dist/src/components/Prose.d.ts.map +1 -1
- package/dist/src/components/ReferenceOrContent.d.ts +1 -1
- package/dist/src/components/ReferenceOrContent.d.ts.map +1 -1
- package/dist/src/components/Scope.d.ts +1 -1
- package/dist/src/components/Scope.d.ts.map +1 -1
- package/dist/src/components/Show.d.ts +1 -1
- package/dist/src/components/Show.d.ts.map +1 -1
- package/dist/src/components/SourceDirectory.d.ts +1 -1
- package/dist/src/components/SourceDirectory.d.ts.map +1 -1
- package/dist/src/components/SourceDirectory.js +1 -1
- package/dist/src/components/SourceFile.d.ts +1 -1
- package/dist/src/components/SourceFile.d.ts.map +1 -1
- package/dist/src/components/SourceFile.js +1 -1
- package/dist/src/components/StatementList.d.ts +1 -1
- package/dist/src/components/StatementList.d.ts.map +1 -1
- package/dist/src/components/Switch.d.ts +2 -2
- package/dist/src/components/Switch.d.ts.map +1 -1
- package/dist/src/components/Switch.js +2 -1
- package/dist/src/components/Wrap.d.ts +1 -1
- package/dist/src/components/Wrap.d.ts.map +1 -1
- package/dist/src/context/binder.d.ts +1 -1
- package/dist/src/context/binder.d.ts.map +1 -1
- package/dist/src/context/source-file.d.ts +1 -1
- package/dist/src/context/source-file.d.ts.map +1 -1
- package/dist/src/context.d.ts +2 -2
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +1 -1
- package/dist/src/debug.d.ts +1 -0
- package/dist/src/debug.d.ts.map +1 -1
- package/dist/src/debug.js +4 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/jsx-runtime.d.ts +4 -281
- package/dist/src/jsx-runtime.d.ts.map +1 -1
- package/dist/src/jsx-runtime.js +3 -319
- package/dist/src/props-combinators.d.ts +19 -0
- package/dist/src/props-combinators.d.ts.map +1 -0
- package/dist/src/props-combinators.js +108 -0
- package/dist/src/reactive-union-set.d.ts.map +1 -1
- package/dist/src/reactive-union-set.js +1 -1
- package/dist/src/reactivity.d.ts +75 -0
- package/dist/src/reactivity.d.ts.map +1 -0
- package/dist/src/reactivity.js +141 -0
- package/dist/src/render.d.ts +5 -1
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +68 -15
- package/dist/src/runtime/component.d.ts +24 -0
- package/dist/src/runtime/component.d.ts.map +1 -0
- package/dist/src/runtime/component.js +19 -0
- package/dist/src/runtime/intrinsic.d.ts +168 -0
- package/dist/src/runtime/intrinsic.d.ts.map +1 -0
- package/dist/src/runtime/intrinsic.js +11 -0
- package/dist/src/slot.d.ts +2 -2
- package/dist/src/slot.d.ts.map +1 -1
- package/dist/src/slot.js +1 -1
- package/dist/src/stc.d.ts +1 -1
- package/dist/src/stc.d.ts.map +1 -1
- package/dist/src/sti.d.ts +7 -6
- package/dist/src/sti.d.ts.map +1 -1
- package/dist/src/sti.js +1 -1
- package/dist/src/symbols/flags.d.ts +70 -0
- package/dist/src/symbols/flags.d.ts.map +1 -0
- package/dist/src/symbols/flags.js +72 -0
- package/dist/src/symbols/index.d.ts +1 -0
- package/dist/src/symbols/index.d.ts.map +1 -1
- package/dist/src/symbols/index.js +1 -0
- package/dist/src/symbols/output-scope.d.ts +2 -24
- package/dist/src/symbols/output-scope.d.ts.map +1 -1
- package/dist/src/symbols/output-scope.js +1 -25
- package/dist/src/symbols/output-symbol.d.ts +2 -47
- package/dist/src/symbols/output-symbol.d.ts.map +1 -1
- package/dist/src/symbols/output-symbol.js +2 -48
- package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
- package/dist/src/symbols/symbol-flow.js +2 -2
- package/dist/src/symbols/symbol-slot.d.ts +1 -1
- package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
- package/dist/src/symbols/symbol-slot.js +1 -1
- package/dist/src/symbols/symbol-table.d.ts +3 -3
- package/dist/src/symbols/symbol-table.d.ts.map +1 -1
- package/dist/src/tap.d.ts +1 -1
- package/dist/src/tap.d.ts.map +1 -1
- package/dist/src/tracer.d.ts +60 -3
- package/dist/src/tracer.d.ts.map +1 -1
- package/dist/src/tracer.js +60 -5
- package/dist/src/utils.d.ts +4 -3
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +2 -1
- package/dist/test/props-with-defaults.test.js +1 -1
- package/dist/test/reactive-union-set.test.js +1 -1
- package/dist/test/reactivity/cleanup.test.js +2 -1
- package/dist/test/reactivity/memo.test.js +1 -1
- package/dist/test/reactivity/untrack.test.js +1 -1
- package/dist/test/rendering/memoization.test.js +2 -1
- package/dist/test/split-props.test.js +1 -1
- package/dist/test/symbols/output-scope.test.js +2 -1
- package/dist/test/symbols/output-symbol.test.js +4 -3
- package/dist/test/symbols/resolution.test.js +2 -1
- package/dist/test/symbols/utils.d.ts +3 -2
- package/dist/test/symbols/utils.d.ts.map +1 -1
- package/dist/test/symbols/utils.js +2 -1
- package/dist/testing/extend-expect.d.ts +15 -0
- package/dist/testing/extend-expect.d.ts.map +1 -1
- package/dist/testing/extend-expect.js +2 -1
- package/dist/testing/render.d.ts +1 -1
- package/dist/testing/render.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -24
- package/src/binder.ts +3 -5
- package/src/code.ts +1 -1
- package/src/components/Block.tsx +1 -1
- package/src/components/Declaration.tsx +2 -1
- package/src/components/For.tsx +2 -1
- package/src/components/Indent.tsx +1 -1
- package/src/components/List.tsx +3 -1
- package/src/components/MemberDeclaration.tsx +4 -3
- package/src/components/MemberScope.tsx +2 -2
- package/src/components/Output.tsx +2 -1
- package/src/components/Prose.tsx +1 -1
- package/src/components/ReferenceOrContent.tsx +1 -1
- package/src/components/Scope.tsx +1 -1
- package/src/components/Show.tsx +1 -1
- package/src/components/SourceDirectory.tsx +2 -1
- package/src/components/SourceFile.tsx +2 -1
- package/src/components/StatementList.tsx +1 -1
- package/src/components/Switch.tsx +2 -1
- package/src/components/Wrap.tsx +1 -1
- package/src/context/binder.ts +1 -1
- package/src/context/source-file.ts +1 -1
- package/src/context.ts +3 -7
- package/src/debug.ts +5 -1
- package/src/index.ts +4 -1
- package/src/jsx-runtime.ts +15 -690
- package/src/props-combinators.ts +148 -0
- package/src/reactive-union-set.ts +1 -1
- package/src/reactivity.ts +230 -0
- package/src/render.ts +97 -26
- package/src/runtime/component.ts +67 -0
- package/src/runtime/intrinsic.ts +199 -0
- package/src/slot.ts +3 -4
- package/src/stc.ts +2 -2
- package/src/sti.ts +11 -11
- package/src/symbols/flags.ts +82 -0
- package/src/symbols/index.ts +1 -0
- package/src/symbols/output-scope.ts +2 -29
- package/src/symbols/output-symbol.ts +2 -55
- package/src/symbols/symbol-flow.ts +3 -7
- package/src/symbols/symbol-slot.tsx +2 -1
- package/src/symbols/symbol-table.ts +3 -3
- package/src/tap.ts +1 -1
- package/src/tracer.ts +38 -4
- package/src/utils.tsx +7 -5
- package/temp/api.json +984 -1261
- package/test/props-with-defaults.test.ts +1 -1
- package/test/reactive-union-set.test.tsx +1 -1
- package/test/reactivity/cleanup.test.tsx +2 -1
- package/test/reactivity/memo.test.tsx +1 -1
- package/test/reactivity/untrack.test.ts +1 -1
- package/test/rendering/basic.test.tsx +1 -1
- package/test/rendering/memoization.test.tsx +1 -1
- package/test/split-props.test.ts +1 -1
- package/test/symbols/output-scope.test.ts +2 -4
- package/test/symbols/output-symbol.test.ts +4 -7
- package/test/symbols/resolution.test.ts +2 -4
- package/test/symbols/utils.ts +3 -5
- package/test/utils.test.tsx +1 -1
- package/testing/extend-expect.ts +16 -0
- package/testing/render.ts +1 -2
- package/LICENSE +0 -7
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { computed, isReactive, proxyRefs, toRefs } from "@vue/reactivity";
|
|
2
|
+
import { untrack } from "./reactivity.js";
|
|
3
|
+
|
|
4
|
+
export function mergeProps<T, U>(source: T, source1: U): T & U;
|
|
5
|
+
export function mergeProps<T, U, V>(
|
|
6
|
+
source: T,
|
|
7
|
+
source1: U,
|
|
8
|
+
source2: V,
|
|
9
|
+
): T & U & V;
|
|
10
|
+
export function mergeProps<T, U, V, W>(
|
|
11
|
+
source: T,
|
|
12
|
+
source1: U,
|
|
13
|
+
source2: V,
|
|
14
|
+
source3: W,
|
|
15
|
+
): T & U & V & W;
|
|
16
|
+
export function mergeProps(...sources: any): any {
|
|
17
|
+
const target = {};
|
|
18
|
+
for (let i = 0; i < sources.length; i++) {
|
|
19
|
+
let source = sources[i];
|
|
20
|
+
if (typeof source === "function") source = source();
|
|
21
|
+
if (source) {
|
|
22
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
23
|
+
for (const key in descriptors) {
|
|
24
|
+
if (key in target) continue;
|
|
25
|
+
Object.defineProperty(target, key, {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get() {
|
|
28
|
+
for (let i = sources.length - 1; i >= 0; i--) {
|
|
29
|
+
let s = sources[i];
|
|
30
|
+
if (typeof s === "function") s = s();
|
|
31
|
+
const v = (s || {})[key];
|
|
32
|
+
if (v !== undefined) return v;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return target;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SplitProps<T, K extends (readonly (keyof T)[])[]> = [
|
|
43
|
+
...{
|
|
44
|
+
[P in keyof K]: P extends `${number}` ?
|
|
45
|
+
Pick<T, Extract<K[P], readonly (keyof T)[]>[number]>
|
|
46
|
+
: never;
|
|
47
|
+
},
|
|
48
|
+
{ [P in keyof T as Exclude<P, K[number][number]>]: T[P] },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export function splitProps<
|
|
52
|
+
T extends Record<any, any>,
|
|
53
|
+
K extends [readonly (keyof T)[], ...(readonly (keyof T)[])[]],
|
|
54
|
+
>(props: T, ...keys: K): SplitProps<T, K> {
|
|
55
|
+
if (isReactive(props)) {
|
|
56
|
+
const refs = untrack(() => toRefs(props));
|
|
57
|
+
const remainingKeys = new Set(Object.keys(refs));
|
|
58
|
+
|
|
59
|
+
const result: any = keys.map((keySet) => {
|
|
60
|
+
const resultSet: any = {};
|
|
61
|
+
for (const key of keySet) {
|
|
62
|
+
resultSet[key] = refs[key];
|
|
63
|
+
remainingKeys.delete(key as string);
|
|
64
|
+
}
|
|
65
|
+
return proxyRefs(resultSet);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const remaining: any = {};
|
|
69
|
+
for (const key of remainingKeys) {
|
|
70
|
+
remaining[key] = refs[key];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return [...result, proxyRefs(remaining)] as any;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const descriptors = Object.getOwnPropertyDescriptors(props);
|
|
77
|
+
const remainingKeys = new Set(Object.keys(descriptors));
|
|
78
|
+
const result: any = keys.map((keySet) => {
|
|
79
|
+
const resultSet: any = {};
|
|
80
|
+
for (const key of keySet) {
|
|
81
|
+
if (key in descriptors) {
|
|
82
|
+
Object.defineProperty(resultSet, key, descriptors[key]);
|
|
83
|
+
remainingKeys.delete(key as string);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return resultSet;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const remaining: any = {};
|
|
90
|
+
for (const key of remainingKeys) {
|
|
91
|
+
Object.defineProperty(remaining, key, descriptors[key]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [...result, remaining] as any;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Applies default values to a props object. Reactive props are handled properly
|
|
99
|
+
* by ensuring that their value is not accessed by `defaultProps`, avoiding any
|
|
100
|
+
* unintended side effects.
|
|
101
|
+
*/
|
|
102
|
+
export function defaultProps<T extends {}>(props: T, defaults: Partial<T>): T {
|
|
103
|
+
if (isReactive(props)) {
|
|
104
|
+
const refs = untrack(() => toRefs(props));
|
|
105
|
+
for (const key in defaults) {
|
|
106
|
+
const originalRef = refs[key];
|
|
107
|
+
refs[key] = computed(() =>
|
|
108
|
+
originalRef.value === undefined ? defaults[key] : originalRef.value,
|
|
109
|
+
) as any;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return proxyRefs(refs);
|
|
113
|
+
}
|
|
114
|
+
const withDefaults = {} as T;
|
|
115
|
+
const copied = new Set<string>();
|
|
116
|
+
for (const key in defaults) {
|
|
117
|
+
copied.add(key);
|
|
118
|
+
const desc = Object.getOwnPropertyDescriptor(props, key);
|
|
119
|
+
if (!desc) {
|
|
120
|
+
withDefaults[key] = defaults[key]!;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (desc.get) {
|
|
125
|
+
const originalGet = desc.get;
|
|
126
|
+
desc.get = function () {
|
|
127
|
+
const value = originalGet.call(this);
|
|
128
|
+
return value === undefined ? defaults[key as keyof T] : value;
|
|
129
|
+
};
|
|
130
|
+
Object.defineProperty(withDefaults, key, desc);
|
|
131
|
+
} else {
|
|
132
|
+
desc.value =
|
|
133
|
+
desc.value === undefined ? defaults[key as keyof T] : desc.value;
|
|
134
|
+
Object.defineProperty(withDefaults, key, desc);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const descriptors = Object.getOwnPropertyDescriptors(props);
|
|
139
|
+
for (const key in descriptors) {
|
|
140
|
+
if (copied.has(key)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Object.defineProperty(withDefaults, key, descriptors[key]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return withDefaults;
|
|
148
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { effect } from "@alloy-js/core/jsx-runtime";
|
|
2
1
|
import {
|
|
3
2
|
ITERATE_KEY,
|
|
4
3
|
ReactiveFlags,
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
trigger,
|
|
10
9
|
TriggerOpTypes,
|
|
11
10
|
} from "@vue/reactivity";
|
|
11
|
+
import { effect } from "./reactivity.js";
|
|
12
12
|
|
|
13
13
|
export interface ReactiveUnionSetOptions<T> {
|
|
14
14
|
onAdd?: OnReactiveSetAddCallback<T>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pauseTracking,
|
|
3
|
+
ReactiveEffectRunner,
|
|
4
|
+
resetTracking,
|
|
5
|
+
ShallowReactive,
|
|
6
|
+
shallowRef,
|
|
7
|
+
stop,
|
|
8
|
+
effect as vueEffect,
|
|
9
|
+
} from "@vue/reactivity";
|
|
10
|
+
import type { RenderedTextTree } from "./render.js";
|
|
11
|
+
import type { Children, ComponentCreator } from "./runtime/component.js";
|
|
12
|
+
import { scheduler } from "./scheduler.js";
|
|
13
|
+
import type { OutputSymbol } from "./symbols/output-symbol.js";
|
|
14
|
+
import { trace, TracePhase } from "./tracer.js";
|
|
15
|
+
|
|
16
|
+
// check for multiple versions of alloy here.
|
|
17
|
+
if ((globalThis as any).__ALLOY__) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"Multiple versions of Alloy are loaded for this project. This will likely cause undesirable behavior.",
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
(globalThis as any).__ALLOY__ = true;
|
|
23
|
+
|
|
24
|
+
export function getElementCache() {
|
|
25
|
+
return getContext()!.elementCache;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ElementCacheKey =
|
|
29
|
+
| ComponentCreator
|
|
30
|
+
| (() => unknown)
|
|
31
|
+
| CustomContext;
|
|
32
|
+
|
|
33
|
+
export type ElementCache = Map<ElementCacheKey, RenderedTextTree>;
|
|
34
|
+
|
|
35
|
+
export interface Disposable {
|
|
36
|
+
(): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Context {
|
|
40
|
+
disposables: Disposable[];
|
|
41
|
+
owner: Context | null;
|
|
42
|
+
|
|
43
|
+
// context providers
|
|
44
|
+
context?: Record<symbol, unknown>;
|
|
45
|
+
|
|
46
|
+
// store random info about the node
|
|
47
|
+
meta?: Record<string, any>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A cache of RenderTextTree nodes created within this context,
|
|
51
|
+
* indexed by the component or function which created them.
|
|
52
|
+
*/
|
|
53
|
+
elementCache: ElementCache;
|
|
54
|
+
/**
|
|
55
|
+
* When this context was created by a component, this will
|
|
56
|
+
* be the component that created it.
|
|
57
|
+
*/
|
|
58
|
+
componentOwner?: ComponentCreator<unknown>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether this context will take an emitted symbol.
|
|
62
|
+
*/
|
|
63
|
+
takesSymbols: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The symbol that this component has taken.
|
|
67
|
+
*/
|
|
68
|
+
takenSymbols?: ShallowReactive<Set<OutputSymbol>>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let globalContext: Context | null = null;
|
|
72
|
+
export function getContext() {
|
|
73
|
+
return globalContext;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RootOptions {
|
|
77
|
+
componentOwner?: ComponentCreator<any>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
|
|
81
|
+
const context: Context = {
|
|
82
|
+
componentOwner: options?.componentOwner,
|
|
83
|
+
disposables: [],
|
|
84
|
+
owner: globalContext,
|
|
85
|
+
context: {},
|
|
86
|
+
elementCache: new Map(),
|
|
87
|
+
takesSymbols: false,
|
|
88
|
+
takenSymbols: undefined,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
globalContext = context;
|
|
92
|
+
let ret;
|
|
93
|
+
try {
|
|
94
|
+
ret = untrack(() =>
|
|
95
|
+
fn(() => {
|
|
96
|
+
for (const d of context!.disposables) {
|
|
97
|
+
d();
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
} finally {
|
|
102
|
+
globalContext = globalContext!.owner;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return ret;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function untrack<T>(fn: () => T): T {
|
|
109
|
+
pauseTracking();
|
|
110
|
+
const v = fn();
|
|
111
|
+
resetTracking();
|
|
112
|
+
return v;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function memo<T>(fn: () => T, equal?: boolean): () => T {
|
|
116
|
+
const o = shallowRef();
|
|
117
|
+
effect((prev) => {
|
|
118
|
+
const res = fn();
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
120
|
+
(!equal || prev !== res) && (o.value = res);
|
|
121
|
+
return res;
|
|
122
|
+
}, undefined as T);
|
|
123
|
+
return () => o.value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function effect<T>(fn: (prev?: T) => T, current?: T) {
|
|
127
|
+
const context: Context = {
|
|
128
|
+
context: {},
|
|
129
|
+
disposables: [] as (() => void)[],
|
|
130
|
+
owner: globalContext,
|
|
131
|
+
elementCache: new Map(),
|
|
132
|
+
takesSymbols: false,
|
|
133
|
+
takenSymbols: undefined,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const cleanupFn = (final: boolean) => {
|
|
137
|
+
const d = context.disposables;
|
|
138
|
+
context.disposables = [];
|
|
139
|
+
for (let k = 0, len = d.length; k < len; k++) d[k]();
|
|
140
|
+
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
142
|
+
final && stop(runner);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
onCleanup(() => cleanupFn(true));
|
|
146
|
+
const runner: ReactiveEffectRunner<void> = vueEffect(
|
|
147
|
+
() => {
|
|
148
|
+
cleanupFn(false);
|
|
149
|
+
|
|
150
|
+
const oldContext = globalContext;
|
|
151
|
+
globalContext = context;
|
|
152
|
+
try {
|
|
153
|
+
current = fn(current);
|
|
154
|
+
} finally {
|
|
155
|
+
globalContext = oldContext;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
scheduler: scheduler(() => runner),
|
|
160
|
+
onTrack(event) {
|
|
161
|
+
trace(TracePhase.effect.track, () => {
|
|
162
|
+
return `tracking ${event.target}, ${event.key}`;
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
onTrigger(event) {
|
|
166
|
+
trace(TracePhase.effect.trigger, () => {
|
|
167
|
+
return `triggering ${event.target}, ${event.key}`;
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// allow recursive effects (recursive option does nothing, possible bug)
|
|
174
|
+
(runner as any).effect.flags |= 1 << 5;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Register a cleanup function which is called when the current reactive scope
|
|
179
|
+
* is recalculated or disposed. This is useful to clean up any side effects
|
|
180
|
+
* created in the reactive scope.
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
*
|
|
184
|
+
* When onCleanup is called inside a component definition, the provided function
|
|
185
|
+
* is called when the component is removed from the tree. This can be useful to
|
|
186
|
+
* clean up any side effects created as a result of rendering the component. For
|
|
187
|
+
* example, if rendering a component creates a symbol, `onCleanup` can be used
|
|
188
|
+
* to remove the symbol when the component is removed from the tree.
|
|
189
|
+
*
|
|
190
|
+
* When onCleanup is called inside a memo or effect, the function is called when
|
|
191
|
+
* the effect is refreshed (e.g. when a memo or computed recalculates) or
|
|
192
|
+
* disposed (e.g. it is no longer needed because it is attached to a component
|
|
193
|
+
* which was removed).
|
|
194
|
+
*/
|
|
195
|
+
export function onCleanup(fn: Disposable) {
|
|
196
|
+
if (globalContext != null) {
|
|
197
|
+
globalContext.disposables.push(fn);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a custom reactive context for the children returned by
|
|
203
|
+
* the provided context.
|
|
204
|
+
*/
|
|
205
|
+
export interface CustomContext {
|
|
206
|
+
[CUSTOM_CONTEXT_SYM]: true;
|
|
207
|
+
useCustomContext: (useCb: CustomContextChildrenCallback) => void;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type CustomContextChildrenCallback = (child: Children) => void;
|
|
211
|
+
const CUSTOM_CONTEXT_SYM = Symbol();
|
|
212
|
+
|
|
213
|
+
export function createCustomContext(
|
|
214
|
+
useCallback: (useChildren: CustomContextChildrenCallback) => void,
|
|
215
|
+
): CustomContext {
|
|
216
|
+
return {
|
|
217
|
+
[CUSTOM_CONTEXT_SYM]: true,
|
|
218
|
+
useCustomContext(useCb: (children: Children) => void): void {
|
|
219
|
+
useCallback(useCb);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function isCustomContext(child: Children): child is CustomContext {
|
|
225
|
+
return (
|
|
226
|
+
typeof child === "object" &&
|
|
227
|
+
child !== null &&
|
|
228
|
+
Object.hasOwn(child, CUSTOM_CONTEXT_SYM)
|
|
229
|
+
);
|
|
230
|
+
}
|
package/src/render.ts
CHANGED
|
@@ -3,26 +3,29 @@ import { Doc, doc } from "prettier";
|
|
|
3
3
|
import prettier from "prettier/doc.js";
|
|
4
4
|
import { useContext } from "./context.js";
|
|
5
5
|
import { SourceFileContext } from "./context/source-file.js";
|
|
6
|
+
import { shouldDebug } from "./debug.js";
|
|
6
7
|
import {
|
|
7
|
-
Child,
|
|
8
|
-
Children,
|
|
9
8
|
Context,
|
|
10
9
|
CustomContext,
|
|
11
10
|
effect,
|
|
12
11
|
getContext,
|
|
13
12
|
getElementCache,
|
|
14
|
-
IntrinsicElement,
|
|
15
|
-
isComponentCreator,
|
|
16
13
|
isCustomContext,
|
|
17
|
-
isIntrinsicElement,
|
|
18
|
-
popStack,
|
|
19
|
-
printRenderStack,
|
|
20
|
-
pushStack,
|
|
21
14
|
root,
|
|
22
15
|
untrack,
|
|
23
|
-
} from "./
|
|
16
|
+
} from "./reactivity.js";
|
|
24
17
|
import { isRefkey } from "./refkey.js";
|
|
18
|
+
import {
|
|
19
|
+
Child,
|
|
20
|
+
Children,
|
|
21
|
+
Component,
|
|
22
|
+
isComponentCreator,
|
|
23
|
+
Props,
|
|
24
|
+
} from "./runtime/component.js";
|
|
25
|
+
import { IntrinsicElement, isIntrinsicElement } from "./runtime/intrinsic.js";
|
|
25
26
|
import { flushJobs } from "./scheduler.js";
|
|
27
|
+
import { trace, TracePhase } from "./tracer.js";
|
|
28
|
+
|
|
26
29
|
const {
|
|
27
30
|
builders: {
|
|
28
31
|
align,
|
|
@@ -172,11 +175,6 @@ export function isPrintHook(type: unknown): type is PrintHook {
|
|
|
172
175
|
|
|
173
176
|
export type RenderedTextTree = (string | RenderedTextTree | PrintHook)[];
|
|
174
177
|
|
|
175
|
-
function traceRender(phase: string, message: () => string) {
|
|
176
|
-
return false;
|
|
177
|
-
//console.log(`[\x1b[34m${phase}\x1b[0m]: ${message()}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
178
|
export function render(
|
|
181
179
|
children: Children,
|
|
182
180
|
options?: PrintTreeOptions,
|
|
@@ -284,7 +282,12 @@ export function renderTree(children: Children) {
|
|
|
284
282
|
}
|
|
285
283
|
|
|
286
284
|
function renderWorker(node: RenderedTextTree, children: Children) {
|
|
287
|
-
|
|
285
|
+
if (!getContext()) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
"Cannot render without a context. Make sure you are using the Output component.",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
trace(TracePhase.render.worker, () => dumpChildren(children));
|
|
288
291
|
|
|
289
292
|
if (Array.isArray(node)) {
|
|
290
293
|
nodesToContext.set(node, getContext()!);
|
|
@@ -300,7 +303,7 @@ function renderWorker(node: RenderedTextTree, children: Children) {
|
|
|
300
303
|
}
|
|
301
304
|
|
|
302
305
|
function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
303
|
-
|
|
306
|
+
trace(TracePhase.render.appendChild, () => debugPrintChild(rawChild));
|
|
304
307
|
const child = normalizeChild(rawChild);
|
|
305
308
|
|
|
306
309
|
if (typeof child === "string") {
|
|
@@ -308,12 +311,18 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
308
311
|
} else {
|
|
309
312
|
const cache = getElementCache();
|
|
310
313
|
if (cache.has(child as any)) {
|
|
311
|
-
|
|
314
|
+
trace(
|
|
315
|
+
TracePhase.render.appendChild,
|
|
316
|
+
() => "Cached: " + debugPrintChild(child),
|
|
317
|
+
);
|
|
312
318
|
node.push(cache.get(child as any)!);
|
|
313
319
|
return;
|
|
314
320
|
}
|
|
315
321
|
if (isCustomContext(child)) {
|
|
316
|
-
|
|
322
|
+
trace(
|
|
323
|
+
TracePhase.render.appendChild,
|
|
324
|
+
() => "CustomContext: " + debugPrintChild(child),
|
|
325
|
+
);
|
|
317
326
|
child.useCustomContext((children) => {
|
|
318
327
|
const newNode: RenderedTextTree = [];
|
|
319
328
|
renderWorker(newNode, children);
|
|
@@ -321,10 +330,11 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
321
330
|
cache.set(child, newNode);
|
|
322
331
|
});
|
|
323
332
|
} else if (isIntrinsicElement(child)) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
debugPrintChild(child),
|
|
333
|
+
trace(
|
|
334
|
+
TracePhase.render.appendChild,
|
|
335
|
+
() => "IntrinsicElement: " + debugPrintChild(child),
|
|
327
336
|
);
|
|
337
|
+
// don't need a new context here because intrinsics are never reactive
|
|
328
338
|
const newNode: RenderedTextTree = [];
|
|
329
339
|
|
|
330
340
|
function formatHookWithChildren(command: (doc: Doc) => Doc) {
|
|
@@ -442,20 +452,26 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
442
452
|
}
|
|
443
453
|
} else if (isComponentCreator(child)) {
|
|
444
454
|
effect(() => {
|
|
445
|
-
|
|
455
|
+
trace(
|
|
456
|
+
TracePhase.render.appendChild,
|
|
457
|
+
() => "Component: " + debugPrintChild(child),
|
|
458
|
+
);
|
|
446
459
|
const componentRoot: RenderedTextTree = [];
|
|
447
460
|
pushStack(child.component, child.props);
|
|
448
461
|
renderWorker(componentRoot, untrack(child));
|
|
449
462
|
popStack();
|
|
450
463
|
node.push(componentRoot);
|
|
451
464
|
cache.set(child, componentRoot);
|
|
452
|
-
|
|
465
|
+
trace(
|
|
466
|
+
TracePhase.render.appendChild,
|
|
467
|
+
() => "Component done: " + debugPrintChild(child),
|
|
468
|
+
);
|
|
453
469
|
});
|
|
454
470
|
} else if (typeof child === "function") {
|
|
455
|
-
|
|
471
|
+
trace(TracePhase.render.appendChild, () => "Memo: " + child.toString());
|
|
456
472
|
const index = node.length;
|
|
457
473
|
effect(() => {
|
|
458
|
-
|
|
474
|
+
trace(TracePhase.render.renderEffect, () => "");
|
|
459
475
|
let res = child();
|
|
460
476
|
while (typeof res === "function" && !isComponentCreator(res)) {
|
|
461
477
|
res = res();
|
|
@@ -466,7 +482,6 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
|
|
|
466
482
|
cache.set(child, newNodes);
|
|
467
483
|
return newNodes;
|
|
468
484
|
});
|
|
469
|
-
traceRender("appendChild:memo-done", () => "");
|
|
470
485
|
} else {
|
|
471
486
|
throw new Error("Unexpected child type");
|
|
472
487
|
}
|
|
@@ -525,6 +540,8 @@ function debugPrintChild(child: Children): string {
|
|
|
525
540
|
return "$memo";
|
|
526
541
|
} else if (isRef(child)) {
|
|
527
542
|
return "$ref";
|
|
543
|
+
} else if (isIntrinsicElement(child)) {
|
|
544
|
+
return `<${child.name}>`;
|
|
528
545
|
} else {
|
|
529
546
|
return JSON.stringify(child);
|
|
530
547
|
}
|
|
@@ -588,3 +605,57 @@ function printTreeWorker(tree: RenderedTextTree): Doc {
|
|
|
588
605
|
|
|
589
606
|
return doc;
|
|
590
607
|
}
|
|
608
|
+
// debugging utilities
|
|
609
|
+
const renderStack: {
|
|
610
|
+
component: Component<any>;
|
|
611
|
+
props: Props;
|
|
612
|
+
}[] = [];
|
|
613
|
+
|
|
614
|
+
export function pushStack(component: Component<any>, props: Props) {
|
|
615
|
+
if (!shouldDebug()) return;
|
|
616
|
+
renderStack.push({ component, props });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function popStack() {
|
|
620
|
+
if (!shouldDebug()) return;
|
|
621
|
+
renderStack.pop();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function printRenderStack() {
|
|
625
|
+
if (!shouldDebug()) return;
|
|
626
|
+
|
|
627
|
+
// eslint-disable-next-line no-console
|
|
628
|
+
console.error("Error rendering:");
|
|
629
|
+
for (let i = renderStack.length - 1; i >= 0; i--) {
|
|
630
|
+
const { component, props } = renderStack[i];
|
|
631
|
+
// eslint-disable-next-line no-console
|
|
632
|
+
console.error(` at ${component.name}(${inspectProps(props)})`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function inspectProps(props: Props) {
|
|
637
|
+
return JSON.stringify(
|
|
638
|
+
Object.fromEntries(
|
|
639
|
+
Object.entries(props).map(([key, value]) => {
|
|
640
|
+
let safeValue;
|
|
641
|
+
switch (typeof value) {
|
|
642
|
+
case "string":
|
|
643
|
+
case "number":
|
|
644
|
+
case "boolean":
|
|
645
|
+
safeValue = value;
|
|
646
|
+
break;
|
|
647
|
+
case "undefined":
|
|
648
|
+
safeValue = "undefined";
|
|
649
|
+
break;
|
|
650
|
+
case "object":
|
|
651
|
+
safeValue = value ? "{...}" : null;
|
|
652
|
+
break;
|
|
653
|
+
case "function":
|
|
654
|
+
safeValue = "function";
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
return [key, safeValue];
|
|
658
|
+
}),
|
|
659
|
+
),
|
|
660
|
+
);
|
|
661
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Ref } from "@vue/reactivity";
|
|
2
|
+
import { CustomContext } from "../reactivity.js";
|
|
3
|
+
import { Refkey } from "../refkey.js";
|
|
4
|
+
import { IntrinsicElement } from "./intrinsic.js";
|
|
5
|
+
|
|
6
|
+
export type Child =
|
|
7
|
+
| string
|
|
8
|
+
| boolean
|
|
9
|
+
| number
|
|
10
|
+
| undefined
|
|
11
|
+
| null
|
|
12
|
+
| void
|
|
13
|
+
| (() => Children)
|
|
14
|
+
| Ref
|
|
15
|
+
| Refkey
|
|
16
|
+
| CustomContext
|
|
17
|
+
| IntrinsicElement;
|
|
18
|
+
|
|
19
|
+
export type Children = Child | Children[];
|
|
20
|
+
export type Props = Record<string, any>;
|
|
21
|
+
|
|
22
|
+
export interface ComponentDefinition<TProps = Props> {
|
|
23
|
+
(props: TProps): Children;
|
|
24
|
+
}
|
|
25
|
+
export interface Component<TProps = Props> {
|
|
26
|
+
(props: TProps): Children;
|
|
27
|
+
tag?: symbol;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ComponentCreator<TProps = Props> {
|
|
31
|
+
component: Component<TProps>;
|
|
32
|
+
(): Children;
|
|
33
|
+
props: TProps;
|
|
34
|
+
tag?: symbol;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isComponentCreator<TProps = any>(
|
|
38
|
+
item: unknown,
|
|
39
|
+
component?: Component<TProps>,
|
|
40
|
+
): item is ComponentCreator<TProps> {
|
|
41
|
+
if (!component) {
|
|
42
|
+
return typeof item === "function" && (item as any).component;
|
|
43
|
+
}
|
|
44
|
+
return typeof item === "function" && (item as any).component === component;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createComponent<TProps extends Props = Props>(
|
|
48
|
+
C: Component<TProps>,
|
|
49
|
+
props: TProps,
|
|
50
|
+
): ComponentCreator<TProps> {
|
|
51
|
+
const creator: ComponentCreator<TProps> = () => /* */ C(props);
|
|
52
|
+
creator.props = props;
|
|
53
|
+
creator.component = C;
|
|
54
|
+
if (C.tag) {
|
|
55
|
+
creator.tag = C.tag;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return creator;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function taggedComponent<TProps = Props>(
|
|
62
|
+
tag: symbol,
|
|
63
|
+
component: Component<TProps>,
|
|
64
|
+
): Component<TProps> & Required<Pick<Component<TProps>, "tag">> {
|
|
65
|
+
component.tag = tag;
|
|
66
|
+
return component as any;
|
|
67
|
+
}
|