@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.
Files changed (196) hide show
  1. package/dist/src/binder.d.ts +2 -1
  2. package/dist/src/binder.d.ts.map +1 -1
  3. package/dist/src/binder.js +2 -2
  4. package/dist/src/code.d.ts +1 -1
  5. package/dist/src/code.d.ts.map +1 -1
  6. package/dist/src/components/Block.d.ts +1 -1
  7. package/dist/src/components/Block.d.ts.map +1 -1
  8. package/dist/src/components/Declaration.d.ts +1 -1
  9. package/dist/src/components/Declaration.d.ts.map +1 -1
  10. package/dist/src/components/Declaration.js +1 -1
  11. package/dist/src/components/For.d.ts +1 -1
  12. package/dist/src/components/For.d.ts.map +1 -1
  13. package/dist/src/components/For.js +1 -1
  14. package/dist/src/components/Indent.d.ts +1 -1
  15. package/dist/src/components/Indent.d.ts.map +1 -1
  16. package/dist/src/components/List.d.ts +1 -1
  17. package/dist/src/components/List.d.ts.map +1 -1
  18. package/dist/src/components/List.js +2 -1
  19. package/dist/src/components/MemberDeclaration.d.ts +2 -2
  20. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  21. package/dist/src/components/MemberDeclaration.js +2 -1
  22. package/dist/src/components/MemberScope.d.ts +2 -2
  23. package/dist/src/components/MemberScope.d.ts.map +1 -1
  24. package/dist/src/components/Output.d.ts +1 -1
  25. package/dist/src/components/Output.d.ts.map +1 -1
  26. package/dist/src/components/Output.js +1 -1
  27. package/dist/src/components/Prose.d.ts +1 -1
  28. package/dist/src/components/Prose.d.ts.map +1 -1
  29. package/dist/src/components/ReferenceOrContent.d.ts +1 -1
  30. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -1
  31. package/dist/src/components/Scope.d.ts +1 -1
  32. package/dist/src/components/Scope.d.ts.map +1 -1
  33. package/dist/src/components/Show.d.ts +1 -1
  34. package/dist/src/components/Show.d.ts.map +1 -1
  35. package/dist/src/components/SourceDirectory.d.ts +1 -1
  36. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  37. package/dist/src/components/SourceDirectory.js +1 -1
  38. package/dist/src/components/SourceFile.d.ts +1 -1
  39. package/dist/src/components/SourceFile.d.ts.map +1 -1
  40. package/dist/src/components/SourceFile.js +1 -1
  41. package/dist/src/components/StatementList.d.ts +1 -1
  42. package/dist/src/components/StatementList.d.ts.map +1 -1
  43. package/dist/src/components/Switch.d.ts +2 -2
  44. package/dist/src/components/Switch.d.ts.map +1 -1
  45. package/dist/src/components/Switch.js +2 -1
  46. package/dist/src/components/Wrap.d.ts +1 -1
  47. package/dist/src/components/Wrap.d.ts.map +1 -1
  48. package/dist/src/context/binder.d.ts +1 -1
  49. package/dist/src/context/binder.d.ts.map +1 -1
  50. package/dist/src/context/source-file.d.ts +1 -1
  51. package/dist/src/context/source-file.d.ts.map +1 -1
  52. package/dist/src/context.d.ts +2 -2
  53. package/dist/src/context.d.ts.map +1 -1
  54. package/dist/src/context.js +1 -1
  55. package/dist/src/debug.d.ts +1 -0
  56. package/dist/src/debug.d.ts.map +1 -1
  57. package/dist/src/debug.js +4 -1
  58. package/dist/src/index.d.ts +4 -1
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/index.js +4 -1
  61. package/dist/src/jsx-runtime.d.ts +4 -281
  62. package/dist/src/jsx-runtime.d.ts.map +1 -1
  63. package/dist/src/jsx-runtime.js +3 -319
  64. package/dist/src/props-combinators.d.ts +19 -0
  65. package/dist/src/props-combinators.d.ts.map +1 -0
  66. package/dist/src/props-combinators.js +108 -0
  67. package/dist/src/reactive-union-set.d.ts.map +1 -1
  68. package/dist/src/reactive-union-set.js +1 -1
  69. package/dist/src/reactivity.d.ts +75 -0
  70. package/dist/src/reactivity.d.ts.map +1 -0
  71. package/dist/src/reactivity.js +141 -0
  72. package/dist/src/render.d.ts +5 -1
  73. package/dist/src/render.d.ts.map +1 -1
  74. package/dist/src/render.js +68 -15
  75. package/dist/src/runtime/component.d.ts +24 -0
  76. package/dist/src/runtime/component.d.ts.map +1 -0
  77. package/dist/src/runtime/component.js +19 -0
  78. package/dist/src/runtime/intrinsic.d.ts +168 -0
  79. package/dist/src/runtime/intrinsic.d.ts.map +1 -0
  80. package/dist/src/runtime/intrinsic.js +11 -0
  81. package/dist/src/slot.d.ts +2 -2
  82. package/dist/src/slot.d.ts.map +1 -1
  83. package/dist/src/slot.js +1 -1
  84. package/dist/src/stc.d.ts +1 -1
  85. package/dist/src/stc.d.ts.map +1 -1
  86. package/dist/src/sti.d.ts +7 -6
  87. package/dist/src/sti.d.ts.map +1 -1
  88. package/dist/src/sti.js +1 -1
  89. package/dist/src/symbols/flags.d.ts +70 -0
  90. package/dist/src/symbols/flags.d.ts.map +1 -0
  91. package/dist/src/symbols/flags.js +72 -0
  92. package/dist/src/symbols/index.d.ts +1 -0
  93. package/dist/src/symbols/index.d.ts.map +1 -1
  94. package/dist/src/symbols/index.js +1 -0
  95. package/dist/src/symbols/output-scope.d.ts +2 -24
  96. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  97. package/dist/src/symbols/output-scope.js +1 -25
  98. package/dist/src/symbols/output-symbol.d.ts +2 -47
  99. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  100. package/dist/src/symbols/output-symbol.js +2 -48
  101. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  102. package/dist/src/symbols/symbol-flow.js +2 -2
  103. package/dist/src/symbols/symbol-slot.d.ts +1 -1
  104. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  105. package/dist/src/symbols/symbol-slot.js +1 -1
  106. package/dist/src/symbols/symbol-table.d.ts +3 -3
  107. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  108. package/dist/src/tap.d.ts +1 -1
  109. package/dist/src/tap.d.ts.map +1 -1
  110. package/dist/src/tracer.d.ts +60 -3
  111. package/dist/src/tracer.d.ts.map +1 -1
  112. package/dist/src/tracer.js +60 -5
  113. package/dist/src/utils.d.ts +4 -3
  114. package/dist/src/utils.d.ts.map +1 -1
  115. package/dist/src/utils.js +2 -1
  116. package/dist/test/props-with-defaults.test.js +1 -1
  117. package/dist/test/reactive-union-set.test.js +1 -1
  118. package/dist/test/reactivity/cleanup.test.js +2 -1
  119. package/dist/test/reactivity/memo.test.js +1 -1
  120. package/dist/test/reactivity/untrack.test.js +1 -1
  121. package/dist/test/rendering/memoization.test.js +2 -1
  122. package/dist/test/split-props.test.js +1 -1
  123. package/dist/test/symbols/output-scope.test.js +2 -1
  124. package/dist/test/symbols/output-symbol.test.js +4 -3
  125. package/dist/test/symbols/resolution.test.js +2 -1
  126. package/dist/test/symbols/utils.d.ts +3 -2
  127. package/dist/test/symbols/utils.d.ts.map +1 -1
  128. package/dist/test/symbols/utils.js +2 -1
  129. package/dist/testing/extend-expect.d.ts +15 -0
  130. package/dist/testing/extend-expect.d.ts.map +1 -1
  131. package/dist/testing/extend-expect.js +2 -1
  132. package/dist/testing/render.d.ts +1 -1
  133. package/dist/testing/render.d.ts.map +1 -1
  134. package/dist/tsconfig.tsbuildinfo +1 -1
  135. package/package.json +28 -24
  136. package/src/binder.ts +3 -5
  137. package/src/code.ts +1 -1
  138. package/src/components/Block.tsx +1 -1
  139. package/src/components/Declaration.tsx +2 -1
  140. package/src/components/For.tsx +2 -1
  141. package/src/components/Indent.tsx +1 -1
  142. package/src/components/List.tsx +3 -1
  143. package/src/components/MemberDeclaration.tsx +4 -3
  144. package/src/components/MemberScope.tsx +2 -2
  145. package/src/components/Output.tsx +2 -1
  146. package/src/components/Prose.tsx +1 -1
  147. package/src/components/ReferenceOrContent.tsx +1 -1
  148. package/src/components/Scope.tsx +1 -1
  149. package/src/components/Show.tsx +1 -1
  150. package/src/components/SourceDirectory.tsx +2 -1
  151. package/src/components/SourceFile.tsx +2 -1
  152. package/src/components/StatementList.tsx +1 -1
  153. package/src/components/Switch.tsx +2 -1
  154. package/src/components/Wrap.tsx +1 -1
  155. package/src/context/binder.ts +1 -1
  156. package/src/context/source-file.ts +1 -1
  157. package/src/context.ts +3 -7
  158. package/src/debug.ts +5 -1
  159. package/src/index.ts +4 -1
  160. package/src/jsx-runtime.ts +15 -690
  161. package/src/props-combinators.ts +148 -0
  162. package/src/reactive-union-set.ts +1 -1
  163. package/src/reactivity.ts +230 -0
  164. package/src/render.ts +97 -26
  165. package/src/runtime/component.ts +67 -0
  166. package/src/runtime/intrinsic.ts +199 -0
  167. package/src/slot.ts +3 -4
  168. package/src/stc.ts +2 -2
  169. package/src/sti.ts +11 -11
  170. package/src/symbols/flags.ts +82 -0
  171. package/src/symbols/index.ts +1 -0
  172. package/src/symbols/output-scope.ts +2 -29
  173. package/src/symbols/output-symbol.ts +2 -55
  174. package/src/symbols/symbol-flow.ts +3 -7
  175. package/src/symbols/symbol-slot.tsx +2 -1
  176. package/src/symbols/symbol-table.ts +3 -3
  177. package/src/tap.ts +1 -1
  178. package/src/tracer.ts +38 -4
  179. package/src/utils.tsx +7 -5
  180. package/temp/api.json +984 -1261
  181. package/test/props-with-defaults.test.ts +1 -1
  182. package/test/reactive-union-set.test.tsx +1 -1
  183. package/test/reactivity/cleanup.test.tsx +2 -1
  184. package/test/reactivity/memo.test.tsx +1 -1
  185. package/test/reactivity/untrack.test.ts +1 -1
  186. package/test/rendering/basic.test.tsx +1 -1
  187. package/test/rendering/memoization.test.tsx +1 -1
  188. package/test/split-props.test.ts +1 -1
  189. package/test/symbols/output-scope.test.ts +2 -4
  190. package/test/symbols/output-symbol.test.ts +4 -7
  191. package/test/symbols/resolution.test.ts +2 -4
  192. package/test/symbols/utils.ts +3 -5
  193. package/test/utils.test.tsx +1 -1
  194. package/testing/extend-expect.ts +16 -0
  195. package/testing/render.ts +1 -2
  196. 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 "./jsx-runtime.js";
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
- traceRender("render", () => dumpChildren(children));
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
- traceRender("appendChild", () => debugPrintChild(rawChild));
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
- traceRender("appendChild:cached", () => debugPrintChild(child));
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
- traceRender("appendChild:custom-context", () => debugPrintChild(child));
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
- // don't need a new context here because intrinsics are never reactive
325
- traceRender("appendChild:intrinsic-element", () =>
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
- traceRender("appendChild:component", () => debugPrintChild(child));
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
- traceRender("appendChild:component-done", () => debugPrintChild(child));
465
+ trace(
466
+ TracePhase.render.appendChild,
467
+ () => "Component done: " + debugPrintChild(child),
468
+ );
453
469
  });
454
470
  } else if (typeof child === "function") {
455
- traceRender("appendChild:memo", () => child.toString());
471
+ trace(TracePhase.render.appendChild, () => "Memo: " + child.toString());
456
472
  const index = node.length;
457
473
  effect(() => {
458
- traceRender("memoEffect:run", () => "");
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
+ }