@alloy-js/core 0.23.0-dev.0 → 0.23.0-dev.10

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 (316) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +68 -0
  3. package/dist/src/binder.d.ts +2 -0
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +55 -12
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/components/AccessExpression.d.ts +78 -0
  8. package/dist/src/components/AccessExpression.d.ts.map +1 -0
  9. package/dist/src/components/AccessExpression.js +218 -0
  10. package/dist/src/components/AccessExpression.js.map +1 -0
  11. package/dist/src/components/AccessExpression.test.d.ts +2 -0
  12. package/dist/src/components/AccessExpression.test.d.ts.map +1 -0
  13. package/dist/src/components/AccessExpression.test.js +137 -0
  14. package/dist/src/components/AccessExpression.test.js.map +1 -0
  15. package/dist/src/components/AppendFile.d.ts.map +1 -1
  16. package/dist/src/components/AppendFile.js +14 -3
  17. package/dist/src/components/AppendFile.js.map +1 -1
  18. package/dist/src/components/Block.js +1 -1
  19. package/dist/src/components/Block.js.map +1 -1
  20. package/dist/src/components/Declaration.d.ts.map +1 -1
  21. package/dist/src/components/Declaration.js +2 -1
  22. package/dist/src/components/Declaration.js.map +1 -1
  23. package/dist/src/components/Prose.js +2 -2
  24. package/dist/src/components/Prose.js.map +1 -1
  25. package/dist/src/components/Scope.d.ts.map +1 -1
  26. package/dist/src/components/Scope.js +6 -1
  27. package/dist/src/components/Scope.js.map +1 -1
  28. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  29. package/dist/src/components/SourceDirectory.js +1 -2
  30. package/dist/src/components/SourceDirectory.js.map +1 -1
  31. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  32. package/dist/src/components/TemplateFile.js +18 -3
  33. package/dist/src/components/TemplateFile.js.map +1 -1
  34. package/dist/src/components/index.d.ts +1 -0
  35. package/dist/src/components/index.d.ts.map +1 -1
  36. package/dist/src/components/index.js +1 -0
  37. package/dist/src/components/index.js.map +1 -1
  38. package/dist/src/content-slot.d.ts.map +1 -1
  39. package/dist/src/content-slot.js +7 -6
  40. package/dist/src/content-slot.js.map +1 -1
  41. package/dist/src/context.d.ts.map +1 -1
  42. package/dist/src/context.js +10 -3
  43. package/dist/src/context.js.map +1 -1
  44. package/dist/src/debug/cli.d.ts +6 -0
  45. package/dist/src/debug/cli.d.ts.map +1 -0
  46. package/dist/src/{debug.js → debug/cli.js} +78 -84
  47. package/dist/src/debug/cli.js.map +1 -0
  48. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  49. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  50. package/dist/src/debug/diagnostics.test.js +45 -0
  51. package/dist/src/debug/diagnostics.test.js.map +1 -0
  52. package/dist/src/debug/effects.d.ts +73 -0
  53. package/dist/src/debug/effects.d.ts.map +1 -0
  54. package/dist/src/debug/effects.js +228 -0
  55. package/dist/src/debug/effects.js.map +1 -0
  56. package/dist/src/debug/effects.test.d.ts +2 -0
  57. package/dist/src/debug/effects.test.d.ts.map +1 -0
  58. package/dist/src/debug/effects.test.js +84 -0
  59. package/dist/src/debug/effects.test.js.map +1 -0
  60. package/dist/src/debug/files.d.ts +14 -0
  61. package/dist/src/debug/files.d.ts.map +1 -0
  62. package/dist/src/debug/files.js +40 -0
  63. package/dist/src/debug/files.js.map +1 -0
  64. package/dist/src/debug/files.test.d.ts +2 -0
  65. package/dist/src/debug/files.test.d.ts.map +1 -0
  66. package/dist/src/debug/files.test.js +89 -0
  67. package/dist/src/debug/files.test.js.map +1 -0
  68. package/dist/src/debug/index.d.ts +61 -0
  69. package/dist/src/debug/index.d.ts.map +1 -0
  70. package/dist/src/debug/index.js +69 -0
  71. package/dist/src/debug/index.js.map +1 -0
  72. package/dist/src/debug/render.d.ts +57 -0
  73. package/dist/src/debug/render.d.ts.map +1 -0
  74. package/dist/src/debug/render.js +519 -0
  75. package/dist/src/debug/render.js.map +1 -0
  76. package/dist/src/debug/render.test.d.ts +2 -0
  77. package/dist/src/debug/render.test.d.ts.map +1 -0
  78. package/dist/src/debug/render.test.js +328 -0
  79. package/dist/src/debug/render.test.js.map +1 -0
  80. package/dist/src/debug/serialize.d.ts +9 -0
  81. package/dist/src/debug/serialize.d.ts.map +1 -0
  82. package/dist/src/debug/serialize.js +70 -0
  83. package/dist/src/debug/serialize.js.map +1 -0
  84. package/dist/src/debug/symbols.d.ts +15 -0
  85. package/dist/src/debug/symbols.d.ts.map +1 -0
  86. package/dist/src/debug/symbols.js +173 -0
  87. package/dist/src/debug/symbols.js.map +1 -0
  88. package/dist/src/debug/symbols.test.d.ts +2 -0
  89. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  90. package/dist/src/debug/symbols.test.js +104 -0
  91. package/dist/src/debug/symbols.test.js.map +1 -0
  92. package/dist/src/debug/trace.d.ts +342 -0
  93. package/dist/src/debug/trace.d.ts.map +1 -0
  94. package/dist/src/debug/trace.js +443 -0
  95. package/dist/src/debug/trace.js.map +1 -0
  96. package/dist/src/devtools/devtools-protocol.d.ts +232 -0
  97. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  98. package/dist/src/devtools/devtools-protocol.js +2 -0
  99. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  100. package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
  101. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  102. package/dist/src/devtools/devtools-server.browser.js +36 -0
  103. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  104. package/dist/src/devtools/devtools-server.d.ts +72 -0
  105. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  106. package/dist/src/devtools/devtools-server.js +256 -0
  107. package/dist/src/devtools/devtools-server.js.map +1 -0
  108. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  109. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  110. package/dist/src/devtools/devtools-transport.js +114 -0
  111. package/dist/src/devtools/devtools-transport.js.map +1 -0
  112. package/dist/src/devtools-entry.browser.d.ts +4 -0
  113. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  114. package/dist/src/devtools-entry.browser.js +2 -0
  115. package/dist/src/devtools-entry.browser.js.map +1 -0
  116. package/dist/src/devtools-entry.d.ts +4 -0
  117. package/dist/src/devtools-entry.d.ts.map +1 -0
  118. package/dist/src/devtools-entry.js +2 -0
  119. package/dist/src/devtools-entry.js.map +1 -0
  120. package/dist/src/diagnostics.d.ts +34 -0
  121. package/dist/src/diagnostics.d.ts.map +1 -0
  122. package/dist/src/diagnostics.js +89 -0
  123. package/dist/src/diagnostics.js.map +1 -0
  124. package/dist/src/index.d.ts +3 -2
  125. package/dist/src/index.d.ts.map +1 -1
  126. package/dist/src/index.js +3 -2
  127. package/dist/src/index.js.map +1 -1
  128. package/dist/src/print-hook.d.ts +14 -0
  129. package/dist/src/print-hook.d.ts.map +1 -0
  130. package/dist/src/print-hook.js +10 -0
  131. package/dist/src/print-hook.js.map +1 -0
  132. package/dist/src/reactive-union-set.d.ts.map +1 -1
  133. package/dist/src/reactive-union-set.js +28 -3
  134. package/dist/src/reactive-union-set.js.map +1 -1
  135. package/dist/src/reactivity.d.ts +50 -8
  136. package/dist/src/reactivity.d.ts.map +1 -1
  137. package/dist/src/reactivity.js +225 -39
  138. package/dist/src/reactivity.js.map +1 -1
  139. package/dist/src/render-stack.d.ts +30 -0
  140. package/dist/src/render-stack.d.ts.map +1 -0
  141. package/dist/src/render-stack.js +251 -0
  142. package/dist/src/render-stack.js.map +1 -0
  143. package/dist/src/render.d.ts +9 -19
  144. package/dist/src/render.d.ts.map +1 -1
  145. package/dist/src/render.js +371 -159
  146. package/dist/src/render.js.map +1 -1
  147. package/dist/src/resource.d.ts.map +1 -1
  148. package/dist/src/resource.js +5 -0
  149. package/dist/src/resource.js.map +1 -1
  150. package/dist/src/runtime/component.d.ts +7 -1
  151. package/dist/src/runtime/component.d.ts.map +1 -1
  152. package/dist/src/runtime/component.js +4 -1
  153. package/dist/src/runtime/component.js.map +1 -1
  154. package/dist/src/scheduler.d.ts +8 -0
  155. package/dist/src/scheduler.d.ts.map +1 -1
  156. package/dist/src/scheduler.js +69 -3
  157. package/dist/src/scheduler.js.map +1 -1
  158. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  159. package/dist/src/symbols/basic-symbol.js +6 -1
  160. package/dist/src/symbols/basic-symbol.js.map +1 -1
  161. package/dist/src/symbols/decl.d.ts.map +1 -1
  162. package/dist/src/symbols/decl.js +5 -1
  163. package/dist/src/symbols/decl.js.map +1 -1
  164. package/dist/src/symbols/output-scope.d.ts +2 -1
  165. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  166. package/dist/src/symbols/output-scope.js +13 -8
  167. package/dist/src/symbols/output-scope.js.map +1 -1
  168. package/dist/src/symbols/output-symbol.d.ts +1 -0
  169. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  170. package/dist/src/symbols/output-symbol.js +25 -8
  171. package/dist/src/symbols/output-symbol.js.map +1 -1
  172. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  173. package/dist/src/symbols/symbol-flow.js +24 -8
  174. package/dist/src/symbols/symbol-flow.js.map +1 -1
  175. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  176. package/dist/src/symbols/symbol-slot.js +15 -0
  177. package/dist/src/symbols/symbol-slot.js.map +1 -1
  178. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  179. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  180. package/dist/src/symbols/symbol-slot.test.js +35 -0
  181. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  182. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  183. package/dist/src/symbols/symbol-table.js +6 -5
  184. package/dist/src/symbols/symbol-table.js.map +1 -1
  185. package/dist/src/trace.d.ts +2 -0
  186. package/dist/src/trace.d.ts.map +1 -0
  187. package/dist/src/trace.js +2 -0
  188. package/dist/src/trace.js.map +1 -0
  189. package/dist/src/tracer.d.ts +2 -228
  190. package/dist/src/tracer.d.ts.map +1 -1
  191. package/dist/src/tracer.js +5 -298
  192. package/dist/src/tracer.js.map +1 -1
  193. package/dist/src/utils.d.ts.map +1 -1
  194. package/dist/src/utils.js +7 -5
  195. package/dist/src/utils.js.map +1 -1
  196. package/dist/test/components/append-file.test.d.ts.map +1 -1
  197. package/dist/test/components/append-file.test.js +18 -10
  198. package/dist/test/components/append-file.test.js.map +1 -1
  199. package/dist/test/components/template-file.test.d.ts.map +1 -1
  200. package/dist/test/components/template-file.test.js +6 -4
  201. package/dist/test/components/template-file.test.js.map +1 -1
  202. package/dist/test/lazy-isempty.test.d.ts +2 -0
  203. package/dist/test/lazy-isempty.test.d.ts.map +1 -0
  204. package/dist/test/lazy-isempty.test.js +89 -0
  205. package/dist/test/lazy-isempty.test.js.map +1 -0
  206. package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
  207. package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
  208. package/dist/test/reactive-union-set-disposers.test.js +98 -0
  209. package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
  210. package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
  211. package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
  212. package/dist/test/reactivity/shallow-reactive.test.js +52 -0
  213. package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
  214. package/dist/test/rendering/basic.test.js +3 -0
  215. package/dist/test/rendering/basic.test.js.map +1 -1
  216. package/dist/test/rendering/print-render-stack.test.d.ts +2 -0
  217. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -0
  218. package/dist/test/rendering/print-render-stack.test.js +207 -0
  219. package/dist/test/rendering/print-render-stack.test.js.map +1 -0
  220. package/dist/test/scheduler-extended.test.d.ts +2 -0
  221. package/dist/test/scheduler-extended.test.d.ts.map +1 -0
  222. package/dist/test/scheduler-extended.test.js +96 -0
  223. package/dist/test/scheduler-extended.test.js.map +1 -0
  224. package/dist/test/scheduler.test.d.ts +2 -0
  225. package/dist/test/scheduler.test.d.ts.map +1 -0
  226. package/dist/test/scheduler.test.js +46 -0
  227. package/dist/test/scheduler.test.js.map +1 -0
  228. package/dist/testing/create-test-wrapper.d.ts +1 -1
  229. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  230. package/dist/testing/create-test-wrapper.js +1 -1
  231. package/dist/testing/create-test-wrapper.js.map +1 -1
  232. package/dist/testing/devtools-utils.d.ts +26 -0
  233. package/dist/testing/devtools-utils.d.ts.map +1 -0
  234. package/dist/testing/devtools-utils.js +140 -0
  235. package/dist/testing/devtools-utils.js.map +1 -0
  236. package/dist/testing/extend-expect.d.ts.map +1 -1
  237. package/dist/testing/extend-expect.js +63 -1
  238. package/dist/testing/extend-expect.js.map +1 -1
  239. package/dist/testing/render.d.ts +2 -2
  240. package/dist/testing/render.d.ts.map +1 -1
  241. package/dist/testing/render.js +2 -2
  242. package/dist/testing/render.js.map +1 -1
  243. package/dist/tsconfig.tsbuildinfo +1 -1
  244. package/package.json +21 -7
  245. package/scripts/copy-devtools-ui.mjs +26 -0
  246. package/src/binder.ts +71 -16
  247. package/src/components/AccessExpression.test.tsx +132 -0
  248. package/src/components/AccessExpression.tsx +344 -0
  249. package/src/components/AppendFile.tsx +14 -9
  250. package/src/components/Block.tsx +1 -1
  251. package/src/components/Declaration.tsx +2 -1
  252. package/src/components/Prose.tsx +1 -1
  253. package/src/components/Scope.tsx +6 -1
  254. package/src/components/SourceDirectory.tsx +1 -2
  255. package/src/components/TemplateFile.tsx +18 -9
  256. package/src/components/index.tsx +1 -0
  257. package/src/content-slot.tsx +7 -7
  258. package/src/context.ts +17 -6
  259. package/src/{debug.ts → debug/cli.ts} +112 -127
  260. package/src/debug/diagnostics.test.tsx +55 -0
  261. package/src/debug/effects.test.tsx +89 -0
  262. package/src/debug/effects.ts +317 -0
  263. package/src/debug/files.test.tsx +96 -0
  264. package/src/debug/files.ts +40 -0
  265. package/src/debug/index.ts +128 -0
  266. package/src/debug/render.test.tsx +379 -0
  267. package/src/debug/render.ts +639 -0
  268. package/src/debug/serialize.ts +85 -0
  269. package/src/debug/symbols.test.tsx +106 -0
  270. package/src/debug/symbols.ts +239 -0
  271. package/src/debug/trace.ts +312 -0
  272. package/src/devtools/devtools-protocol.ts +312 -0
  273. package/src/devtools/devtools-server.browser.ts +71 -0
  274. package/src/devtools/devtools-server.ts +290 -0
  275. package/src/devtools/devtools-transport.ts +154 -0
  276. package/src/devtools-entry.browser.ts +52 -0
  277. package/src/devtools-entry.ts +54 -0
  278. package/src/diagnostics.ts +141 -0
  279. package/src/index.ts +2 -7
  280. package/src/print-hook.ts +22 -0
  281. package/src/reactive-union-set.ts +85 -44
  282. package/src/reactivity.ts +301 -59
  283. package/src/render-stack.ts +294 -0
  284. package/src/render.ts +470 -216
  285. package/src/resource.ts +28 -19
  286. package/src/runtime/component.ts +11 -0
  287. package/src/scheduler.ts +80 -4
  288. package/src/symbols/basic-symbol.ts +6 -1
  289. package/src/symbols/decl.ts +5 -1
  290. package/src/symbols/output-scope.ts +21 -13
  291. package/src/symbols/output-symbol.ts +34 -14
  292. package/src/symbols/symbol-flow.ts +76 -39
  293. package/src/symbols/symbol-slot.test.tsx +41 -0
  294. package/src/symbols/symbol-slot.tsx +47 -20
  295. package/src/symbols/symbol-table.ts +6 -10
  296. package/src/trace.ts +1 -0
  297. package/src/tracer.ts +13 -242
  298. package/src/utils.tsx +24 -17
  299. package/temp/api.json +4187 -1603
  300. package/test/components/append-file.test.tsx +36 -29
  301. package/test/components/template-file.test.tsx +11 -11
  302. package/test/lazy-isempty.test.tsx +106 -0
  303. package/test/reactive-union-set-disposers.test.tsx +112 -0
  304. package/test/reactivity/shallow-reactive.test.tsx +56 -0
  305. package/test/rendering/basic.test.tsx +4 -0
  306. package/test/rendering/print-render-stack.test.tsx +244 -0
  307. package/test/scheduler-extended.test.tsx +122 -0
  308. package/test/scheduler.test.tsx +50 -0
  309. package/testing/create-test-wrapper.tsx +1 -1
  310. package/testing/devtools-utils.ts +203 -0
  311. package/testing/extend-expect.ts +89 -0
  312. package/testing/render.ts +2 -2
  313. package/testing/vitest.d.ts +9 -0
  314. package/dist/src/debug.d.ts +0 -15
  315. package/dist/src/debug.d.ts.map +0 -1
  316. package/dist/src/debug.js.map +0 -1
