@alloy-js/core 0.23.0-dev.1 → 0.23.0-dev.8

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 (263) 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/AppendFile.d.ts.map +1 -1
  8. package/dist/src/components/AppendFile.js +14 -3
  9. package/dist/src/components/AppendFile.js.map +1 -1
  10. package/dist/src/components/Block.js +1 -1
  11. package/dist/src/components/Block.js.map +1 -1
  12. package/dist/src/components/Declaration.d.ts.map +1 -1
  13. package/dist/src/components/Declaration.js +2 -1
  14. package/dist/src/components/Declaration.js.map +1 -1
  15. package/dist/src/components/Scope.d.ts.map +1 -1
  16. package/dist/src/components/Scope.js +4 -1
  17. package/dist/src/components/Scope.js.map +1 -1
  18. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  19. package/dist/src/components/TemplateFile.js +18 -3
  20. package/dist/src/components/TemplateFile.js.map +1 -1
  21. package/dist/src/content-slot.d.ts.map +1 -1
  22. package/dist/src/content-slot.js +6 -5
  23. package/dist/src/content-slot.js.map +1 -1
  24. package/dist/src/context.d.ts.map +1 -1
  25. package/dist/src/context.js +8 -1
  26. package/dist/src/context.js.map +1 -1
  27. package/dist/src/debug/cli.d.ts +6 -0
  28. package/dist/src/debug/cli.d.ts.map +1 -0
  29. package/dist/src/{debug.js → debug/cli.js} +79 -82
  30. package/dist/src/debug/cli.js.map +1 -0
  31. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  32. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  33. package/dist/src/debug/diagnostics.test.js +45 -0
  34. package/dist/src/debug/diagnostics.test.js.map +1 -0
  35. package/dist/src/debug/effects.d.ts +69 -0
  36. package/dist/src/debug/effects.d.ts.map +1 -0
  37. package/dist/src/debug/effects.js +228 -0
  38. package/dist/src/debug/effects.js.map +1 -0
  39. package/dist/src/debug/effects.test.d.ts +2 -0
  40. package/dist/src/debug/effects.test.d.ts.map +1 -0
  41. package/dist/src/debug/effects.test.js +86 -0
  42. package/dist/src/debug/effects.test.js.map +1 -0
  43. package/dist/src/debug/files.d.ts +14 -0
  44. package/dist/src/debug/files.d.ts.map +1 -0
  45. package/dist/src/debug/files.js +40 -0
  46. package/dist/src/debug/files.js.map +1 -0
  47. package/dist/src/debug/files.test.d.ts +2 -0
  48. package/dist/src/debug/files.test.d.ts.map +1 -0
  49. package/dist/src/debug/files.test.js +89 -0
  50. package/dist/src/debug/files.test.js.map +1 -0
  51. package/dist/src/debug/index.d.ts +60 -0
  52. package/dist/src/debug/index.d.ts.map +1 -0
  53. package/dist/src/debug/index.js +68 -0
  54. package/dist/src/debug/index.js.map +1 -0
  55. package/dist/src/debug/render.d.ts +57 -0
  56. package/dist/src/debug/render.d.ts.map +1 -0
  57. package/dist/src/debug/render.js +519 -0
  58. package/dist/src/debug/render.js.map +1 -0
  59. package/dist/src/debug/render.test.d.ts +2 -0
  60. package/dist/src/debug/render.test.d.ts.map +1 -0
  61. package/dist/src/debug/render.test.js +328 -0
  62. package/dist/src/debug/render.test.js.map +1 -0
  63. package/dist/src/debug/serialize.d.ts +9 -0
  64. package/dist/src/debug/serialize.d.ts.map +1 -0
  65. package/dist/src/debug/serialize.js +70 -0
  66. package/dist/src/debug/serialize.js.map +1 -0
  67. package/dist/src/debug/symbols.d.ts +9 -0
  68. package/dist/src/debug/symbols.d.ts.map +1 -0
  69. package/dist/src/debug/symbols.js +164 -0
  70. package/dist/src/debug/symbols.js.map +1 -0
  71. package/dist/src/debug/symbols.test.d.ts +2 -0
  72. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  73. package/dist/src/debug/symbols.test.js +104 -0
  74. package/dist/src/debug/symbols.test.js.map +1 -0
  75. package/dist/src/debug/trace.d.ts +342 -0
  76. package/dist/src/debug/trace.d.ts.map +1 -0
  77. package/dist/src/debug/trace.js +443 -0
  78. package/dist/src/debug/trace.js.map +1 -0
  79. package/dist/src/devtools/devtools-protocol.d.ts +232 -0
  80. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  81. package/dist/src/devtools/devtools-protocol.js +2 -0
  82. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  83. package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
  84. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  85. package/dist/src/devtools/devtools-server.browser.js +36 -0
  86. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  87. package/dist/src/devtools/devtools-server.d.ts +72 -0
  88. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  89. package/dist/src/devtools/devtools-server.js +256 -0
  90. package/dist/src/devtools/devtools-server.js.map +1 -0
  91. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  92. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  93. package/dist/src/devtools/devtools-transport.js +114 -0
  94. package/dist/src/devtools/devtools-transport.js.map +1 -0
  95. package/dist/src/devtools-entry.browser.d.ts +4 -0
  96. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  97. package/dist/src/devtools-entry.browser.js +2 -0
  98. package/dist/src/devtools-entry.browser.js.map +1 -0
  99. package/dist/src/devtools-entry.d.ts +4 -0
  100. package/dist/src/devtools-entry.d.ts.map +1 -0
  101. package/dist/src/devtools-entry.js +2 -0
  102. package/dist/src/devtools-entry.js.map +1 -0
  103. package/dist/src/diagnostics.d.ts +34 -0
  104. package/dist/src/diagnostics.d.ts.map +1 -0
  105. package/dist/src/diagnostics.js +89 -0
  106. package/dist/src/diagnostics.js.map +1 -0
  107. package/dist/src/index.d.ts +3 -2
  108. package/dist/src/index.d.ts.map +1 -1
  109. package/dist/src/index.js +3 -2
  110. package/dist/src/index.js.map +1 -1
  111. package/dist/src/print-hook.d.ts +14 -0
  112. package/dist/src/print-hook.d.ts.map +1 -0
  113. package/dist/src/print-hook.js +10 -0
  114. package/dist/src/print-hook.js.map +1 -0
  115. package/dist/src/reactive-union-set.d.ts.map +1 -1
  116. package/dist/src/reactive-union-set.js +15 -0
  117. package/dist/src/reactive-union-set.js.map +1 -1
  118. package/dist/src/reactivity.d.ts +17 -3
  119. package/dist/src/reactivity.d.ts.map +1 -1
  120. package/dist/src/reactivity.js +162 -14
  121. package/dist/src/reactivity.js.map +1 -1
  122. package/dist/src/render-stack.d.ts +17 -1
  123. package/dist/src/render-stack.d.ts.map +1 -1
  124. package/dist/src/render-stack.js +57 -1
  125. package/dist/src/render-stack.js.map +1 -1
  126. package/dist/src/render.d.ts +8 -15
  127. package/dist/src/render.d.ts.map +1 -1
  128. package/dist/src/render.js +362 -103
  129. package/dist/src/render.js.map +1 -1
  130. package/dist/src/resource.d.ts.map +1 -1
  131. package/dist/src/resource.js +5 -0
  132. package/dist/src/resource.js.map +1 -1
  133. package/dist/src/scheduler.d.ts +3 -0
  134. package/dist/src/scheduler.d.ts.map +1 -1
  135. package/dist/src/scheduler.js +45 -2
  136. package/dist/src/scheduler.js.map +1 -1
  137. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  138. package/dist/src/symbols/basic-symbol.js +6 -1
  139. package/dist/src/symbols/basic-symbol.js.map +1 -1
  140. package/dist/src/symbols/decl.d.ts.map +1 -1
  141. package/dist/src/symbols/decl.js +5 -1
  142. package/dist/src/symbols/decl.js.map +1 -1
  143. package/dist/src/symbols/output-scope.d.ts +2 -1
  144. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  145. package/dist/src/symbols/output-scope.js +13 -8
  146. package/dist/src/symbols/output-scope.js.map +1 -1
  147. package/dist/src/symbols/output-symbol.d.ts +1 -0
  148. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  149. package/dist/src/symbols/output-symbol.js +23 -6
  150. package/dist/src/symbols/output-symbol.js.map +1 -1
  151. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  152. package/dist/src/symbols/symbol-flow.js +22 -6
  153. package/dist/src/symbols/symbol-flow.js.map +1 -1
  154. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  155. package/dist/src/symbols/symbol-slot.js +15 -0
  156. package/dist/src/symbols/symbol-slot.js.map +1 -1
  157. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  158. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  159. package/dist/src/symbols/symbol-slot.test.js +35 -0
  160. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  161. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  162. package/dist/src/symbols/symbol-table.js +6 -5
  163. package/dist/src/symbols/symbol-table.js.map +1 -1
  164. package/dist/src/trace.d.ts +2 -0
  165. package/dist/src/trace.d.ts.map +1 -0
  166. package/dist/src/trace.js +2 -0
  167. package/dist/src/trace.js.map +1 -0
  168. package/dist/src/tracer.d.ts +2 -228
  169. package/dist/src/tracer.d.ts.map +1 -1
  170. package/dist/src/tracer.js +5 -298
  171. package/dist/src/tracer.js.map +1 -1
  172. package/dist/src/utils.d.ts.map +1 -1
  173. package/dist/src/utils.js +5 -0
  174. package/dist/src/utils.js.map +1 -1
  175. package/dist/test/components/append-file.test.d.ts.map +1 -1
  176. package/dist/test/components/append-file.test.js +18 -10
  177. package/dist/test/components/append-file.test.js.map +1 -1
  178. package/dist/test/components/template-file.test.d.ts.map +1 -1
  179. package/dist/test/components/template-file.test.js +6 -4
  180. package/dist/test/components/template-file.test.js.map +1 -1
  181. package/dist/test/rendering/basic.test.js +3 -0
  182. package/dist/test/rendering/basic.test.js.map +1 -1
  183. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -1
  184. package/dist/test/rendering/print-render-stack.test.js +91 -98
  185. package/dist/test/rendering/print-render-stack.test.js.map +1 -1
  186. package/dist/testing/create-test-wrapper.d.ts +1 -1
  187. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  188. package/dist/testing/create-test-wrapper.js +1 -1
  189. package/dist/testing/create-test-wrapper.js.map +1 -1
  190. package/dist/testing/devtools-utils.d.ts +26 -0
  191. package/dist/testing/devtools-utils.d.ts.map +1 -0
  192. package/dist/testing/devtools-utils.js +140 -0
  193. package/dist/testing/devtools-utils.js.map +1 -0
  194. package/dist/testing/extend-expect.d.ts.map +1 -1
  195. package/dist/testing/extend-expect.js +63 -1
  196. package/dist/testing/extend-expect.js.map +1 -1
  197. package/dist/testing/render.d.ts +2 -2
  198. package/dist/testing/render.d.ts.map +1 -1
  199. package/dist/testing/render.js +2 -2
  200. package/dist/testing/render.js.map +1 -1
  201. package/dist/tsconfig.tsbuildinfo +1 -1
  202. package/package.json +21 -7
  203. package/scripts/copy-devtools-ui.mjs +26 -0
  204. package/src/binder.ts +71 -16
  205. package/src/components/AppendFile.tsx +14 -9
  206. package/src/components/Block.tsx +1 -1
  207. package/src/components/Declaration.tsx +2 -1
  208. package/src/components/Scope.tsx +4 -1
  209. package/src/components/TemplateFile.tsx +18 -9
  210. package/src/content-slot.tsx +6 -6
  211. package/src/context.ts +15 -4
  212. package/src/{debug.ts → debug/cli.ts} +114 -125
  213. package/src/debug/diagnostics.test.tsx +55 -0
  214. package/src/debug/effects.test.tsx +96 -0
  215. package/src/debug/effects.ts +313 -0
  216. package/src/debug/files.test.tsx +96 -0
  217. package/src/debug/files.ts +40 -0
  218. package/src/debug/index.ts +126 -0
  219. package/src/debug/render.test.tsx +379 -0
  220. package/src/debug/render.ts +639 -0
  221. package/src/debug/serialize.ts +85 -0
  222. package/src/debug/symbols.test.tsx +106 -0
  223. package/src/debug/symbols.ts +230 -0
  224. package/src/debug/trace.ts +312 -0
  225. package/src/devtools/devtools-protocol.ts +312 -0
  226. package/src/devtools/devtools-server.browser.ts +71 -0
  227. package/src/devtools/devtools-server.ts +290 -0
  228. package/src/devtools/devtools-transport.ts +154 -0
  229. package/src/devtools-entry.browser.ts +52 -0
  230. package/src/devtools-entry.ts +54 -0
  231. package/src/diagnostics.ts +141 -0
  232. package/src/index.ts +2 -6
  233. package/src/print-hook.ts +22 -0
  234. package/src/reactive-union-set.ts +71 -41
  235. package/src/reactivity.ts +206 -23
  236. package/src/render-stack.ts +68 -1
  237. package/src/render.ts +464 -157
  238. package/src/resource.ts +28 -19
  239. package/src/scheduler.ts +55 -3
  240. package/src/symbols/basic-symbol.ts +6 -1
  241. package/src/symbols/decl.ts +5 -1
  242. package/src/symbols/output-scope.ts +21 -12
  243. package/src/symbols/output-symbol.ts +33 -12
  244. package/src/symbols/symbol-flow.ts +68 -37
  245. package/src/symbols/symbol-slot.test.tsx +41 -0
  246. package/src/symbols/symbol-slot.tsx +47 -20
  247. package/src/symbols/symbol-table.ts +6 -10
  248. package/src/trace.ts +1 -0
  249. package/src/tracer.ts +13 -242
  250. package/src/utils.tsx +22 -13
  251. package/temp/api.json +1675 -162
  252. package/test/components/append-file.test.tsx +36 -29
  253. package/test/components/template-file.test.tsx +11 -11
  254. package/test/rendering/basic.test.tsx +4 -0
  255. package/test/rendering/print-render-stack.test.tsx +52 -43
  256. package/testing/create-test-wrapper.tsx +1 -1
  257. package/testing/devtools-utils.ts +203 -0
  258. package/testing/extend-expect.ts +89 -0
  259. package/testing/render.ts +2 -2
  260. package/testing/vitest.d.ts +9 -0
  261. package/dist/src/debug.d.ts +0 -14
  262. package/dist/src/debug.d.ts.map +0 -1
  263. package/dist/src/debug.js.map +0 -1
