@alloy-js/core 0.10.0 → 0.12.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.
Files changed (183) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/src/binder.d.ts.map +1 -1
  3. package/dist/src/binder.js +100 -19
  4. package/dist/src/code.js +1 -2
  5. package/dist/src/components/Block.js +2 -5
  6. package/dist/src/components/Declaration.js +2 -4
  7. package/dist/src/components/For.d.ts +2 -2
  8. package/dist/src/components/For.d.ts.map +1 -1
  9. package/dist/src/components/For.js +1 -2
  10. package/dist/src/components/Indent.js +2 -4
  11. package/dist/src/components/List.js +2 -5
  12. package/dist/src/components/MemberDeclaration.js +2 -4
  13. package/dist/src/components/MemberName.js +1 -2
  14. package/dist/src/components/MemberScope.js +2 -4
  15. package/dist/src/components/Name.js +1 -2
  16. package/dist/src/components/Output.js +2 -4
  17. package/dist/src/components/Prose.js +1 -2
  18. package/dist/src/components/ReferenceOrContent.d.ts +8 -0
  19. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -0
  20. package/dist/src/components/ReferenceOrContent.js +11 -0
  21. package/dist/src/components/Scope.js +2 -4
  22. package/dist/src/components/Show.js +1 -2
  23. package/dist/src/components/SourceDirectory.js +2 -4
  24. package/dist/src/components/SourceFile.js +2 -5
  25. package/dist/src/components/StatementList.js +2 -4
  26. package/dist/src/components/Switch.d.ts +1 -1
  27. package/dist/src/components/Switch.d.ts.map +1 -1
  28. package/dist/src/components/Switch.js +1 -2
  29. package/dist/src/components/Wrap.js +2 -4
  30. package/dist/src/components/index.d.ts +1 -0
  31. package/dist/src/components/index.d.ts.map +1 -1
  32. package/dist/src/components/index.js +2 -2
  33. package/dist/src/components/stc/index.d.ts +1 -0
  34. package/dist/src/components/stc/index.d.ts.map +1 -1
  35. package/dist/src/components/stc/index.js +2 -2
  36. package/dist/src/components/stc/sti.js +1 -2
  37. package/dist/src/context/assignment.js +1 -2
  38. package/dist/src/context/binder.js +1 -2
  39. package/dist/src/context/declaration.js +1 -2
  40. package/dist/src/context/index.js +1 -2
  41. package/dist/src/context/member-declaration.js +1 -2
  42. package/dist/src/context/member-scope.js +1 -2
  43. package/dist/src/context/name-policy.js +1 -2
  44. package/dist/src/context/scope.js +1 -2
  45. package/dist/src/context/source-directory.js +1 -2
  46. package/dist/src/context/source-file.js +1 -2
  47. package/dist/src/context.js +1 -2
  48. package/dist/src/debug.js +13 -15
  49. package/dist/src/index.browser.js +1 -2
  50. package/dist/src/index.js +1 -2
  51. package/dist/src/jsx-runtime.d.ts +1 -1
  52. package/dist/src/jsx-runtime.d.ts.map +1 -1
  53. package/dist/src/jsx-runtime.js +10 -5
  54. package/dist/src/name-policy.js +1 -2
  55. package/dist/src/refkey.js +1 -2
  56. package/dist/src/render.d.ts.map +1 -1
  57. package/dist/src/render.js +6 -2
  58. package/dist/src/scheduler.d.ts +8 -0
  59. package/dist/src/scheduler.d.ts.map +1 -0
  60. package/dist/src/scheduler.js +17 -0
  61. package/dist/src/slot.js +1 -2
  62. package/dist/src/stc.js +1 -2
  63. package/dist/src/sti.js +1 -2
  64. package/dist/src/tap.js +1 -2
  65. package/dist/src/tsdoc-metadata.json +1 -1
  66. package/dist/src/utils.js +2 -4
  67. package/dist/src/write-output.browser.js +1 -2
  68. package/dist/src/write-output.js +1 -2
  69. package/dist/test/browser-build.test.js +85 -0
  70. package/dist/test/children.test.js +27 -0
  71. package/dist/test/components/block.test.js +45 -0
  72. package/dist/test/components/declaration.test.js +32 -0
  73. package/dist/test/components/list.test.js +86 -0
  74. package/dist/test/components/prose.test.js +25 -0
  75. package/dist/test/components/reference-or-content.test.d.ts +2 -0
  76. package/dist/test/components/reference-or-content.test.d.ts.map +1 -0
  77. package/dist/test/components/reference-or-content.test.js +149 -0
  78. package/dist/test/components/slot.test.js +134 -0
  79. package/dist/test/components/source-file.test.js +64 -0
  80. package/dist/test/components/wrap.test.js +35 -0
  81. package/dist/test/control-flow/for.test.js +219 -0
  82. package/dist/test/control-flow/match.test.js +67 -0
  83. package/dist/test/control-flow/show.test.js +29 -0
  84. package/dist/test/name-policy.test.js +19 -0
  85. package/dist/test/props-with-defaults.test.js +93 -0
  86. package/dist/test/reactivity/circular-reactives.test.d.ts +2 -0
  87. package/dist/test/reactivity/circular-reactives.test.d.ts.map +1 -0
  88. package/dist/test/reactivity/circular-reactives.test.js +31 -0
  89. package/dist/test/reactivity/cleanup.test.js +82 -0
  90. package/dist/test/reactivity/memo.test.js +16 -0
  91. package/dist/test/reactivity/ref-rendering.test.js +37 -0
  92. package/dist/test/reactivity/test.test.js +61 -0
  93. package/dist/test/reactivity/untrack.test.js +26 -0
  94. package/dist/test/refkey.test.js +25 -0
  95. package/dist/test/rendering/basic.test.js +96 -0
  96. package/dist/test/rendering/code.test.js +55 -0
  97. package/dist/test/rendering/formatting.test.js +402 -0
  98. package/dist/test/rendering/indent.test.js +90 -0
  99. package/dist/test/rendering/memoization.test.js +27 -0
  100. package/dist/test/rendering/refkeys.test.js +32 -0
  101. package/dist/test/split-props.test.js +77 -0
  102. package/dist/test/stc.test.js +34 -0
  103. package/dist/test/symbols.test.js +877 -0
  104. package/dist/test/utils.test.d.ts.map +1 -1
  105. package/dist/test/utils.test.js +223 -0
  106. package/dist/testing/extend-expect.js +1 -2
  107. package/dist/testing/index.js +1 -2
  108. package/dist/testing/render.js +1 -2
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +14 -22
  111. package/src/binder.ts +100 -17
  112. package/src/components/For.tsx +6 -6
  113. package/src/components/ReferenceOrContent.tsx +22 -0
  114. package/src/components/index.tsx +1 -0
  115. package/src/components/stc/index.ts +1 -0
  116. package/src/debug.ts +12 -13
  117. package/src/jsx-runtime.ts +24 -14
  118. package/src/render.ts +5 -0
  119. package/src/scheduler.ts +24 -0
  120. package/temp/api.json +216 -15
  121. package/test/components/declaration.test.tsx +2 -0
  122. package/test/components/list.test.tsx +0 -1
  123. package/test/components/reference-or-content.test.tsx +138 -0
  124. package/test/control-flow/for.test.tsx +34 -4
  125. package/test/reactivity/circular-reactives.test.tsx +32 -0
  126. package/test/reactivity/cleanup.test.tsx +5 -0
  127. package/test/reactivity/untrack.test.ts +3 -0
  128. package/test/rendering/memoization.test.tsx +2 -0
  129. package/test/symbols.test.ts +392 -13
  130. package/test/utils.test.tsx +2 -0
  131. package/babel.config.cjs +0 -4
  132. package/dist/src/binder.js.map +0 -1
  133. package/dist/src/code.js.map +0 -1
  134. package/dist/src/components/Block.js.map +0 -1
  135. package/dist/src/components/Declaration.js.map +0 -1
  136. package/dist/src/components/For.js.map +0 -1
  137. package/dist/src/components/Indent.js.map +0 -1
  138. package/dist/src/components/List.js.map +0 -1
  139. package/dist/src/components/MemberDeclaration.js.map +0 -1
  140. package/dist/src/components/MemberName.js.map +0 -1
  141. package/dist/src/components/MemberScope.js.map +0 -1
  142. package/dist/src/components/Name.js.map +0 -1
  143. package/dist/src/components/Output.js.map +0 -1
  144. package/dist/src/components/Prose.js.map +0 -1
  145. package/dist/src/components/Scope.js.map +0 -1
  146. package/dist/src/components/Show.js.map +0 -1
  147. package/dist/src/components/SourceDirectory.js.map +0 -1
  148. package/dist/src/components/SourceFile.js.map +0 -1
  149. package/dist/src/components/StatementList.js.map +0 -1
  150. package/dist/src/components/Switch.js.map +0 -1
  151. package/dist/src/components/Wrap.js.map +0 -1
  152. package/dist/src/components/index.js.map +0 -1
  153. package/dist/src/components/stc/index.js.map +0 -1
  154. package/dist/src/components/stc/sti.js.map +0 -1
  155. package/dist/src/context/assignment.js.map +0 -1
  156. package/dist/src/context/binder.js.map +0 -1
  157. package/dist/src/context/declaration.js.map +0 -1
  158. package/dist/src/context/index.js.map +0 -1
  159. package/dist/src/context/member-declaration.js.map +0 -1
  160. package/dist/src/context/member-scope.js.map +0 -1
  161. package/dist/src/context/name-policy.js.map +0 -1
  162. package/dist/src/context/scope.js.map +0 -1
  163. package/dist/src/context/source-directory.js.map +0 -1
  164. package/dist/src/context/source-file.js.map +0 -1
  165. package/dist/src/context.js.map +0 -1
  166. package/dist/src/debug.js.map +0 -1
  167. package/dist/src/index.browser.js.map +0 -1
  168. package/dist/src/index.js.map +0 -1
  169. package/dist/src/jsx-runtime.js.map +0 -1
  170. package/dist/src/name-policy.js.map +0 -1
  171. package/dist/src/refkey.js.map +0 -1
  172. package/dist/src/render.js.map +0 -1
  173. package/dist/src/slot.js.map +0 -1
  174. package/dist/src/stc.js.map +0 -1
  175. package/dist/src/sti.js.map +0 -1
  176. package/dist/src/tap.js.map +0 -1
  177. package/dist/src/utils.js.map +0 -1
  178. package/dist/src/write-output.browser.js.map +0 -1
  179. package/dist/src/write-output.js.map +0 -1
  180. package/dist/testing/extend-expect.js.map +0 -1
  181. package/dist/testing/index.js.map +0 -1
  182. package/dist/testing/render.js.map +0 -1
  183. package/dist/testing/vitest.d.js.map +0 -1