package/src/render.ts CHANGED
@@ -1,9 +1,27 @@
1
- import { isRef, ref } from "@vue/reactivity";
1
+ import { isRef } from "@vue/reactivity";
2
2
  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
+ import {
7
+ debug,
8
+ getRenderNodeId,
9
+ isDevtoolsEnabled,
10
+ type RenderTreeNodeInfo,
11
+ } from "./debug/index.js";
12
+ import { broadcastDevtoolsMessage } from "./devtools/devtools-server.js";
13
+ import {
14
+ attachDiagnosticsCollector,
15
+ DiagnosticsCollector,
16
+ emitDiagnostic,
17
+ reportDiagnostics,
18
+ } from "./diagnostics.js";
19
+ import {
20
+ isPrintHook,
21
+ printHookTag,
22
+ type PrintHook,
23
+ type RenderedTextTree,
24
+ } from "./print-hook.js";
7
25
  import {
8
26
  Context,
9
27
  CustomContext,
@@ -11,22 +29,111 @@ import {
11
29
  getContext,
12
30
  getElementCache,
13
31
  isCustomContext,
32
+ onCleanup,
33
+ ref,
14
34
  root,
15
35
  untrack,
16
36
  } from "./reactivity.js";
17
37
  import { isRefkeyable, toRefkey } from "./refkey.js";
38
+ import {
39
+ getRenderStackSnapshot,
40
+ popStack,
41
+ printRenderStack,
42
+ pushStack,
43
+ } from "./render-stack.js";
18
44
  import {
19
45
  Child,
20
46
  Children,
21
- Component,
22
47
  isComponentCreator,
23
48
  isRenderableObject,
24
- Props,
25
49
  RENDERABLE,
26
50
  } from "./runtime/component.js";
27
51
  import { IntrinsicElement, isIntrinsicElement } from "./runtime/intrinsic.js";
28
- import { flushJobs, flushJobsAsync } from "./scheduler.js";
29
- import { trace, TracePhase } from "./tracer.js";
52
+ import { flushJobs, flushJobsAsync, waitForSignal } from "./scheduler.js";
53
+
54
+ const notifiedErrors = new WeakSet<object>();
55
+ let lastRenderError: {
56
+ error: { name: string; message: string; stack?: string };
57
+ componentStack: Array<{
58
+ name: string;
59
+ props?: Record<string, unknown> | undefined;
60
+ propsSerialized?: string;
61
+ renderNodeId?: number;
62
+ source?: RenderTreeNodeInfo["source"];
63
+ }>;
64
+ } | null = null;
65
+
66
+ function normalizeRenderError(error: unknown): {
67
+ name: string;
68
+ message: string;
69
+ stack?: string;
70
+ } {
71
+ if (error instanceof Error) {
72
+ return {
73
+ name: error.name || error.constructor?.name || "Error",
74
+ message: error.message || "",
75
+ stack: error.stack,
76
+ };
77
+ }
78
+ if (error && typeof error === "object") {
79
+ const anyError = error as {
80
+ name?: string;
81
+ message?: string;
82
+ stack?: string;
83
+ };
84
+ return {
85
+ name: anyError.name || "Error",
86
+ message: anyError.message || String(error),
87
+ stack: anyError.stack,
88
+ };
89
+ }
90
+ return {
91
+ name: "Error",
92
+ message: String(error),
93
+ };
94
+ }
95
+
96
+ function notifyRenderError(error: unknown) {
97
+ if (error && typeof error === "object") {
98
+ if (notifiedErrors.has(error)) return;
99
+ notifiedErrors.add(error);
100
+ }
101
+ if (lastRenderError) return;
102
+
103
+ const { name, message, stack } = normalizeRenderError(error);
104
+ const componentStack = getRenderStackSnapshot().map((entry) => {
105
+ const renderNode = entry.context?.meta?.renderNode as
106
+ | RenderedTextTree
107
+ | undefined;
108
+ const renderNodeId = renderNode ? getRenderNodeId(renderNode) : undefined;
109
+ return {
110
+ name: entry.displayName,
111
+ props: entry.props as Record<string, unknown> | undefined,
112
+ renderNodeId,
113
+ source: entry.source,
114
+ };
115
+ });
116
+
117
+ // Output to console
118
+ printRenderStack(error);
119
+
120
+ // Send to devtools if enabled
121
+ debug.render.error({ name, message, stack }, componentStack);
122
+
123
+ // Store for diagnostics
124
+ lastRenderError = { error: { name, message, stack }, componentStack };
125
+ const lastEntry = componentStack.at(-1);
126
+ emitDiagnostic({
127
+ severity: "error",
128
+ message: `${name}: ${message}`,
129
+ source: lastEntry?.source,
130
+ });
131
+ }
132
+
133
+ function reportLastRenderError() {
134
+ // Error already reported in notifyRenderError via debug.renderError
135
+ lastRenderError = null;
136
+ }
30
137
 
31
138
  const {
32
139
  builders: {
@@ -152,23 +259,36 @@ export interface ContentOutputFile extends OutputFileBase {
152
259
  export type OutputFile = ContentOutputFile | CopyOutputFile;
153
260
 
154
261
  const nodesToContext = new WeakMap<RenderedTextTree, Context>();
262
+ const diagnosticsByTree = new WeakMap<RenderedTextTree, DiagnosticsCollector>();
155
263
 
156
264
  export function getContextForRenderNode(node: RenderedTextTree) {
157
265
  return nodesToContext.get(node);
158
266
  }
159
267
 
160
- export const printHookTag = Symbol();
268
+ export function getDiagnosticsForTree(tree: RenderedTextTree) {
269
+ return diagnosticsByTree.get(tree)?.getDiagnostics() ?? [];
270
+ }
161
271
 
162
- export interface PrintHook {
163
- [printHookTag]: true;
164
- transform?(tree: RenderedTextTree): RenderedTextTree;
165
- print?(
166
- tree: RenderedTextTree,
167
- print: (subtree: RenderedTextTree) => Doc,
168
- ): Doc;
169
- subtree: RenderedTextTree;
272
+ function reportDiagnosticsForTree(tree: RenderedTextTree) {
273
+ const diagnostics = diagnosticsByTree.get(tree);
274
+ if (!diagnostics) return;
275
+ const entries = diagnostics.getDiagnostics();
276
+ if (entries.length === 0) return;
277
+ reportDiagnostics(diagnostics);
278
+ void broadcastDevtoolsMessage({
279
+ type: "diagnostics:report",
280
+ diagnostics: entries,
281
+ });
170
282
  }
171
283
 
284
+ // Re-export from print-hook.ts to maintain backwards compatibility
285
+ export {
286
+ isPrintHook,
287
+ printHookTag,
288
+ type PrintHook,
289
+ type RenderedTextTree,
290
+ } from "./print-hook.js";
291
+
172
292
  export function createRenderTreeHook(
173
293
  subtree: RenderedTextTree,
174
294
  hooks: Omit<PrintHook, typeof printHookTag | "subtree">,
@@ -180,12 +300,6 @@ export function createRenderTreeHook(
180
300
  };
181
301
  }
182
302
 
183
- export function isPrintHook(type: unknown): type is PrintHook {
184
- return typeof type === "object" && type !== null && printHookTag in type;
185
- }
186
-
187
- export type RenderedTextTree = (string | RenderedTextTree | PrintHook)[];
188
-
189
303
  /**
190
304
  * Render a component tree to source directories and files. Will ensure that
191
305
  * all non-async scheduled jobs are completed before returning. If async jobs
@@ -198,7 +312,14 @@ export function render(
198
312
  ): OutputDirectory {
199
313
  const tree = renderTree(children);
200
314
  flushJobs();
201
- return sourceFilesForTree(tree, options);
315
+ const output = sourceFilesForTree(tree, options);
316
+ reportDiagnosticsForTree(tree);
317
+ reportLastRenderError();
318
+ debug.render.complete();
319
+ if (isDevtoolsEnabled()) {
320
+ void waitForSignal();
321
+ }
322
+ return output;
202
323
  }
203
324
 
204
325
  /**
@@ -209,23 +330,16 @@ export async function renderAsync(
209
330
  children: Children,
210
331
  options?: PrintTreeOptions,
211
332
  ): Promise<OutputDirectory> {
333
+ await debug.prepare();
212
334
  const tree = renderTree(children);
213
- return sourceFilesForTreeAsync(tree, options);
214
- }
215
-
216
- /**
217
- * Convert a rendered text tree to source directories and files. Will ensure that
218
- * all scheduled jobs are completed, including async ones.
219
- */
220
- export async function sourceFilesForTreeAsync(
221
- tree: RenderedTextTree,
222
- options?: PrintTreeOptions,
223
- ) {
224
- // if we await here, we ensure all reactive updates are flushed.
225
- // sourceFilesForTree will flush again, but won't find anything, because tree
226
- // printing won't schedule anything.
335
+ // Ensure all reactive updates are flushed before printing.
227
336
  await flushJobsAsync();
228
- return sourceFilesForTree(tree, options);
337
+ const output = sourceFilesForTree(tree, options);
338
+ reportDiagnosticsForTree(tree);
339
+ reportLastRenderError();
340
+ debug.render.complete();
341
+
342
+ return output;
229
343
  }
230
344
 
231
345
  /**
@@ -241,9 +355,12 @@ export function sourceFilesForTree(
241
355
  collectSourceFiles(undefined, tree);
242
356
 
243
357
  if (!rootDirectory) {
244
- throw new Error(
245
- "No root directory found. Make sure you are using the Output component.",
246
- );
358
+ emitDiagnostic({
359
+ severity: "error",
360
+ message:
361
+ "No root directory found. Make sure you are using the output component.",
362
+ });
363
+ return { kind: "directory", path: "", contents: [] };
247
364
  }
248
365
 
249
366
  return rootDirectory;
@@ -327,25 +444,35 @@ export function sourceFilesForTree(
327
444
  }
328
445
  export function renderTree(children: Children) {
329
446
  const rootElem: RenderedTextTree = [];
447
+ const diagnostics = new DiagnosticsCollector();
448
+ lastRenderError = null;
449
+ debug.effect.reset();
450
+ debug.symbols.reset();
451
+ debug.files.reset();
452
+ debug.render.initialize(rootElem);
330
453
  try {
331
454
  root(() => {
455
+ attachDiagnosticsCollector(diagnostics);
332
456
  renderWorker(rootElem, children);
333
457
  });
334
458
  } catch (e) {
335
- printRenderStack();
459
+ notifyRenderError(e);
460
+ reportLastRenderError();
336
461
  throw e;
337
462
  }
338
463
 
464
+ diagnosticsByTree.set(rootElem, diagnostics);
465
+
339
466
  return rootElem;
340
467
  }
341
468
 
342
469
  function renderWorker(node: RenderedTextTree, children: Children) {
470
+ if (lastRenderError) return;
343
471
  if (!getContext()) {
344
472
  throw new Error(
345
473
  "Cannot render without a context. Make sure you are using the Output component.",
346
474
  );
347
475
  }
348
- trace(TracePhase.render.worker, () => dumpChildren(children));
349
476
 
350
477
  if (Array.isArray(node)) {
351
478
  nodesToContext.set(node, getContext()!);
@@ -354,6 +481,7 @@ function renderWorker(node: RenderedTextTree, children: Children) {
354
481
  if (Array.isArray(children)) {
355
482
  for (const child of (children as any).flat(Infinity)) {
356
483
  appendChild(node, child);
484
+ if (lastRenderError) break;
357
485
  }
358
486
  } else {
359
487
  appendChild(node, children);
@@ -370,11 +498,12 @@ export function notifyContentState() {
370
498
  const startContext = getContext()!;
371
499
 
372
500
  if (startContext.childrenWithContent === 0) {
373
- if (startContext.isEmpty!.value === true) {
501
+ if (startContext._lastEmpty) {
374
502
  // it was already empty, no work to do.
375
503
  return;
376
504
  }
377
505
 
506
+ startContext._lastEmpty = true;
378
507
  if (startContext.isEmpty) {
379
508
  startContext.isEmpty.value = true;
380
509
  }
@@ -386,18 +515,24 @@ export function notifyContentState() {
386
515
  break;
387
516
  }
388
517
  current.childrenWithContent--;
518
+ if (current.childrenWithContent > 0) {
519
+ // This isn't the last content so we have no work to do
520
+ break;
521
+ }
522
+ current._lastEmpty = true;
389
523
  if (current.isEmpty) {
390
524
  current.isEmpty.value = true;
391
525
  }
392
526
  current = current.owner;
393
527
  }
394
528
  } else {
395
- if (startContext.isEmpty!.value === false) {
529
+ if (!startContext._lastEmpty) {
396
530
  // it was already non-empty, no work to do.
397
531
  return;
398
532
  }
399
533
 
400
- if (startContext.isEmpty && startContext.isEmpty.value) {
534
+ startContext._lastEmpty = false;
535
+ if (startContext.isEmpty) {
401
536
  startContext.isEmpty.value = false;
402
537
  }
403
538
 
@@ -410,7 +545,8 @@ export function notifyContentState() {
410
545
  break;
411
546
  }
412
547
 
413
- if (current.isEmpty && current.isEmpty.value) {
548
+ current._lastEmpty = false;
549
+ if (current.isEmpty) {
414
550
  current.isEmpty.value = false;
415
551
  }
416
552
 
@@ -421,95 +557,120 @@ export function notifyContentState() {
421
557
  }
422
558
 
423
559
  function appendChild(node: RenderedTextTree, rawChild: Child) {
424
- trace(TracePhase.render.appendChild, () => debugPrintChild(rawChild));
560
+ if (lastRenderError) return;
425
561
  const child = normalizeChild(rawChild);
426
562
 
427
563
  if (typeof child === "string") {
428
564
  if (child !== "") {
429
565
  contentAdded();
566
+ debug.render.appendTextNode(node, node.length, child);
430
567
  }
431
568
  node.push(child);
432
569
  } else {
433
570
  const cache = getElementCache();
434
571
  if (cache.has(child as any)) {
435
- trace(
436
- TracePhase.render.appendChild,
437
- () => "Cached: " + debugPrintChild(child),
438
- );
439
- node.push(cache.get(child as any)!);
572
+ const cachedNode = cache.get(child as any)!;
573
+ // recordSubtreeAdded detects cached nodes automatically and re-adds their children
574
+ if (isCustomContext(child)) {
575
+ debug.render.appendCustomContext(node, cachedNode);
576
+ } else {
577
+ debug.render.appendFragmentChild(node, cachedNode);
578
+ }
579
+ node.push(cachedNode);
440
580
  return;
441
581
  }
442
582
  if (isCustomContext(child)) {
443
- trace(
444
- TracePhase.render.appendChild,
445
- () => "CustomContext: " + debugPrintChild(child),
446
- );
583
+ const newNode: RenderedTextTree = [];
584
+ debug.render.appendCustomContext(node, newNode);
447
585
  child.useCustomContext((children) => {
448
- const newNode: RenderedTextTree = [];
449
586
  renderWorker(newNode, children);
450
587
  node.push(newNode);
451
588
  cache.set(child, newNode);
452
589
  notifyContentState();
590
+ notifyFileUpdateForNode(node);
453
591
  });
454
592
  } else if (isIntrinsicElement(child)) {
455
- trace(
456
- TracePhase.render.appendChild,
457
- () => "IntrinsicElement: " + debugPrintChild(child),
458
- );
459
593
  // don't need a new context here because intrinsics are never reactive
594
+ const intrinsic = child as IntrinsicElement;
460
595
  const newNode: RenderedTextTree = [];
461
596
 
462
597
  function formatHookWithChildren(command: (doc: Doc) => Doc) {
463
- node.push(
464
- createRenderTreeHook(newNode, {
465
- print(tree, print) {
466
- return command(print(tree));
467
- },
468
- }),
598
+ const hook = createRenderTreeHook(newNode, {
599
+ print(tree, print) {
600
+ return command(print(tree));
601
+ },
602
+ });
603
+ debug.render.appendPrintHook(
604
+ node,
605
+ node.length,
606
+ hook,
607
+ intrinsic.name,
608
+ newNode,
469
609
  );
610
+ node.push(hook);
470
611
  renderWorker(newNode, (child as any).props.children);
612
+ notifyFileUpdateForNode(node);
471
613
  }
472
614
 
473
615
  function formatHook(command: Doc) {
474
- return node.push(
475
- createRenderTreeHook(newNode, {
476
- print() {
477
- return command;
478
- },
479
- }),
480
- );
616
+ const hook = createRenderTreeHook(newNode, {
617
+ print() {
618
+ return command;
619
+ },
620
+ });
621
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name);
622
+ node.push(hook);
623
+ return hook;
481
624
  }
482
625
 
483
626
  switch (child.name) {
484
627
  case "indent":
485
628
  return formatHookWithChildren(indent);
486
629
  case "indentIfBreak":
487
- node.push(
488
- createRenderTreeHook(newNode, {
630
+ {
631
+ const hook = createRenderTreeHook(newNode, {
489
632
  print(tree, print) {
490
633
  return indentIfBreak(print(tree), {
491
634
  groupId: child.props.groupId,
492
635
  negate: child.props.negate,
493
636
  });
494
637
  },
495
- }),
496
- );
638
+ });
639
+ debug.render.appendPrintHook(
640
+ node,
641
+ node.length,
642
+ hook,
643
+ intrinsic.name,
644
+ newNode,
645
+ );
646
+ node.push(hook);
647
+ }
497
648
  renderWorker(newNode, child.props.children);
649
+ notifyFileUpdateForNode(node);
498
650
  return;
499
651
  case "fill":
500
652
  return formatHookWithChildren(fill as any);
501
653
  case "group":
502
- node.push(
503
- createRenderTreeHook(newNode, {
654
+ {
655
+ const hook = createRenderTreeHook(newNode, {
504
656
  print(tree, print) {
505
657
  return group(print(tree), {
506
658
  id: child.props.id,
507
659
  shouldBreak: child.props.shouldBreak,
508
660
  });
509
661
  },
510
- }),
511
- );
662
+ });
663
+ debug.render.appendPrintHook(
664
+ node,
665
+ node.length,
666
+ hook,
667
+ intrinsic.name,
668
+ newNode,
669
+ );
670
+ node.push(hook);
671
+ }
512
672
  renderWorker(newNode, child.props.children);
673
+ notifyFileUpdateForNode(node);
513
674
  return;
514
675
  case "line":
515
676
  case "br":
@@ -524,17 +685,26 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
524
685
  case "lbr":
525
686
  return formatHook(literalline);
526
687
  case "align":
527
- node.push(
528
- createRenderTreeHook(newNode, {
688
+ {
689
+ const hook = createRenderTreeHook(newNode, {
529
690
  print(tree, print) {
530
691
  return align(
531
692
  (child.props as any).width ?? (child.props as any).string!,
532
693
  print(tree),
533
694
  );
534
695
  },
535
- }),
536
- );
696
+ });
697
+ debug.render.appendPrintHook(
698
+ node,
699
+ node.length,
700
+ hook,
701
+ intrinsic.name,
702
+ newNode,
703
+ );
704
+ node.push(hook);
705
+ }
537
706
  renderWorker(newNode, (child as any).props.children);
707
+ notifyFileUpdateForNode(node);
538
708
  return;
539
709
  case "lineSuffix":
540
710
  return formatHookWithChildren(lineSuffix);
@@ -549,17 +719,33 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
549
719
  case "markAsRoot":
550
720
  return formatHookWithChildren(markAsRoot);
551
721
  case "ifBreak":
552
- node.push(
553
- createRenderTreeHook(newNode, {
722
+ {
723
+ const hook = createRenderTreeHook(newNode, {
554
724
  print(tree, print) {
555
725
  return ifBreak(
556
726
  print((tree as RenderedTextTree[])[0]),
557
727
  print((tree as RenderedTextTree[])[1]),
558
728
  );
559
729
  },
560
- }),
561
- );
730
+ });
731
+ debug.render.appendPrintHook(
732
+ node,
733
+ node.length,
734
+ hook,
735
+ intrinsic.name,
736
+ newNode,
737
+ );
738
+ node.push(hook);
739
+ }
562
740
  newNode.push([], []);
741
+ debug.render.appendFragmentChild(
742
+ newNode,
743
+ newNode[0] as RenderedTextTree,
744
+ );
745
+ debug.render.appendFragmentChild(
746
+ newNode,
747
+ newNode[1] as RenderedTextTree,
748
+ );
563
749
  renderWorker(
564
750
  newNode[0] as RenderedTextTree[],
565
751
  (child as any).props.children,
@@ -568,65 +754,204 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
568
754
  newNode[1] as RenderedTextTree[],
569
755
  (child as any).props.flatContents,
570
756
  );
757
+ notifyFileUpdateForNode(node);
571
758
  return;
572
759
  default:
573
760
  throw new Error("Unknown intrinsic element");
574
761
  }
575
762
  } else if (isComponentCreator(child)) {
763
+ const index = node.length;
764
+ const rerenderToken = isDevtoolsEnabled() ? ref(0) : undefined;
765
+ const breakNext = isDevtoolsEnabled() ? ref(false) : undefined;
576
766
  // todo: remove this effect (only needed for context, not needed for anything else)
577
- effect(() => {
578
- trace(
579
- TracePhase.render.appendChild,
580
- () => "Component: " + debugPrintChild(child),
581
- );
582
- const context = getContext();
583
- context!.childrenWithContent = 0;
584
- context!.isEmpty ??= ref(true);
585
-
586
- if (context) context.componentOwner = child;
587
- const componentRoot: RenderedTextTree = [];
588
- pushStack(child.component, child.props);
589
- renderWorker(componentRoot, untrack(child));
590
- popStack();
591
- node.push(componentRoot);
592
- cache.set(child, componentRoot);
593
- notifyContentState();
594
- trace(
595
- TracePhase.render.appendChild,
596
- () =>
597
- "Component done: " +
598
- debugPrintChild(child) +
599
- ", empty: " +
600
- context!.isEmpty!.value,
601
- );
602
- });
767
+ effect(
768
+ () => {
769
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
770
+ rerenderToken?.value;
771
+ const context = getContext();
772
+ context!.childrenWithContent = 0;
773
+
774
+ if (context) context.componentOwner = child;
775
+ const existing = node[index];
776
+ const componentRoot: RenderedTextTree =
777
+ Array.isArray(existing) ? existing : [];
778
+ context!.meta ??= {};
779
+ context!.meta.renderNode = componentRoot;
780
+ const propsSource = (child.props ?? undefined) as
781
+ | Record<string, unknown>
782
+ | undefined;
783
+ const debugSession = debug.render.beginComponent({
784
+ parent: node,
785
+ index,
786
+ node: componentRoot,
787
+ component: child,
788
+ propsSource,
789
+ source: child.source,
790
+ isExisting: Array.isArray(existing),
791
+ actions: {
792
+ rerender: () => {
793
+ lastRenderError = null;
794
+ if (rerenderToken) rerenderToken.value++;
795
+ },
796
+ rerenderAndBreak: () => {
797
+ lastRenderError = null;
798
+ if (breakNext) breakNext.value = true;
799
+ if (rerenderToken) rerenderToken.value++;
800
+ },
801
+ },
802
+ });
803
+ if (Array.isArray(existing)) {
804
+ componentRoot.length = 0;
805
+ }
806
+
807
+ pushStack(child.component, child.props, child.source);
808
+ let renderFailed = false;
809
+ let childResult: Children | undefined;
810
+ try {
811
+ childResult = untrack(() => {
812
+ const shouldBreak = breakNext?.value;
813
+ if (shouldBreak) {
814
+ breakNext!.value = false;
815
+ // eslint-disable-next-line no-debugger
816
+ debugger;
817
+ }
818
+ return child();
819
+ });
820
+ } catch (error) {
821
+ notifyRenderError(error);
822
+ renderFailed = true;
823
+ throw error;
824
+ }
825
+ try {
826
+ if (context?.meta?.directory) {
827
+ debugSession.recordDirectory(context.meta.directory.path);
828
+ }
829
+ if (context?.meta?.sourceFile) {
830
+ context.meta.renderNode = componentRoot;
831
+ debugSession.recordFile(
832
+ context.meta.sourceFile.path,
833
+ context.meta.sourceFile.filetype,
834
+ );
835
+ context.meta.sourceFileReady = false;
836
+ }
837
+ if (!renderFailed) {
838
+ renderWorker(componentRoot, childResult);
839
+ }
840
+ } finally {
841
+ popStack();
842
+ }
843
+ if (renderFailed) {
844
+ node[index] = componentRoot;
845
+ cache.set(child, componentRoot);
846
+ notifyFileUpdateForNode(node);
847
+ notifyContentState();
848
+ onCleanup(() => debugSession.dispose());
849
+ return;
850
+ }
851
+ if (context?.meta?.sourceFile) {
852
+ context.meta.sourceFileReady = true;
853
+ notifyFileUpdateForNode(componentRoot);
854
+ }
855
+ node[index] = componentRoot;
856
+ cache.set(child, componentRoot);
857
+ notifyContentState();
858
+ onCleanup(() => debugSession.dispose());
859
+ },
860
+ undefined,
861
+ {
862
+ debug: {
863
+ name: `render:${child.component.name || "Anonymous"}`,
864
+ type: "render",
865
+ },
866
+ },
867
+ );
603
868
  } else if (typeof child === "function") {
604
- trace(TracePhase.render.appendChild, () => "Memo: " + child.toString());
605
869
  const index = node.length;
606
- effect(() => {
607
- trace(TracePhase.render.renderEffect, () => "");
608
- let res = child();
609
- while (typeof res === "function" && !isComponentCreator(res)) {
610
- res = res();
611
- }
612
- const context = getContext();
613
- context!.childrenWithContent = 0;
614
- context!.isEmpty ??= ref(true);
615
-
616
- const newNodes: RenderedTextTree = [];
617
- renderWorker(newNodes, res);
618
- node[index] = newNodes;
619
- cache.set(child, newNodes);
620
-
621
- notifyContentState();
622
- return newNodes;
623
- });
870
+ effect(
871
+ () => {
872
+ let res: Child | Children | undefined;
873
+ let renderFailed = false;
874
+ try {
875
+ res = child();
876
+ while (typeof res === "function" && !isComponentCreator(res)) {
877
+ res = res();
878
+ }
879
+ } catch (error) {
880
+ notifyRenderError(error);
881
+ renderFailed = true;
882
+ throw error;
883
+ }
884
+ const context = getContext();
885
+ context!.childrenWithContent = 0;
886
+
887
+ const existing = node[index];
888
+ const memoNode: RenderedTextTree =
889
+ Array.isArray(existing) ? existing : [];
890
+
891
+ debug.render.prepareMemoNode(node, memoNode, Array.isArray(existing));
892
+ if (Array.isArray(existing)) {
893
+ memoNode.length = 0;
894
+ }
895
+
896
+ if (!renderFailed) {
897
+ renderWorker(memoNode, res);
898
+ }
899
+ node[index] = memoNode;
900
+ cache.set(child, memoNode);
901
+ notifyFileUpdateForNode(node);
902
+ notifyContentState();
903
+ return memoNode;
904
+ },
905
+ undefined,
906
+ {
907
+ debug: {
908
+ name: `render:memo:${child.name || "anonymous"}`,
909
+ type: "render",
910
+ },
911
+ },
912
+ );
624
913
  } else {
625
914
  throw new Error("Unexpected child type");
626
915
  }
627
916
  }
628
917
  }
629
918
 
919
+ function findSourceFileContext(node: RenderedTextTree) {
920
+ let context: Context | null | undefined =
921
+ getContextForRenderNode(node) ?? null;
922
+ while (context) {
923
+ if (context.meta?.sourceFile) return context;
924
+ context = context.owner;
925
+ }
926
+ return undefined;
927
+ }
928
+
929
+ function notifyFileUpdateForNode(node: RenderedTextTree) {
930
+ // Only do the expensive printTree when devtools are actually enabled
931
+ if (!isDevtoolsEnabled()) return;
932
+ const context = findSourceFileContext(node);
933
+ if (!context?.meta?.sourceFile) return;
934
+ if (context.meta.sourceFileReady === false) return;
935
+ const sourceFile = context.meta.sourceFile;
936
+ const renderNode: RenderedTextTree =
937
+ (context.meta.renderNode as RenderedTextTree | undefined) ?? node;
938
+ // Pass noFlush here since it flushes jobs and can re-enter rendering
939
+ // during effect setup, triggering premature cleanup.
940
+ const contents = printTree(renderNode, {
941
+ printWidth: context.meta?.printOptions?.printWidth,
942
+ tabWidth: context.meta?.printOptions?.tabWidth,
943
+ useTabs: context.meta?.printOptions?.useTabs,
944
+ insertFinalNewLine: context.meta?.printOptions?.insertFinalNewLine ?? true,
945
+ noFlush: true,
946
+ });
947
+
948
+ debug.files.updated({
949
+ path: sourceFile.path,
950
+ filetype: sourceFile.filetype,
951
+ contents,
952
+ });
953
+ }
954
+
630
955
  type NormalizedChildren = NormalizedChild | NormalizedChildren[];
631
956
  type NormalizedChild =
632
957
  | string
@@ -669,31 +994,6 @@ function normalizeChild(child: Child): NormalizedChildren {
669
994
  }
670
995
  }
671
996
 
672
- function dumpChildren(children: Children): string {
673
- if (Array.isArray(children)) {
674
- return `[ ${children.map(debugPrintChild).join(", ")} ]`;
675
- }
676
- return debugPrintChild(children);
677
- }
678
-
679
- function debugPrintChild(child: Children): string {
680
- if (isComponentCreator(child)) {
681
- return "<" + child.component.name + ">";
682
- } else if (typeof child === "function") {
683
- return "$memo";
684
- } else if (isRef(child)) {
685
- return "$ref";
686
- } else if (isIntrinsicElement(child)) {
687
- return `<${child.name}>`;
688
- } else if (isRenderableObject(child)) {
689
- return `CustomChildElement(${JSON.stringify(child)})`;
690
- } else if (isRefkeyable(child)) {
691
- return `refkey`;
692
- } else {
693
- return JSON.stringify(child);
694
- }
695
- }
696
-
697
997
  export interface PrintTreeOptions {
698
998
  /**
699
999
  * The number of characters the printer will wrap on. Defaults to 100
@@ -716,6 +1016,12 @@ export interface PrintTreeOptions {
716
1016
  * @default true
717
1017
  */
718
1018
  insertFinalNewLine?: boolean;
1019
+
1020
+ /**
1021
+ * Skip flushing scheduled jobs before printing.
1022
+ * @default false
1023
+ */
1024
+ noFlush?: boolean;
719
1025
  }
720
1026
 
721
1027
  const defaultPrintTreeOptions: PrintTreeOptions = {
@@ -735,8 +1041,10 @@ export function printTree(tree: RenderedTextTree, options?: PrintTreeOptions) {
735
1041
  ),
736
1042
  };
737
1043
 
738
- // make sure queue is empty
739
- flushJobs();
1044
+ if (!options.noFlush) {
1045
+ // make sure queue is empty
1046
+ flushJobs();
1047
+ }
740
1048
 
741
1049
  const d = printTreeWorker(tree);
742
1050
  const result = doc.printer.printDocToString(
@@ -768,57 +1076,3 @@ function printTreeWorker(tree: RenderedTextTree): Doc {
768
1076
 
769
1077
  return doc;
770
1078
  }
771
- // debugging utilities
772
- const renderStack: {
773
- component: Component<any>;
774
- props: Props;
775
- }[] = [];
776
-
777
- export function pushStack(component: Component<any>, props: Props) {
778
- if (!shouldDebug()) return;
779
- renderStack.push({ component, props });
780
- }
781
-
782
- export function popStack() {
783
- if (!shouldDebug()) return;
784
- renderStack.pop();
785
- }
786
-
787
- export function printRenderStack() {
788
- if (!shouldDebug()) return;
789
-
790
- // eslint-disable-next-line no-console
791
- console.error("Error rendering:");
792
- for (let i = renderStack.length - 1; i >= 0; i--) {
793
- const { component, props } = renderStack[i];
794
- // eslint-disable-next-line no-console
795
- console.error(` at ${component.name}(${inspectProps(props)})`);
796
- }
797
- }
798
-
799
- function inspectProps(props: Props) {
800
- return JSON.stringify(
801
- Object.fromEntries(
802
- Object.entries(props).map(([key, value]) => {
803
- let safeValue;
804
- switch (typeof value) {
805
- case "string":
806
- case "number":
807
- case "boolean":
808
- safeValue = value;
809
- break;
810
- case "undefined":
811
- safeValue = "undefined";
812
- break;
813
- case "object":
814
- safeValue = value ? "{...}" : null;
815
- break;
816
- case "function":
817
- safeValue = "function";
818
- break;
819
- }
820
- return [key, safeValue];
821
- }),
822
- ),
823
- );
824
- }