@@ -1,15 +1,93 @@
1
- import { isRef, ref } from "@vue/reactivity";
1
+ import { isRef } from "@vue/reactivity";
2
2
  import { 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 { effect, getContext, getElementCache, isCustomContext, root, untrack } from "./reactivity.js";
6
+ import { debug, getRenderNodeId, isDevtoolsEnabled } from "./debug/index.js";
7
+ import { broadcastDevtoolsMessage } from "./devtools/devtools-server.js";
8
+ import { attachDiagnosticsCollector, DiagnosticsCollector, emitDiagnostic, reportDiagnostics } from "./diagnostics.js";
9
+ import { isPrintHook, printHookTag } from "./print-hook.js";
10
+ import { effect, getContext, getElementCache, isCustomContext, onCleanup, ref, root, untrack } from "./reactivity.js";
7
11
  import { isRefkeyable, toRefkey } from "./refkey.js";
8
- import { popStack, printRenderStack, pushStack } from "./render-stack.js";
12
+ import { getRenderStackSnapshot, popStack, printRenderStack, pushStack } from "./render-stack.js";
9
13
  import { isComponentCreator, isRenderableObject, RENDERABLE } from "./runtime/component.js";
10
14
  import { isIntrinsicElement } from "./runtime/intrinsic.js";
11
- import { flushJobs, flushJobsAsync } from "./scheduler.js";
12
- import { trace, TracePhase } from "./tracer.js";
15
+ import { flushJobs, flushJobsAsync, waitForSignal } from "./scheduler.js";
16
+ const notifiedErrors = new WeakSet();
17
+ let lastRenderError = null;
18
+ function normalizeRenderError(error) {
19
+ if (error instanceof Error) {
20
+ return {
21
+ name: error.name || error.constructor?.name || "Error",
22
+ message: error.message || "",
23
+ stack: error.stack
24
+ };
25
+ }
26
+ if (error && typeof error === "object") {
27
+ const anyError = error;
28
+ return {
29
+ name: anyError.name || "Error",
30
+ message: anyError.message || String(error),
31
+ stack: anyError.stack
32
+ };
33
+ }
34
+ return {
35
+ name: "Error",
36
+ message: String(error)
37
+ };
38
+ }
39
+ function notifyRenderError(error) {
40
+ if (error && typeof error === "object") {
41
+ if (notifiedErrors.has(error)) return;
42
+ notifiedErrors.add(error);
43
+ }
44
+ if (lastRenderError) return;
45
+ const {
46
+ name,
47
+ message,
48
+ stack
49
+ } = normalizeRenderError(error);
50
+ const componentStack = getRenderStackSnapshot().map(entry => {
51
+ const renderNode = entry.context?.meta?.renderNode;
52
+ const renderNodeId = renderNode ? getRenderNodeId(renderNode) : undefined;
53
+ return {
54
+ name: entry.displayName,
55
+ props: entry.props,
56
+ renderNodeId,
57
+ source: entry.source
58
+ };
59
+ });
60
+
61
+ // Output to console
62
+ printRenderStack(error);
63
+
64
+ // Send to devtools if enabled
65
+ debug.render.error({
66
+ name,
67
+ message,
68
+ stack
69
+ }, componentStack);
70
+
71
+ // Store for diagnostics
72
+ lastRenderError = {
73
+ error: {
74
+ name,
75
+ message,
76
+ stack
77
+ },
78
+ componentStack
79
+ };
80
+ const lastEntry = componentStack.at(-1);
81
+ emitDiagnostic({
82
+ severity: "error",
83
+ message: `${name}: ${message}`,
84
+ source: lastEntry?.source
85
+ });
86
+ }
87
+ function reportLastRenderError() {
88
+ // Error already reported in notifyRenderError via debug.renderError
89
+ lastRenderError = null;
90
+ }
13
91
  const {
14
92
  builders: {
15
93
  align,
@@ -112,10 +190,27 @@ const {
112
190
  */
113
191
 
114
192
  const nodesToContext = new WeakMap();
193
+ const diagnosticsByTree = new WeakMap();
115
194
  export function getContextForRenderNode(node) {
116
195
  return nodesToContext.get(node);
117
196
  }
118
- export const printHookTag = Symbol();
197
+ export function getDiagnosticsForTree(tree) {
198
+ return diagnosticsByTree.get(tree)?.getDiagnostics() ?? [];
199
+ }
200
+ function reportDiagnosticsForTree(tree) {
201
+ const diagnostics = diagnosticsByTree.get(tree);
202
+ if (!diagnostics) return;
203
+ const entries = diagnostics.getDiagnostics();
204
+ if (entries.length === 0) return;
205
+ reportDiagnostics(diagnostics);
206
+ void broadcastDevtoolsMessage({
207
+ type: "diagnostics:report",
208
+ diagnostics: entries
209
+ });
210
+ }
211
+
212
+ // Re-export from print-hook.ts to maintain backwards compatibility
213
+ export { isPrintHook, printHookTag } from "./print-hook.js";
119
214
  export function createRenderTreeHook(subtree, hooks) {
120
215
  return {
121
216
  [printHookTag]: true,
@@ -123,9 +218,7 @@ export function createRenderTreeHook(subtree, hooks) {
123
218
  ...hooks
124
219
  };
125
220
  }
126
- export function isPrintHook(type) {
127
- return typeof type === "object" && type !== null && printHookTag in type;
128
- }
221
+
129
222
  /**
130
223
  * Render a component tree to source directories and files. Will ensure that
131
224
  * all non-async scheduled jobs are completed before returning. If async jobs
@@ -135,7 +228,14 @@ export function isPrintHook(type) {
135
228
  export function render(children, options) {
136
229
  const tree = renderTree(children);
137
230
  flushJobs();
138
- return sourceFilesForTree(tree, options);
231
+ const output = sourceFilesForTree(tree, options);
232
+ reportDiagnosticsForTree(tree);
233
+ reportLastRenderError();
234
+ debug.render.complete();
235
+ if (isDevtoolsEnabled()) {
236
+ void waitForSignal();
237
+ }
238
+ return output;
139
239
  }
140
240
 
141
241
  /**
@@ -143,20 +243,15 @@ export function render(children, options) {
143
243
  * scheduled jobs are completed before returning.
144
244
  */
145
245
  export async function renderAsync(children, options) {
246
+ await debug.prepare();
146
247
  const tree = renderTree(children);
147
- return sourceFilesForTreeAsync(tree, options);
148
- }
149
-
150
- /**
151
- * Convert a rendered text tree to source directories and files. Will ensure that
152
- * all scheduled jobs are completed, including async ones.
153
- */
154
- export async function sourceFilesForTreeAsync(tree, options) {
155
- // if we await here, we ensure all reactive updates are flushed.
156
- // sourceFilesForTree will flush again, but won't find anything, because tree
157
- // printing won't schedule anything.
248
+ // Ensure all reactive updates are flushed before printing.
158
249
  await flushJobsAsync();
159
- return sourceFilesForTree(tree, options);
250
+ const output = sourceFilesForTree(tree, options);
251
+ reportDiagnosticsForTree(tree);
252
+ reportLastRenderError();
253
+ debug.render.complete();
254
+ return output;
160
255
  }
161
256
 
162
257
  /**
@@ -167,7 +262,15 @@ export function sourceFilesForTree(tree, options) {
167
262
  let rootDirectory = undefined;
168
263
  collectSourceFiles(undefined, tree);
169
264
  if (!rootDirectory) {
170
- throw new Error("No root directory found. Make sure you are using the Output component.");
265
+ emitDiagnostic({
266
+ severity: "error",
267
+ message: "No root directory found. Make sure you are using the output component."
268
+ });
269
+ return {
270
+ kind: "directory",
271
+ path: "",
272
+ contents: []
273
+ };
171
274
  }
172
275
  return rootDirectory;
173
276
  function collectSourceFiles(currentDirectory, root) {
@@ -230,27 +333,37 @@ export function sourceFilesForTree(tree, options) {
230
333
  }
231
334
  export function renderTree(children) {
232
335
  const rootElem = [];
336
+ const diagnostics = new DiagnosticsCollector();
337
+ lastRenderError = null;
338
+ debug.effect.reset();
339
+ debug.symbols.reset();
340
+ debug.files.reset();
341
+ debug.render.initialize(rootElem);
233
342
  try {
234
343
  root(() => {
344
+ attachDiagnosticsCollector(diagnostics);
235
345
  renderWorker(rootElem, children);
236
346
  });
237
347
  } catch (e) {
238
- printRenderStack();
348
+ notifyRenderError(e);
349
+ reportLastRenderError();
239
350
  throw e;
240
351
  }
352
+ diagnosticsByTree.set(rootElem, diagnostics);
241
353
  return rootElem;
242
354
  }
243
355
  function renderWorker(node, children) {
356
+ if (lastRenderError) return;
244
357
  if (!getContext()) {
245
358
  throw new Error("Cannot render without a context. Make sure you are using the Output component.");
246
359
  }
247
- trace(TracePhase.render.worker, () => dumpChildren(children));
248
360
  if (Array.isArray(node)) {
249
361
  nodesToContext.set(node, getContext());
250
362
  }
251
363
  if (Array.isArray(children)) {
252
364
  for (const child of children.flat(Infinity)) {
253
365
  appendChild(node, child);
366
+ if (lastRenderError) break;
254
367
  }
255
368
  } else {
256
369
  appendChild(node, children);
@@ -279,6 +392,10 @@ export function notifyContentState() {
279
392
  break;
280
393
  }
281
394
  current.childrenWithContent--;
395
+ if (current.childrenWithContent > 0) {
396
+ // This isn't the last content so we have no work to do
397
+ break;
398
+ }
282
399
  if (current.isEmpty) {
283
400
  current.isEmpty.value = true;
284
401
  }
@@ -310,74 +427,98 @@ export function notifyContentState() {
310
427
  });
311
428
  }
312
429
  function appendChild(node, rawChild) {
313
- trace(TracePhase.render.appendChild, () => debugPrintChild(rawChild));
430
+ if (lastRenderError) return;
314
431
  const child = normalizeChild(rawChild);
315
432
  if (typeof child === "string") {
316
433
  if (child !== "") {
317
434
  contentAdded();
435
+ debug.render.appendTextNode(node, node.length, child);
318
436
  }
319
437
  node.push(child);
320
438
  } else {
321
439
  const cache = getElementCache();
322
440
  if (cache.has(child)) {
323
- trace(TracePhase.render.appendChild, () => "Cached: " + debugPrintChild(child));
324
- node.push(cache.get(child));
441
+ const cachedNode = cache.get(child);
442
+ // recordSubtreeAdded detects cached nodes automatically and re-adds their children
443
+ if (isCustomContext(child)) {
444
+ debug.render.appendCustomContext(node, cachedNode);
445
+ } else {
446
+ debug.render.appendFragmentChild(node, cachedNode);
447
+ }
448
+ node.push(cachedNode);
325
449
  return;
326
450
  }
327
451
  if (isCustomContext(child)) {
328
- trace(TracePhase.render.appendChild, () => "CustomContext: " + debugPrintChild(child));
452
+ const newNode = [];
453
+ debug.render.appendCustomContext(node, newNode);
329
454
  child.useCustomContext(children => {
330
- const newNode = [];
331
455
  renderWorker(newNode, children);
332
456
  node.push(newNode);
333
457
  cache.set(child, newNode);
334
458
  notifyContentState();
459
+ notifyFileUpdateForNode(node);
335
460
  });
336
461
  } else if (isIntrinsicElement(child)) {
337
- trace(TracePhase.render.appendChild, () => "IntrinsicElement: " + debugPrintChild(child));
338
462
  // don't need a new context here because intrinsics are never reactive
463
+ const intrinsic = child;
339
464
  const newNode = [];
340
465
  function formatHookWithChildren(command) {
341
- node.push(createRenderTreeHook(newNode, {
466
+ const hook = createRenderTreeHook(newNode, {
342
467
  print(tree, print) {
343
468
  return command(print(tree));
344
469
  }
345
- }));
470
+ });
471
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
472
+ node.push(hook);
346
473
  renderWorker(newNode, child.props.children);
474
+ notifyFileUpdateForNode(node);
347
475
  }
348
476
  function formatHook(command) {
349
- return node.push(createRenderTreeHook(newNode, {
477
+ const hook = createRenderTreeHook(newNode, {
350
478
  print() {
351
479
  return command;
352
480
  }
353
- }));
481
+ });
482
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name);
483
+ node.push(hook);
484
+ return hook;
354
485
  }
355
486
  switch (child.name) {
356
487
  case "indent":
357
488
  return formatHookWithChildren(indent);
358
489
  case "indentIfBreak":
359
- node.push(createRenderTreeHook(newNode, {
360
- print(tree, print) {
361
- return indentIfBreak(print(tree), {
362
- groupId: child.props.groupId,
363
- negate: child.props.negate
364
- });
365
- }
366
- }));
490
+ {
491
+ const hook = createRenderTreeHook(newNode, {
492
+ print(tree, print) {
493
+ return indentIfBreak(print(tree), {
494
+ groupId: child.props.groupId,
495
+ negate: child.props.negate
496
+ });
497
+ }
498
+ });
499
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
500
+ node.push(hook);
501
+ }
367
502
  renderWorker(newNode, child.props.children);
503
+ notifyFileUpdateForNode(node);
368
504
  return;
369
505
  case "fill":
370
506
  return formatHookWithChildren(fill);
371
507
  case "group":
372
- node.push(createRenderTreeHook(newNode, {
373
- print(tree, print) {
374
- return group(print(tree), {
375
- id: child.props.id,
376
- shouldBreak: child.props.shouldBreak
377
- });
378
- }
379
- }));
508
+ {
509
+ const hook = createRenderTreeHook(newNode, {
510
+ print(tree, print) {
511
+ return group(print(tree), {
512
+ id: child.props.id,
513
+ shouldBreak: child.props.shouldBreak
514
+ });
515
+ }
516
+ });
517
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
518
+ node.push(hook);
519
+ }
380
520
  renderWorker(newNode, child.props.children);
521
+ notifyFileUpdateForNode(node);
381
522
  return;
382
523
  case "line":
383
524
  case "br":
@@ -392,12 +533,17 @@ function appendChild(node, rawChild) {
392
533
  case "lbr":
393
534
  return formatHook(literalline);
394
535
  case "align":
395
- node.push(createRenderTreeHook(newNode, {
396
- print(tree, print) {
397
- return align(child.props.width ?? child.props.string, print(tree));
398
- }
399
- }));
536
+ {
537
+ const hook = createRenderTreeHook(newNode, {
538
+ print(tree, print) {
539
+ return align(child.props.width ?? child.props.string, print(tree));
540
+ }
541
+ });
542
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
543
+ node.push(hook);
544
+ }
400
545
  renderWorker(newNode, child.props.children);
546
+ notifyFileUpdateForNode(node);
401
547
  return;
402
548
  case "lineSuffix":
403
549
  return formatHookWithChildren(lineSuffix);
@@ -412,59 +558,194 @@ function appendChild(node, rawChild) {
412
558
  case "markAsRoot":
413
559
  return formatHookWithChildren(markAsRoot);
414
560
  case "ifBreak":
415
- node.push(createRenderTreeHook(newNode, {
416
- print(tree, print) {
417
- return ifBreak(print(tree[0]), print(tree[1]));
418
- }
419
- }));
561
+ {
562
+ const hook = createRenderTreeHook(newNode, {
563
+ print(tree, print) {
564
+ return ifBreak(print(tree[0]), print(tree[1]));
565
+ }
566
+ });
567
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
568
+ node.push(hook);
569
+ }
420
570
  newNode.push([], []);
571
+ debug.render.appendFragmentChild(newNode, newNode[0]);
572
+ debug.render.appendFragmentChild(newNode, newNode[1]);
421
573
  renderWorker(newNode[0], child.props.children);
422
574
  renderWorker(newNode[1], child.props.flatContents);
575
+ notifyFileUpdateForNode(node);
423
576
  return;
424
577
  default:
425
578
  throw new Error("Unknown intrinsic element");
426
579
  }
427
580
  } else if (isComponentCreator(child)) {
581
+ const index = node.length;
582
+ const rerenderToken = ref(0);
583
+ const breakNext = ref(false);
428
584
  // todo: remove this effect (only needed for context, not needed for anything else)
429
585
  effect(() => {
430
- trace(TracePhase.render.appendChild, () => "Component: " + debugPrintChild(child));
586
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
587
+ rerenderToken.value;
431
588
  const context = getContext();
432
589
  context.childrenWithContent = 0;
433
590
  context.isEmpty ??= ref(true);
434
591
  if (context) context.componentOwner = child;
435
- const componentRoot = [];
592
+ const existing = node[index];
593
+ const componentRoot = Array.isArray(existing) ? existing : [];
594
+ context.meta ??= {};
595
+ context.meta.renderNode = componentRoot;
596
+ const propsSource = child.props ?? undefined;
597
+ const debugSession = debug.render.beginComponent({
598
+ parent: node,
599
+ index,
600
+ node: componentRoot,
601
+ component: child,
602
+ propsSource,
603
+ source: child.source,
604
+ isExisting: Array.isArray(existing),
605
+ actions: {
606
+ rerender: () => {
607
+ lastRenderError = null;
608
+ rerenderToken.value++;
609
+ },
610
+ rerenderAndBreak: () => {
611
+ lastRenderError = null;
612
+ breakNext.value = true;
613
+ rerenderToken.value++;
614
+ }
615
+ }
616
+ });
617
+ if (Array.isArray(existing)) {
618
+ componentRoot.length = 0;
619
+ }
436
620
  pushStack(child.component, child.props, child.source);
437
- renderWorker(componentRoot, untrack(child));
438
- popStack();
439
- node.push(componentRoot);
621
+ let renderFailed = false;
622
+ let childResult;
623
+ try {
624
+ childResult = untrack(() => {
625
+ const shouldBreak = breakNext.value;
626
+ if (shouldBreak) {
627
+ breakNext.value = false;
628
+ // eslint-disable-next-line no-debugger
629
+ debugger;
630
+ }
631
+ return child();
632
+ });
633
+ } catch (error) {
634
+ notifyRenderError(error);
635
+ renderFailed = true;
636
+ throw error;
637
+ }
638
+ try {
639
+ if (context?.meta?.directory) {
640
+ debugSession.recordDirectory(context.meta.directory.path);
641
+ }
642
+ if (context?.meta?.sourceFile) {
643
+ context.meta.renderNode = componentRoot;
644
+ debugSession.recordFile(context.meta.sourceFile.path, context.meta.sourceFile.filetype);
645
+ context.meta.sourceFileReady = false;
646
+ }
647
+ if (!renderFailed) {
648
+ renderWorker(componentRoot, childResult);
649
+ }
650
+ } finally {
651
+ popStack();
652
+ }
653
+ if (renderFailed) {
654
+ node[index] = componentRoot;
655
+ cache.set(child, componentRoot);
656
+ notifyFileUpdateForNode(node);
657
+ notifyContentState();
658
+ onCleanup(() => debugSession.dispose());
659
+ return;
660
+ }
661
+ if (context?.meta?.sourceFile) {
662
+ context.meta.sourceFileReady = true;
663
+ notifyFileUpdateForNode(componentRoot);
664
+ }
665
+ node[index] = componentRoot;
440
666
  cache.set(child, componentRoot);
441
667
  notifyContentState();
442
- trace(TracePhase.render.appendChild, () => "Component done: " + debugPrintChild(child) + ", empty: " + context.isEmpty.value);
668
+ onCleanup(() => debugSession.dispose());
669
+ }, undefined, {
670
+ debug: {
671
+ name: `render:${child.component.name || "Anonymous"}`,
672
+ type: "render"
673
+ }
443
674
  });
444
675
  } else if (typeof child === "function") {
445
- trace(TracePhase.render.appendChild, () => "Memo: " + child.toString());
446
676
  const index = node.length;
447
677
  effect(() => {
448
- trace(TracePhase.render.renderEffect, () => "");
449
- let res = child();
450
- while (typeof res === "function" && !isComponentCreator(res)) {
451
- res = res();
678
+ let res;
679
+ let renderFailed = false;
680
+ try {
681
+ res = child();
682
+ while (typeof res === "function" && !isComponentCreator(res)) {
683
+ res = res();
684
+ }
685
+ } catch (error) {
686
+ notifyRenderError(error);
687
+ renderFailed = true;
688
+ throw error;
452
689
  }
453
690
  const context = getContext();
454
691
  context.childrenWithContent = 0;
455
692
  context.isEmpty ??= ref(true);
456
- const newNodes = [];
457
- renderWorker(newNodes, res);
458
- node[index] = newNodes;
459
- cache.set(child, newNodes);
693
+ const existing = node[index];
694
+ const memoNode = Array.isArray(existing) ? existing : [];
695
+ debug.render.prepareMemoNode(node, memoNode, Array.isArray(existing));
696
+ if (Array.isArray(existing)) {
697
+ memoNode.length = 0;
698
+ }
699
+ if (!renderFailed) {
700
+ renderWorker(memoNode, res);
701
+ }
702
+ node[index] = memoNode;
703
+ cache.set(child, memoNode);
704
+ notifyFileUpdateForNode(node);
460
705
  notifyContentState();
461
- return newNodes;
706
+ return memoNode;
707
+ }, undefined, {
708
+ debug: {
709
+ name: `render:memo:${child.name || "anonymous"}`,
710
+ type: "render"
711
+ }
462
712
  });
463
713
  } else {
464
714
  throw new Error("Unexpected child type");
465
715
  }
466
716
  }
467
717
  }
718
+ function findSourceFileContext(node) {
719
+ let context = getContextForRenderNode(node) ?? null;
720
+ while (context) {
721
+ if (context.meta?.sourceFile) return context;
722
+ context = context.owner;
723
+ }
724
+ return undefined;
725
+ }
726
+ function notifyFileUpdateForNode(node) {
727
+ // Only do the expensive printTree when devtools are actually enabled
728
+ if (!isDevtoolsEnabled()) return;
729
+ const context = findSourceFileContext(node);
730
+ if (!context?.meta?.sourceFile) return;
731
+ if (context.meta.sourceFileReady === false) return;
732
+ const sourceFile = context.meta.sourceFile;
733
+ const renderNode = context.meta.renderNode ?? node;
734
+ // Pass noFlush here since it flushes jobs and can re-enter rendering
735
+ // during effect setup, triggering premature cleanup.
736
+ const contents = printTree(renderNode, {
737
+ printWidth: context.meta?.printOptions?.printWidth,
738
+ tabWidth: context.meta?.printOptions?.tabWidth,
739
+ useTabs: context.meta?.printOptions?.useTabs,
740
+ insertFinalNewLine: context.meta?.printOptions?.insertFinalNewLine ?? true,
741
+ noFlush: true
742
+ });
743
+ debug.files.updated({
744
+ path: sourceFile.path,
745
+ filetype: sourceFile.filetype,
746
+ contents
747
+ });
748
+ }
468
749
  function normalizeChild(child) {
469
750
  if (Array.isArray(child)) {
470
751
  return child.map(normalizeChild);
@@ -496,29 +777,6 @@ function normalizeChild(child) {
496
777
  return String(child);
497
778
  }
498
779
  }
499
- function dumpChildren(children) {
500
- if (Array.isArray(children)) {
501
- return `[ ${children.map(debugPrintChild).join(", ")} ]`;
502
- }
503
- return debugPrintChild(children);
504
- }
505
- function debugPrintChild(child) {
506
- if (isComponentCreator(child)) {
507
- return "<" + child.component.name + ">";
508
- } else if (typeof child === "function") {
509
- return "$memo";
510
- } else if (isRef(child)) {
511
- return "$ref";
512
- } else if (isIntrinsicElement(child)) {
513
- return `<${child.name}>`;
514
- } else if (isRenderableObject(child)) {
515
- return `CustomChildElement(${JSON.stringify(child)})`;
516
- } else if (isRefkeyable(child)) {
517
- return `refkey`;
518
- } else {
519
- return JSON.stringify(child);
520
- }
521
- }
522
780
  const defaultPrintTreeOptions = {
523
781
  printWidth: 80,
524
782
  tabWidth: 2
@@ -533,9 +791,10 @@ export function printTree(tree, options) {
533
791
  ...defaultPrintTreeOptions,
534
792
  ...Object.fromEntries(Object.entries(options ?? {}).filter(([_, v]) => v !== undefined))
535
793
  };
536
-
537
- // make sure queue is empty
538
- flushJobs();
794
+ if (!options.noFlush) {
795
+ // make sure queue is empty
796
+ flushJobs();
797
+ }
539
798
  const d = printTreeWorker(tree);
540
799
  const result = doc.printer.printDocToString(d, options).formatted;
541
800
  return options.insertFinalNewLine && !result.endsWith("\n") ? `${result}\n` : result;