package/src/binder.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  computed,
3
- effect,
4
3
  reactive,
5
4
  ref,
6
5
  Ref,
@@ -10,8 +9,9 @@ import {
10
9
  import { useBinder } from "./context/binder.js";
11
10
  import { useMemberScope } from "./context/member-scope.js";
12
11
  import { useScope } from "./context/scope.js";
13
- import { memo, untrack } from "./jsx-runtime.js";
12
+ import { effect, memo, onCleanup, untrack } from "./jsx-runtime.js";
14
13
  import { refkey, Refkey } from "./refkey.js";
14
+ import { queueJob, QueueJob } from "./scheduler.js";
15
15
  export type Metadata = object;
16
16
 
17
17
  /**
@@ -464,6 +464,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
464
464
  OutputScope,
465
465
  Map<string, Ref<OutputScope | undefined>>
466
466
  >();
467
+ const deconflictJobs = new Map<OutputScope, Map<string, QueueJob>>();
467
468
 
468
469
  return binder;
469
470
 
@@ -622,26 +623,77 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
622
623
  }
623
624
 
624
625
  function instantiateSymbolInto(source: OutputSymbol, target: OutputSymbol) {
625
- if (~source.flags & OutputSymbolFlags.InstanceMemberContainer) {
626
- throw new Error("Can only instantiate symbols with instance members");
626
+ if (target.staticMemberScope) {
627
+ return;
627
628
  }
628
629
 
629
- addInstanceMembersToSymbol(target);
630
+ // Ensure static member scope exists
631
+ addStaticMembersToSymbol(target);
630
632
 
631
633
  effect(() => {
632
- for (const sym of source.instanceMemberScope!.symbols) {
633
- if (target.instanceMemberScope!.symbols.has(sym)) {
634
- continue;
635
- }
634
+ // copy instance members if it's an instance‐container
635
+ if (source.flags & OutputSymbolFlags.InstanceMemberContainer) {
636
+ copyMembers(
637
+ source.instanceMemberScope!.symbols,
638
+ target,
639
+ target.staticMemberScope!,
640
+ );
641
+ }
636
642
 
637
- createSymbol({
638
- name: sym.name,
639
- scope: target.instanceMemberScope!,
640
- refkey: [refkey(target.refkeys[0], sym.refkeys[0])],
641
- flags: sym.flags | OutputSymbolFlags.InstanceMember,
642
- });
643
+ // copy static members if it's a static‐container
644
+ if (source.flags & OutputSymbolFlags.StaticMemberContainer) {
645
+ copyMembers(
646
+ source.staticMemberScope!.symbols,
647
+ target,
648
+ target.staticMemberScope!,
649
+ );
643
650
  }
644
651
  });
652
+
653
+ /**
654
+ * Recursively copy `symbols` from `sourceSym` into `intoScope` of `targetSym`.
655
+ * Always marks each instantiation as StaticMember so lookups use dot notation (e.g. Parent.child)
656
+ * and preserves any StaticMemberContainer flag to auto create newSym.staticMemberScope.
657
+ */
658
+ function copyMembers(
659
+ symbols: Set<OutputSymbol>,
660
+ targetSym: OutputSymbol,
661
+ intoScope: OutputScope,
662
+ ) {
663
+ for (const srcSym of symbols) {
664
+ untrack(() => {
665
+ const wantKey = refkey(targetSym.refkeys[0], srcSym.refkeys[0]);
666
+
667
+ // create the new symbol. Preserve StaticMemberContainer if present
668
+ const newSym = createSymbol({
669
+ name: srcSym.name,
670
+ scope: intoScope,
671
+ refkey: wantKey,
672
+ flags: srcSym.flags | OutputSymbolFlags.StaticMember,
673
+ });
674
+
675
+ onCleanup(() => {
676
+ binder.deleteSymbol(newSym);
677
+ });
678
+
679
+ // if the source symbol itself was a container of static members,
680
+ // recurse into the newSym.staticMemberScope that createSymbol just gave us
681
+ if (
682
+ srcSym.staticMemberScope &&
683
+ srcSym.staticMemberScope.symbols.size > 0
684
+ ) {
685
+ // ensure we have that scope
686
+ addStaticMembersToSymbol(newSym);
687
+
688
+ copyMembers(
689
+ srcSym.staticMemberScope.symbols,
690
+ newSym,
691
+ newSym.staticMemberScope!,
692
+ );
693
+ }
694
+ });
695
+ }
696
+ }
645
697
  }
646
698
 
647
699
  function addStaticMembersToSymbol(symbol: OutputSymbol) {
@@ -789,7 +841,7 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
789
841
  function deconflict(symbol: OutputSymbol) {
790
842
  const scope = symbol.scope;
791
843
  const existingNames = [...scope.symbols].filter(
792
- (sym) => sym.originalName === symbol.name,
844
+ (sym) => sym.name === symbol.name,
793
845
  );
794
846
 
795
847
  if (existingNames.length < 2) {
@@ -797,7 +849,13 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
797
849
  }
798
850
 
799
851
  if (options.nameConflictResolver) {
800
- options.nameConflictResolver(symbol.name, existingNames);
852
+ queueJob(
853
+ deconflictJobForScopeAndName(
854
+ scope,
855
+ symbol.name,
856
+ options.nameConflictResolver,
857
+ ),
858
+ );
801
859
  } else {
802
860
  // default disambiguation is first-wins
803
861
  for (let i = 1; i < existingNames.length; i++) {
@@ -806,6 +864,31 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
806
864
  }
807
865
  }
808
866
 
867
+ function deconflictJobForScopeAndName(
868
+ scope: OutputScope,
869
+ name: string,
870
+ handler: NameConflictResolver,
871
+ ) {
872
+ if (!deconflictJobs.has(scope)) {
873
+ deconflictJobs.set(scope, new Map());
874
+ }
875
+
876
+ const jobs = deconflictJobs.get(scope)!;
877
+ if (jobs.has(name)) {
878
+ return jobs.get(name)!;
879
+ }
880
+ const job = () => {
881
+ const conflictedSymbols = [...scope.symbols].filter(
882
+ (sym) => sym.name === name,
883
+ );
884
+ handler(name, conflictedSymbols);
885
+ jobs.delete(name);
886
+ };
887
+
888
+ jobs.set(name, job);
889
+ return job;
890
+ }
891
+
809
892
  function getSymbolForRefkey<TSymbol extends OutputSymbol>(
810
893
  refkey: Refkey,
811
894
  ): Ref<TSymbol | undefined> {
@@ -4,9 +4,9 @@ import { baseListPropsToMapJoinArgs, mapJoin } from "../utils.js";
4
4
  import { BaseListProps } from "./List.jsx";
5
5
 
6
6
  export type ForCallbackArgs<T> =
7
- T extends Ref<infer U> ? ForCallbackArgs<U>
7
+ number extends keyof T ? [value: T[number]]
8
+ : T extends Ref<infer U> ? ForCallbackArgs<U>
8
9
  : T extends () => infer U ? ForCallbackArgs<U>
9
- : T extends (infer U)[] ? [value: U]
10
10
  : T extends Map<infer U, infer V> ? [key: U, value: V]
11
11
  : T extends Set<infer U> ? [value: U]
12
12
  : T extends IterableIterator<infer U> ? [value: U]
@@ -38,10 +38,10 @@ export interface ForProps<
38
38
  }
39
39
 
40
40
  export type ForSupportedCollections =
41
- | any[]
42
- | Map<any, any>
43
- | Set<any>
44
- | IterableIterator<any>;
41
+ | readonly unknown[]
42
+ | ReadonlyMap<unknown, unknown>
43
+ | ReadonlySet<unknown>
44
+ | IterableIterator<unknown>;
45
45
  /**
46
46
  * The For component iterates over the provided array and invokes the child
47
47
  * callback for each item. It can optionally be provided with a `joiner` which
@@ -0,0 +1,22 @@
1
+ import { Children } from "@alloy-js/core/jsx-runtime";
2
+ import { computed } from "@vue/reactivity";
3
+ import { useContext } from "../context.js";
4
+ import { BinderContext } from "../context/binder.js";
5
+ import type { Refkey } from "../refkey.js";
6
+
7
+ export interface ReferenceOrContentProps {
8
+ readonly refkey: Refkey;
9
+ readonly children: Children;
10
+ }
11
+
12
+ export function ReferenceOrContent(props: ReferenceOrContentProps) {
13
+ const binder = useContext(BinderContext);
14
+ if (!binder) {
15
+ throw new Error("Need binder context to create declarations");
16
+ }
17
+
18
+ const sym = binder.getSymbolForRefkey(props.refkey);
19
+ return computed(() =>
20
+ sym.value === undefined ? props.children : props.refkey,
21
+ );
22
+ }
@@ -9,6 +9,7 @@ export * from "./MemberScope.jsx";
9
9
  export * from "./Name.jsx";
10
10
  export * from "./Output.js";
11
11
  export * from "./Prose.jsx";
12
+ export * from "./ReferenceOrContent.jsx";
12
13
  export * from "./Scope.js";
13
14
  export * from "./Show.jsx";
14
15
  export * from "./SourceDirectory.js";
@@ -19,5 +19,6 @@ export const SourceDirectory = stc(base.SourceDirectory);
19
19
  export const SourceFile = stc(base.SourceFile);
20
20
  export const Switch = stc(base.Switch);
21
21
  export const Wrap = stc(base.Wrap);
22
+ export const ReferenceOrContent = stc(base.ReferenceOrContent);
22
23
 
23
24
  export * from "./sti.js";
package/src/debug.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isReactive } from "@vue/reactivity";
2
- import { Chalk } from "chalk";
3
2
  import Table from "cli-table3";
3
+ import pc from "picocolors";
4
4
  import { contextsByKey } from "./context.js";
5
5
  import { Context, getContext } from "./jsx-runtime.js";
6
6
 
@@ -56,14 +56,14 @@ function debugStack() {
56
56
  { hAlign: "right", content: "props" },
57
57
  props && Object.keys(props).length > 0 ?
58
58
  dumpValue(props)
59
- : chalk.gray("(none)"),
59
+ : pc.gray("(none)"),
60
60
  ]);
61
61
 
62
62
  table.push([
63
63
  { hAlign: "right", content: "contexts" },
64
64
  foundContexts.length > 0 ?
65
65
  foundContexts.map((c) => printContext(c, true)).join("\n")
66
- : chalk.gray("(none)"),
66
+ : pc.gray("(none)"),
67
67
  ]);
68
68
 
69
69
  process.stdout.write(table.toString() + "\n\n");
@@ -119,39 +119,38 @@ declare global {
119
119
 
120
120
  globalThis.debug = debug;
121
121
 
122
- const chalk = new Chalk();
123
122
  const style = {
124
123
  value: {
125
124
  primitive(value: string | number | boolean | null | undefined) {
126
125
  switch (typeof value) {
127
126
  case "string":
128
- return chalk.blue(`"${value}"`);
127
+ return pc.blue(`"${value}"`);
129
128
  case "object":
130
129
  case "undefined":
131
- return chalk.gray(String(value));
130
+ return pc.gray(String(value));
132
131
  default:
133
- return chalk.blue(String(value));
132
+ return pc.blue(String(value));
134
133
  }
135
134
  },
136
135
  symbol(value: symbol) {
137
- return chalk.gray(String(value));
136
+ return pc.gray(String(value));
138
137
  },
139
138
  },
140
139
  context: {
141
140
  name(name: string) {
142
- return chalk.bgGray(` ${chalk.white(name)} `);
141
+ return pc.bgBlack(` ${pc.white(name)} `);
143
142
  },
144
143
  },
145
144
  component: {
146
145
  name(name: string) {
147
- return chalk.bgBlue(` <${chalk.white(name)}> `);
146
+ return pc.bgBlue(` <${pc.white(name)}> `);
148
147
  },
149
148
  },
150
149
  };
151
150
 
152
151
  function reactiveTag(value: unknown) {
153
152
  if (isReactive(value)) {
154
- return " " + chalk.greenBright(`reactive`) + " ";
153
+ return " " + pc.greenBright(`reactive`) + " ";
155
154
  }
156
155
  return "";
157
156
  }
@@ -168,7 +167,7 @@ function dumpValue(value: unknown, level = 0) {
168
167
  if (value === null) {
169
168
  return style.value.primitive(null) + reactiveTag(value);
170
169
  } else {
171
- if (level > 0) return chalk.gray(`{ ... }` + reactiveTag(value));
170
+ if (level > 0) return pc.gray(`{ ... }` + reactiveTag(value));
172
171
 
173
172
  const table = kvTable(" ");
174
173
 
@@ -179,7 +178,7 @@ function dumpValue(value: unknown, level = 0) {
179
178
  return table.toString();
180
179
  }
181
180
  case "function":
182
- return chalk.gray("ƒ ()");
181
+ return pc.gray("ƒ ()");
183
182
  case "undefined":
184
183
  return style.value.primitive(undefined);
185
184
  }
@@ -5,6 +5,7 @@ import {
5
5
  isReactive,
6
6
  pauseTracking,
7
7
  proxyRefs,
8
+ ReactiveEffectRunner,
8
9
  Ref,
9
10
  resetTracking,
10
11
  shallowRef,
@@ -14,6 +15,7 @@ import {
14
15
  } from "@vue/reactivity";
15
16
  import { Refkey } from "./refkey.js";
16
17
  import { RenderedTextTree } from "./render.js";
18
+ import { scheduler } from "./scheduler.js";
17
19
 
18
20
  if ((globalThis as any).ALLOY) {
19
21
  throw new Error(
@@ -125,21 +127,29 @@ export function effect<T>(fn: (prev?: T) => T, current?: T) {
125
127
  for (let k = 0, len = d.length; k < len; k++) d[k]();
126
128
 
127
129
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
128
- final && stop(c);
130
+ final && stop(runner);
129
131
  };
130
132
 
131
133
  onCleanup(() => cleanupFn(true));
132
- const c = vueEffect(() => {
133
- cleanupFn(false);
134
-
135
- const oldContext = globalContext;
136
- globalContext = context;
137
- try {
138
- current = fn(current);
139
- } finally {
140
- globalContext = oldContext;
141
- }
142
- }, {});
134
+ const runner: ReactiveEffectRunner<void> = vueEffect(
135
+ () => {
136
+ cleanupFn(false);
137
+
138
+ const oldContext = globalContext;
139
+ globalContext = context;
140
+ try {
141
+ current = fn(current);
142
+ } finally {
143
+ globalContext = oldContext;
144
+ }
145
+ },
146
+ {
147
+ scheduler: scheduler(() => runner),
148
+ },
149
+ );
150
+
151
+ // allow recursive effects (recursive option does nothing, possible bug)
152
+ (runner as any).effect.flags |= 1 << 5;
143
153
  }
144
154
 
145
155
  /**
@@ -462,9 +472,9 @@ export function createComponent<TProps extends Props = Props>(
462
472
  export function taggedComponent<TProps = Props>(
463
473
  tag: symbol,
464
474
  component: Component<TProps>,
465
- ): Component<TProps> {
475
+ ): Component<TProps> & Required<Pick<Component<TProps>, "tag">> {
466
476
  component.tag = tag;
467
- return component;
477
+ return component as any;
468
478
  }
469
479
 
470
480
  export const intrinsicElementKey = Symbol();
package/src/render.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  untrack,
23
23
  } from "./jsx-runtime.js";
24
24
  import { isRefkey } from "./refkey.js";
25
+ import { flushJobs } from "./scheduler.js";
25
26
  const {
26
27
  builders: {
27
28
  align,
@@ -181,6 +182,7 @@ export function render(
181
182
  options?: PrintTreeOptions,
182
183
  ): OutputDirectory {
183
184
  const tree = renderTree(children);
185
+ flushJobs();
184
186
  let rootDirectory: OutputDirectory | undefined = undefined;
185
187
 
186
188
  // when passing Output, the first render tree child is the Output component.
@@ -559,6 +561,9 @@ export function printTree(tree: RenderedTextTree, options?: PrintTreeOptions) {
559
561
  ),
560
562
  };
561
563
 
564
+ // make sure queue is empty
565
+ flushJobs();
566
+
562
567
  const d = printTreeWorker(tree);
563
568
  return doc.printer.printDocToString(d, options as doc.printer.Options)
564
569
  .formatted;
@@ -0,0 +1,24 @@
1
+ import { ReactiveEffectRunner } from "@vue/reactivity";
2
+
3
+ export interface QueueJob {
4
+ (): any;
5
+ }
6
+ const queue = new Set<QueueJob>();
7
+
8
+ export function scheduler(jobGetter: () => ReactiveEffectRunner) {
9
+ return () => {
10
+ queueJob(jobGetter());
11
+ };
12
+ }
13
+ export function queueJob(job: QueueJob) {
14
+ // the set is serving an important purpose here in deduping the effects we run
15
+ // (which in effect coalesces multiple update effects together).
16
+ queue.add(job);
17
+ }
18
+
19
+ export function flushJobs() {
20
+ for (const job of queue) {
21
+ queue.delete(job);
22
+ job();
23
+ }
24
+ }