@alloy-js/core 0.23.0-dev.10 → 0.23.0-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devtools/index.html +29 -17
- package/dist/src/binder.d.ts.map +1 -1
- package/dist/src/binder.js +5 -0
- package/dist/src/binder.js.map +1 -1
- package/dist/src/components/For.d.ts.map +1 -1
- package/dist/src/components/For.js +1 -1
- package/dist/src/components/For.js.map +1 -1
- package/dist/src/components/List.d.ts.map +1 -1
- package/dist/src/components/List.js +1 -1
- package/dist/src/components/List.js.map +1 -1
- package/dist/src/components/Switch.d.ts.map +1 -1
- package/dist/src/components/Switch.js +1 -1
- package/dist/src/components/Switch.js.map +1 -1
- package/dist/src/debug/diagnostics.test.js +3 -2
- package/dist/src/debug/diagnostics.test.js.map +1 -1
- package/dist/src/debug/effects.d.ts +12 -4
- package/dist/src/debug/effects.d.ts.map +1 -1
- package/dist/src/debug/effects.js +182 -52
- package/dist/src/debug/effects.js.map +1 -1
- package/dist/src/debug/effects.test.js +213 -41
- package/dist/src/debug/effects.test.js.map +1 -1
- package/dist/src/debug/files.d.ts.map +1 -1
- package/dist/src/debug/files.js +7 -18
- package/dist/src/debug/files.js.map +1 -1
- package/dist/src/debug/files.test.js +13 -36
- package/dist/src/debug/files.test.js.map +1 -1
- package/dist/src/debug/index.d.ts +4 -2
- package/dist/src/debug/index.d.ts.map +1 -1
- package/dist/src/debug/index.js +4 -2
- package/dist/src/debug/index.js.map +1 -1
- package/dist/src/debug/message-format.test.d.ts +2 -0
- package/dist/src/debug/message-format.test.d.ts.map +1 -0
- package/dist/src/debug/message-format.test.js +700 -0
- package/dist/src/debug/message-format.test.js.map +1 -0
- package/dist/src/debug/render-tree-orphans.test.d.ts +2 -0
- package/dist/src/debug/render-tree-orphans.test.d.ts.map +1 -0
- package/dist/src/debug/render-tree-orphans.test.js +297 -0
- package/dist/src/debug/render-tree-orphans.test.js.map +1 -0
- package/dist/src/debug/render.d.ts.map +1 -1
- package/dist/src/debug/render.js +83 -130
- package/dist/src/debug/render.js.map +1 -1
- package/dist/src/debug/render.test.js +91 -128
- package/dist/src/debug/render.test.js.map +1 -1
- package/dist/src/debug/symbols.d.ts +6 -5
- package/dist/src/debug/symbols.d.ts.map +1 -1
- package/dist/src/debug/symbols.js +46 -23
- package/dist/src/debug/symbols.js.map +1 -1
- package/dist/src/debug/symbols.test.js +15 -26
- package/dist/src/debug/symbols.test.js.map +1 -1
- package/dist/src/debug/trace-writer.d.ts +55 -0
- package/dist/src/debug/trace-writer.d.ts.map +1 -0
- package/dist/src/debug/trace-writer.js +658 -0
- package/dist/src/debug/trace-writer.js.map +1 -0
- package/dist/src/debug/trace.d.ts +10 -10
- package/dist/src/debug/trace.d.ts.map +1 -1
- package/dist/src/debug/trace.js +23 -20
- package/dist/src/debug/trace.js.map +1 -1
- package/dist/src/devtools/devtools-protocol.d.ts +318 -161
- package/dist/src/devtools/devtools-protocol.d.ts.map +1 -1
- package/dist/src/devtools/devtools-server.browser.d.ts +0 -5
- package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -1
- package/dist/src/devtools/devtools-server.browser.js +0 -3
- package/dist/src/devtools/devtools-server.browser.js.map +1 -1
- package/dist/src/devtools/devtools-server.d.ts +0 -6
- package/dist/src/devtools/devtools-server.d.ts.map +1 -1
- package/dist/src/devtools/devtools-server.js +212 -24
- package/dist/src/devtools/devtools-server.js.map +1 -1
- package/dist/src/devtools/devtools-transport.d.ts +2 -2
- package/dist/src/devtools/devtools-transport.d.ts.map +1 -1
- package/dist/src/devtools/devtools-transport.js +2 -2
- package/dist/src/devtools/devtools-transport.js.map +1 -1
- package/dist/src/devtools-entry.browser.d.ts +1 -1
- package/dist/src/devtools-entry.browser.d.ts.map +1 -1
- package/dist/src/devtools-entry.browser.js.map +1 -1
- package/dist/src/devtools-entry.d.ts +1 -1
- package/dist/src/devtools-entry.d.ts.map +1 -1
- package/dist/src/devtools-entry.js.map +1 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +5 -5
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/reactivity.d.ts +13 -2
- package/dist/src/reactivity.d.ts.map +1 -1
- package/dist/src/reactivity.js +96 -13
- package/dist/src/reactivity.js.map +1 -1
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +84 -30
- package/dist/src/render.js.map +1 -1
- package/dist/src/scheduler.d.ts +5 -0
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +94 -23
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +11 -5
- package/dist/src/utils.js.map +1 -1
- package/dist/testing/devtools-utils.d.ts +12 -3
- package/dist/testing/devtools-utils.d.ts.map +1 -1
- package/dist/testing/devtools-utils.js +26 -4
- package/dist/testing/devtools-utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/binder.ts +47 -38
- package/src/components/For.tsx +14 -10
- package/src/components/List.tsx +7 -4
- package/src/components/Switch.tsx +11 -7
- package/src/debug/diagnostics.test.tsx +3 -2
- package/src/debug/effects.test.tsx +248 -36
- package/src/debug/effects.ts +276 -62
- package/src/debug/files.test.tsx +15 -35
- package/src/debug/files.ts +11 -11
- package/src/debug/index.ts +4 -0
- package/src/debug/message-format.test.tsx +759 -0
- package/src/debug/render-tree-orphans.test.tsx +344 -0
- package/src/debug/render.test.tsx +96 -118
- package/src/debug/render.ts +183 -124
- package/src/debug/symbols.test.tsx +19 -20
- package/src/debug/symbols.ts +106 -23
- package/src/debug/trace-writer.ts +969 -0
- package/src/debug/trace.ts +25 -28
- package/src/devtools/devtools-protocol.ts +361 -176
- package/src/devtools/devtools-server.browser.ts +0 -9
- package/src/devtools/devtools-server.ts +210 -32
- package/src/devtools/devtools-transport.ts +4 -4
- package/src/devtools-entry.browser.ts +11 -15
- package/src/devtools-entry.ts +9 -15
- package/src/diagnostics.ts +14 -5
- package/src/reactivity.ts +113 -17
- package/src/render.ts +104 -30
- package/src/scheduler.ts +145 -26
- package/src/utils.tsx +7 -4
- package/temp/api.json +142 -20
- package/testing/devtools-utils.ts +46 -4
package/src/debug/effects.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { isReactive, isRef } from "@vue/reactivity";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
formatReactivePropertyLabel,
|
|
4
|
+
getReactiveCreationLocation,
|
|
5
|
+
nextReactiveId,
|
|
6
|
+
} from "../reactivity.js";
|
|
7
|
+
import { insertEdge, insertEffect, insertRef } from "./trace-writer.js";
|
|
8
|
+
import {
|
|
9
|
+
isDebugEnabled,
|
|
10
|
+
isTraceEnabled,
|
|
11
|
+
logDevtoolsMessage,
|
|
5
12
|
TracePhase,
|
|
6
13
|
traceType,
|
|
7
14
|
} from "./trace.js";
|
|
@@ -22,15 +29,19 @@ export interface EffectDebugInfo {
|
|
|
22
29
|
name?: string;
|
|
23
30
|
type?: string;
|
|
24
31
|
createdAt?: SourceLocation;
|
|
32
|
+
contextId?: number;
|
|
33
|
+
ownerContextId?: number | null;
|
|
34
|
+
component?: string;
|
|
25
35
|
lastTriggeredByRefId?: number;
|
|
26
|
-
lastTriggeredAt?: SourceLocation;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
export interface RefDebugInfo {
|
|
30
39
|
id: number;
|
|
31
40
|
kind?: string;
|
|
41
|
+
label?: string;
|
|
32
42
|
createdAt?: SourceLocation;
|
|
33
43
|
createdByEffectId?: number;
|
|
44
|
+
isApproxLocation?: boolean;
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
export interface EffectEdgeDebugInfo {
|
|
@@ -42,33 +53,50 @@ export interface EffectEdgeDebugInfo {
|
|
|
42
53
|
targetKind?: "ref" | "target";
|
|
43
54
|
targetLabel?: string;
|
|
44
55
|
targetKey?: string | number;
|
|
45
|
-
location?: SourceLocation;
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
const effects = new Map<number, EffectDebugInfo>();
|
|
49
59
|
const refs = new Map<number, RefDebugInfo>();
|
|
50
60
|
let effectIdCounter = 1;
|
|
51
61
|
let edgeEventIdCounter = 1;
|
|
52
|
-
let nonRefTargetIdCounter = 1;
|
|
53
62
|
const nonRefTargetIds = new WeakMap<object, number>();
|
|
54
63
|
const primitiveTargetIds = new Map<unknown, number>();
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
// Alloy-internal paths to skip when capturing source locations.
|
|
66
|
+
// We skip core infrastructure (reactivity, render, debug, scheduler, etc.)
|
|
67
|
+
// but allow component and symbol frames through so the location points at
|
|
68
|
+
// the component/symbol that created the effect.
|
|
69
|
+
// These patterns match both src/ and dist/src/ paths.
|
|
70
|
+
const CORE_INTERNAL_PATHS = [
|
|
71
|
+
"/core/src/reactivity",
|
|
72
|
+
"/core/src/render",
|
|
73
|
+
"/core/src/scheduler",
|
|
74
|
+
"/core/src/debug/",
|
|
75
|
+
"/core/src/devtools/",
|
|
76
|
+
"/core/src/resource",
|
|
77
|
+
"/core/src/context",
|
|
78
|
+
"/core/src/tracer",
|
|
79
|
+
"/core/src/reactive-union-set",
|
|
80
|
+
"/core/src/utils",
|
|
81
|
+
"/core/dist/src/reactivity",
|
|
82
|
+
"/core/dist/src/render",
|
|
83
|
+
"/core/dist/src/scheduler",
|
|
84
|
+
"/core/dist/src/debug/",
|
|
85
|
+
"/core/dist/src/devtools/",
|
|
86
|
+
"/core/dist/src/resource",
|
|
87
|
+
"/core/dist/src/context",
|
|
88
|
+
"/core/dist/src/tracer",
|
|
89
|
+
"/core/dist/src/reactive-union-set",
|
|
90
|
+
"/core/dist/src/utils",
|
|
91
|
+
];
|
|
92
|
+
|
|
57
93
|
const STACK_SKIP = [
|
|
58
94
|
"node:internal",
|
|
59
|
-
"/node_modules/",
|
|
60
|
-
"\\node_modules\\",
|
|
61
95
|
"/@vue/",
|
|
62
96
|
"\\@vue\\",
|
|
63
|
-
"/@alloy-js/",
|
|
64
|
-
"\\@alloy-js\\",
|
|
65
|
-
"/packages/core/src/reactivity",
|
|
66
|
-
"/packages/core/src/devtools/effects-debug",
|
|
67
|
-
"/packages/core/dist/src/reactivity",
|
|
68
|
-
"\\packages\\core\\dist\\src\\reactivity",
|
|
69
|
-
"/packages/core/dist/src/devtools/effects-debug",
|
|
70
|
-
"\\packages\\core\\dist\\src\\devtools\\effects-debug",
|
|
71
97
|
"captureSourceLocation",
|
|
98
|
+
...CORE_INTERNAL_PATHS,
|
|
99
|
+
...CORE_INTERNAL_PATHS.map((p) => p.replace(/\//g, "\\")),
|
|
72
100
|
];
|
|
73
101
|
|
|
74
102
|
const VUE_REACTIVITY_MARKERS = [
|
|
@@ -80,70 +108,185 @@ const VUE_REACTIVITY_MARKERS = [
|
|
|
80
108
|
"\\@vue\\",
|
|
81
109
|
];
|
|
82
110
|
|
|
83
|
-
|
|
84
|
-
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
// Fast source location capture using V8 structured CallSite API
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
// Lazily loaded findSourceMap from node:module
|
|
116
|
+
let findSourceMap:
|
|
117
|
+
| ((path: string) =>
|
|
118
|
+
| {
|
|
119
|
+
findEntry: (
|
|
120
|
+
line: number,
|
|
121
|
+
col: number,
|
|
122
|
+
) =>
|
|
123
|
+
| {
|
|
124
|
+
originalSource: string;
|
|
125
|
+
originalLine: number;
|
|
126
|
+
originalColumn: number;
|
|
127
|
+
}
|
|
128
|
+
| undefined;
|
|
129
|
+
}
|
|
130
|
+
| undefined)
|
|
131
|
+
| undefined;
|
|
132
|
+
let findSourceMapLoaded = false;
|
|
133
|
+
let realpathSync: ((path: string) => string) | undefined;
|
|
134
|
+
// Cache realpath lookups to avoid repeated fs calls
|
|
135
|
+
const realpathCache = new Map<string, string>();
|
|
136
|
+
|
|
137
|
+
function loadFindSourceMap() {
|
|
138
|
+
if (findSourceMapLoaded) return;
|
|
139
|
+
findSourceMapLoaded = true;
|
|
140
|
+
// process.getBuiltinModule works in both ESM and CJS contexts
|
|
141
|
+
try {
|
|
142
|
+
const mod = process.getBuiltinModule?.("node:module") as
|
|
143
|
+
| typeof import("node:module")
|
|
144
|
+
| undefined;
|
|
145
|
+
if (mod && typeof mod.findSourceMap === "function") {
|
|
146
|
+
findSourceMap = mod.findSourceMap as typeof findSourceMap;
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// not available
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const fs = process.getBuiltinModule?.("node:fs") as
|
|
153
|
+
| typeof import("node:fs")
|
|
154
|
+
| undefined;
|
|
155
|
+
if (fs) {
|
|
156
|
+
realpathSync = fs.realpathSync;
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// not available
|
|
160
|
+
}
|
|
85
161
|
}
|
|
86
162
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
163
|
+
function getRealPath(fileName: string): string {
|
|
164
|
+
if (!realpathSync) return fileName;
|
|
165
|
+
let real = realpathCache.get(fileName);
|
|
166
|
+
if (real === undefined) {
|
|
167
|
+
try {
|
|
168
|
+
real = realpathSync(fileName);
|
|
169
|
+
} catch {
|
|
170
|
+
real = fileName;
|
|
171
|
+
}
|
|
172
|
+
realpathCache.set(fileName, real);
|
|
173
|
+
}
|
|
174
|
+
return real;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isSkipFile(fileName: string): boolean {
|
|
178
|
+
for (const skip of STACK_SKIP) {
|
|
179
|
+
if (fileName.includes(skip)) return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isVueReactivityFile(fileName: string): boolean {
|
|
185
|
+
for (const marker of VUE_REACTIVITY_MARKERS) {
|
|
186
|
+
if (fileName.includes(marker)) return true;
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function resolveSourceMap(
|
|
192
|
+
fileName: string,
|
|
193
|
+
line: number,
|
|
194
|
+
col: number,
|
|
195
|
+
): { fileName: string; line: number; col: number } {
|
|
196
|
+
if (!findSourceMap) return { fileName, line, col };
|
|
197
|
+
// pnpm uses symlinks; findSourceMap only matches the real path
|
|
198
|
+
const real = getRealPath(fileName);
|
|
199
|
+
const map = findSourceMap(real);
|
|
200
|
+
if (!map) return { fileName, line, col };
|
|
201
|
+
const entry = map.findEntry(line - 1, col - 1);
|
|
202
|
+
if (!entry) return { fileName, line, col };
|
|
94
203
|
return {
|
|
95
|
-
fileName,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
stack,
|
|
204
|
+
fileName: entry.originalSource,
|
|
205
|
+
line: entry.originalLine + 1,
|
|
206
|
+
col: entry.originalColumn + 1,
|
|
99
207
|
};
|
|
100
208
|
}
|
|
101
209
|
|
|
210
|
+
// V8 structured stack capture — avoids string formatting entirely
|
|
211
|
+
const structuredPrepare = (
|
|
212
|
+
_err: Error,
|
|
213
|
+
callSites: NodeJS.CallSite[],
|
|
214
|
+
): NodeJS.CallSite[] => callSites;
|
|
215
|
+
|
|
216
|
+
function captureCallSites(): NodeJS.CallSite[] {
|
|
217
|
+
const orig = Error.prepareStackTrace;
|
|
218
|
+
Error.prepareStackTrace = structuredPrepare;
|
|
219
|
+
const obj: { stack?: NodeJS.CallSite[] } = {};
|
|
220
|
+
Error.captureStackTrace(obj, captureCallSites);
|
|
221
|
+
const sites = obj.stack ?? [];
|
|
222
|
+
Error.prepareStackTrace = orig;
|
|
223
|
+
return sites;
|
|
224
|
+
}
|
|
225
|
+
|
|
102
226
|
export function captureSourceLocation(
|
|
103
227
|
skipReactives = true,
|
|
104
228
|
): SourceLocation | undefined {
|
|
105
|
-
if (!
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
for (const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (
|
|
229
|
+
if (!isDebugEnabled()) return undefined;
|
|
230
|
+
loadFindSourceMap();
|
|
231
|
+
|
|
232
|
+
const sites = captureCallSites();
|
|
233
|
+
|
|
234
|
+
// First pass: skip internal/framework frames
|
|
235
|
+
for (const site of sites) {
|
|
236
|
+
const fn = site.getFileName();
|
|
237
|
+
if (!fn) continue;
|
|
238
|
+
if (isSkipFile(fn)) continue;
|
|
239
|
+
if (skipReactives && isVueReactivityFile(fn)) continue;
|
|
240
|
+
|
|
241
|
+
const line = site.getLineNumber() ?? 0;
|
|
242
|
+
const col = site.getColumnNumber() ?? 0;
|
|
243
|
+
const resolved = resolveSourceMap(fn, line, col);
|
|
244
|
+
return {
|
|
245
|
+
fileName: resolved.fileName,
|
|
246
|
+
lineNumber: resolved.line,
|
|
247
|
+
columnNumber: resolved.col,
|
|
248
|
+
};
|
|
115
249
|
}
|
|
116
250
|
|
|
251
|
+
// Second pass without reactive filter
|
|
117
252
|
if (skipReactives) {
|
|
118
|
-
for (const
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
253
|
+
for (const site of sites) {
|
|
254
|
+
const fn = site.getFileName();
|
|
255
|
+
if (!fn) continue;
|
|
256
|
+
if (isSkipFile(fn)) continue;
|
|
257
|
+
|
|
258
|
+
const line = site.getLineNumber() ?? 0;
|
|
259
|
+
const col = site.getColumnNumber() ?? 0;
|
|
260
|
+
const resolved = resolveSourceMap(fn, line, col);
|
|
261
|
+
return {
|
|
262
|
+
fileName: resolved.fileName,
|
|
263
|
+
lineNumber: resolved.line,
|
|
264
|
+
columnNumber: resolved.col,
|
|
265
|
+
};
|
|
123
266
|
}
|
|
124
267
|
}
|
|
125
268
|
|
|
126
|
-
return
|
|
269
|
+
return undefined;
|
|
127
270
|
}
|
|
128
271
|
|
|
129
272
|
function getNonRefTargetId(target: unknown): number {
|
|
130
273
|
if (typeof target === "object" && target !== null) {
|
|
131
274
|
const existing = nonRefTargetIds.get(target);
|
|
132
275
|
if (existing) return existing;
|
|
133
|
-
const id =
|
|
276
|
+
const id = nextReactiveId();
|
|
134
277
|
nonRefTargetIds.set(target, id);
|
|
135
278
|
return id;
|
|
136
279
|
}
|
|
137
280
|
if (typeof target === "function") {
|
|
138
281
|
const existing = nonRefTargetIds.get(target as object);
|
|
139
282
|
if (existing) return existing;
|
|
140
|
-
const id =
|
|
283
|
+
const id = nextReactiveId();
|
|
141
284
|
nonRefTargetIds.set(target as object, id);
|
|
142
285
|
return id;
|
|
143
286
|
}
|
|
144
287
|
const existing = primitiveTargetIds.get(target);
|
|
145
288
|
if (existing) return existing;
|
|
146
|
-
const id =
|
|
289
|
+
const id = nextReactiveId();
|
|
147
290
|
primitiveTargetIds.set(target, id);
|
|
148
291
|
return id;
|
|
149
292
|
}
|
|
@@ -192,11 +335,11 @@ function buildEffectTargetInfo(input: {
|
|
|
192
335
|
}
|
|
193
336
|
|
|
194
337
|
function emitEffect(message: { type: string; [key: string]: unknown }) {
|
|
195
|
-
|
|
338
|
+
logDevtoolsMessage(message);
|
|
196
339
|
}
|
|
197
340
|
|
|
198
341
|
export function update(input: Partial<EffectDebugInfo> & { id: number }) {
|
|
199
|
-
if (!
|
|
342
|
+
if (!isDebugEnabled()) return;
|
|
200
343
|
const existing = effects.get(input.id);
|
|
201
344
|
if (!existing) return;
|
|
202
345
|
const next: EffectDebugInfo = { ...existing, ...input };
|
|
@@ -214,16 +357,32 @@ export function register(input: {
|
|
|
214
357
|
contextId?: number;
|
|
215
358
|
ownerContextId?: number | null;
|
|
216
359
|
}): number {
|
|
217
|
-
if (!
|
|
360
|
+
if (!isDebugEnabled()) return -1;
|
|
218
361
|
const id = effectIdCounter++;
|
|
219
362
|
const info: EffectDebugInfo = {
|
|
220
363
|
id,
|
|
221
364
|
name: input.name,
|
|
222
365
|
type: input.type,
|
|
223
366
|
createdAt: input.createdAt ?? captureSourceLocation(),
|
|
367
|
+
contextId: input.contextId,
|
|
368
|
+
ownerContextId: input.ownerContextId ?? null,
|
|
224
369
|
};
|
|
225
370
|
effects.set(id, info);
|
|
226
371
|
emitEffect({ type: traceType(TracePhase.effect.effectAdded), effect: info });
|
|
372
|
+
|
|
373
|
+
if (isTraceEnabled()) {
|
|
374
|
+
insertEffect(
|
|
375
|
+
id,
|
|
376
|
+
input.name,
|
|
377
|
+
input.type,
|
|
378
|
+
input.contextId,
|
|
379
|
+
input.ownerContextId ?? null,
|
|
380
|
+
info.createdAt?.fileName,
|
|
381
|
+
info.createdAt?.lineNumber,
|
|
382
|
+
info.createdAt?.columnNumber,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
227
386
|
return id;
|
|
228
387
|
}
|
|
229
388
|
|
|
@@ -233,42 +392,90 @@ export function registerRef(input: {
|
|
|
233
392
|
createdAt?: SourceLocation;
|
|
234
393
|
createdByEffectId?: number;
|
|
235
394
|
isInfrastructure?: boolean;
|
|
395
|
+
isApproxLocation?: boolean;
|
|
396
|
+
label?: string;
|
|
236
397
|
}) {
|
|
237
|
-
if (!
|
|
398
|
+
if (!isDebugEnabled()) return;
|
|
238
399
|
if (refs.has(input.id)) return;
|
|
239
400
|
const info: RefDebugInfo = {
|
|
240
401
|
id: input.id,
|
|
241
402
|
kind: input.kind,
|
|
242
403
|
createdAt: input.createdAt ?? captureSourceLocation(),
|
|
243
404
|
createdByEffectId: input.createdByEffectId,
|
|
405
|
+
label: input.label,
|
|
406
|
+
isApproxLocation: input.isApproxLocation,
|
|
244
407
|
};
|
|
245
408
|
refs.set(input.id, info);
|
|
246
409
|
emitEffect({ type: traceType(TracePhase.effect.refAdded), ref: info });
|
|
410
|
+
|
|
411
|
+
if (isTraceEnabled()) {
|
|
412
|
+
insertRef(
|
|
413
|
+
input.id,
|
|
414
|
+
input.kind,
|
|
415
|
+
input.createdByEffectId,
|
|
416
|
+
info.createdAt?.fileName,
|
|
417
|
+
info.createdAt?.lineNumber,
|
|
418
|
+
info.createdAt?.columnNumber,
|
|
419
|
+
input.label,
|
|
420
|
+
input.isApproxLocation,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
247
423
|
}
|
|
248
424
|
|
|
249
425
|
export function ensureRef(input: { id: number; kind?: string }) {
|
|
250
|
-
if (!
|
|
426
|
+
if (!isDebugEnabled()) return;
|
|
251
427
|
if (refs.has(input.id)) return;
|
|
252
428
|
registerRef({ id: input.id, kind: input.kind });
|
|
253
429
|
}
|
|
254
430
|
|
|
431
|
+
export function ensureReactivePropertyRef(input: {
|
|
432
|
+
id: number;
|
|
433
|
+
target: object;
|
|
434
|
+
key: string | number;
|
|
435
|
+
}) {
|
|
436
|
+
if (!isDebugEnabled()) return;
|
|
437
|
+
if (refs.has(input.id)) return;
|
|
438
|
+
const label = formatReactivePropertyLabel(input.target, input.key);
|
|
439
|
+
const createdAt = getReactiveCreationLocation(input.target);
|
|
440
|
+
// If the reactive wasn't created via alloy's shallowReactive wrapper,
|
|
441
|
+
// createdAt is undefined and registerRef falls back to captureSourceLocation
|
|
442
|
+
// (the first track site). Flag this as approximate.
|
|
443
|
+
const isApproxLocation = !createdAt;
|
|
444
|
+
registerRef({
|
|
445
|
+
id: input.id,
|
|
446
|
+
kind: "reactive-property",
|
|
447
|
+
label,
|
|
448
|
+
createdAt,
|
|
449
|
+
isApproxLocation,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
255
453
|
export function track(input: {
|
|
256
454
|
effectId: number;
|
|
257
455
|
target: unknown;
|
|
258
456
|
refId?: number;
|
|
259
457
|
targetKey?: unknown;
|
|
260
|
-
location?: SourceLocation;
|
|
261
458
|
}) {
|
|
262
|
-
if (!
|
|
459
|
+
if (!isDebugEnabled()) return;
|
|
263
460
|
const edge: EffectEdgeDebugInfo = {
|
|
264
461
|
id: edgeEventIdCounter++,
|
|
265
462
|
type: "track",
|
|
266
463
|
effectId: input.effectId,
|
|
267
464
|
...buildEffectTargetInfo({ target: input.target, refId: input.refId }),
|
|
268
465
|
targetKey: sanitizeTargetKey(input.targetKey),
|
|
269
|
-
location: input.location ?? captureSourceLocation(),
|
|
270
466
|
};
|
|
271
467
|
emitEffect({ type: traceType(TracePhase.effect.track), edge });
|
|
468
|
+
|
|
469
|
+
if (isTraceEnabled()) {
|
|
470
|
+
insertEdge(
|
|
471
|
+
"track",
|
|
472
|
+
input.effectId,
|
|
473
|
+
edge.refId,
|
|
474
|
+
edge.targetId,
|
|
475
|
+
edge.targetKey,
|
|
476
|
+
undefined,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
272
479
|
}
|
|
273
480
|
|
|
274
481
|
export function trigger(input: {
|
|
@@ -276,25 +483,33 @@ export function trigger(input: {
|
|
|
276
483
|
target: unknown;
|
|
277
484
|
refId?: number;
|
|
278
485
|
targetKey?: unknown;
|
|
279
|
-
location?: SourceLocation;
|
|
280
486
|
kind?: "trigger" | "triggered-by";
|
|
281
487
|
causedBy?: number;
|
|
282
488
|
}) {
|
|
283
|
-
if (!
|
|
489
|
+
if (!isDebugEnabled()) return;
|
|
284
490
|
const edge: EffectEdgeDebugInfo = {
|
|
285
491
|
id: edgeEventIdCounter++,
|
|
286
|
-
type: input.kind ?? "
|
|
492
|
+
type: input.kind ?? "trigger",
|
|
287
493
|
effectId: input.effectId,
|
|
288
494
|
...buildEffectTargetInfo({ target: input.target, refId: input.refId }),
|
|
289
495
|
targetKey: sanitizeTargetKey(input.targetKey),
|
|
290
|
-
location: input.location ?? captureSourceLocation(),
|
|
291
496
|
};
|
|
292
497
|
emitEffect({ type: traceType(TracePhase.effect.trigger), edge });
|
|
293
498
|
|
|
499
|
+
if (isTraceEnabled()) {
|
|
500
|
+
insertEdge(
|
|
501
|
+
edge.type,
|
|
502
|
+
input.effectId,
|
|
503
|
+
edge.refId,
|
|
504
|
+
edge.targetId,
|
|
505
|
+
edge.targetKey,
|
|
506
|
+
input.causedBy,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
294
510
|
update({
|
|
295
511
|
id: input.effectId,
|
|
296
512
|
...(input.refId !== undefined ? { lastTriggeredByRefId: input.refId } : {}),
|
|
297
|
-
lastTriggeredAt: input.location ?? captureSourceLocation(),
|
|
298
513
|
});
|
|
299
514
|
}
|
|
300
515
|
|
|
@@ -304,7 +519,6 @@ export function reset() {
|
|
|
304
519
|
primitiveTargetIds.clear();
|
|
305
520
|
effectIdCounter = 1;
|
|
306
521
|
edgeEventIdCounter = 1;
|
|
307
|
-
nonRefTargetIdCounter = 1;
|
|
308
522
|
}
|
|
309
523
|
|
|
310
524
|
// Utilities used by other debug sections
|
package/src/debug/files.test.tsx
CHANGED
|
@@ -5,16 +5,13 @@ import {
|
|
|
5
5
|
type DevtoolsMessage,
|
|
6
6
|
} from "../../testing/devtools-utils.js";
|
|
7
7
|
import { Output } from "../components/Output.jsx";
|
|
8
|
-
import { Show } from "../components/Show.jsx";
|
|
9
8
|
import { SourceDirectory } from "../components/SourceDirectory.jsx";
|
|
10
9
|
import { SourceFile } from "../components/SourceFile.jsx";
|
|
11
10
|
import {
|
|
12
11
|
enableDevtools,
|
|
13
12
|
resetDevtoolsServerForTests,
|
|
14
13
|
} from "../devtools/devtools-server.js";
|
|
15
|
-
import { ref } from "../reactivity.js";
|
|
16
14
|
import { renderAsync } from "../render.js";
|
|
17
|
-
import { flushJobsAsync } from "../scheduler.js";
|
|
18
15
|
|
|
19
16
|
let socket: WebSocket | undefined;
|
|
20
17
|
|
|
@@ -38,59 +35,42 @@ afterEach(async () => {
|
|
|
38
35
|
});
|
|
39
36
|
|
|
40
37
|
it("emits file and directory add/update/remove messages", async () => {
|
|
41
|
-
const
|
|
42
|
-
const collector = createMessageCollector(socket!);
|
|
38
|
+
const collector = await createMessageCollector(socket!);
|
|
43
39
|
|
|
44
40
|
await renderAsync(
|
|
45
41
|
<Output>
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
</SourceDirectory>
|
|
52
|
-
</Show>
|
|
42
|
+
<SourceDirectory path="src">
|
|
43
|
+
<SourceFile path="index.ts" filetype="ts">
|
|
44
|
+
{"export const value = 1;"}
|
|
45
|
+
</SourceFile>
|
|
46
|
+
</SourceDirectory>
|
|
53
47
|
</Output>,
|
|
54
48
|
);
|
|
55
49
|
|
|
56
50
|
const initialMessages = await collector.waitForRender();
|
|
57
|
-
const initialFiles = initialMessages.filter(
|
|
58
|
-
m
|
|
51
|
+
const initialFiles = initialMessages.filter(
|
|
52
|
+
(m: DevtoolsMessage) =>
|
|
53
|
+
(m.type.startsWith("file:") || m.type.startsWith("directory:")) &&
|
|
54
|
+
!("triggerIds" in m),
|
|
59
55
|
);
|
|
60
56
|
expect(initialFiles[0]).toMatchObject({
|
|
57
|
+
type: "directory:added",
|
|
61
58
|
path: "./",
|
|
62
59
|
});
|
|
63
60
|
expect(initialFiles[1]).toMatchObject({
|
|
64
|
-
type: "
|
|
61
|
+
type: "directory:added",
|
|
65
62
|
path: "src",
|
|
66
63
|
});
|
|
67
64
|
expect(initialFiles[2]).toMatchObject({
|
|
68
|
-
type: "
|
|
65
|
+
type: "file:added",
|
|
69
66
|
path: "src/index.ts",
|
|
70
67
|
filetype: "ts",
|
|
71
68
|
});
|
|
72
69
|
expect(initialFiles[3]).toMatchObject({
|
|
73
|
-
type: "
|
|
70
|
+
type: "file:updated",
|
|
74
71
|
path: "src/index.ts",
|
|
75
|
-
|
|
76
|
-
contents: expect.any(String),
|
|
72
|
+
content: expect.any(String),
|
|
77
73
|
});
|
|
78
74
|
|
|
79
|
-
show.value = false;
|
|
80
|
-
await flushJobsAsync();
|
|
81
|
-
|
|
82
|
-
const updateMessages = await collector.waitForFlush();
|
|
83
|
-
const updateFiles = updateMessages.filter((m: DevtoolsMessage) =>
|
|
84
|
-
m.type.startsWith("files:"),
|
|
85
|
-
);
|
|
86
75
|
collector.stop();
|
|
87
|
-
|
|
88
|
-
expect(updateFiles[0]).toMatchObject({
|
|
89
|
-
type: "files:fileRemoved",
|
|
90
|
-
path: "src/index.ts",
|
|
91
|
-
});
|
|
92
|
-
expect(updateFiles[1]).toMatchObject({
|
|
93
|
-
type: "files:directoryRemoved",
|
|
94
|
-
path: "src",
|
|
95
|
-
});
|
|
96
76
|
});
|
package/src/debug/files.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
insertDirectory,
|
|
3
|
+
insertOutputFile,
|
|
4
|
+
updateOutputFileContent,
|
|
5
|
+
} from "./trace-writer.js";
|
|
6
|
+
import { isDebugEnabled } from "./trace.js";
|
|
2
7
|
|
|
3
8
|
export interface FileUpdateInfo {
|
|
4
9
|
path: string;
|
|
@@ -10,14 +15,14 @@ const fileContentCache = new Map<string, string>();
|
|
|
10
15
|
|
|
11
16
|
/** Record a directory being added to the output. */
|
|
12
17
|
export function recordDirectory(path: string) {
|
|
13
|
-
if (!
|
|
14
|
-
|
|
18
|
+
if (!isDebugEnabled()) return;
|
|
19
|
+
insertDirectory(path);
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/** Record a file being added to the output. */
|
|
18
23
|
export function recordFile(path: string, filetype: string) {
|
|
19
|
-
if (!
|
|
20
|
-
|
|
24
|
+
if (!isDebugEnabled()) return;
|
|
25
|
+
insertOutputFile(path, filetype, undefined);
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/** Notify devtools that a file's contents have changed. De-duplicates by content. */
|
|
@@ -26,12 +31,7 @@ export function updated(info: FileUpdateInfo) {
|
|
|
26
31
|
if (previous === info.contents) return;
|
|
27
32
|
fileContentCache.set(info.path, info.contents);
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
type: "files:fileUpdated",
|
|
31
|
-
path: info.path,
|
|
32
|
-
filetype: info.filetype,
|
|
33
|
-
contents: info.contents,
|
|
34
|
-
});
|
|
34
|
+
updateOutputFileContent(info.path, info.contents);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Clear all cached file state. Called when a new render begins. */
|
package/src/debug/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
debugWatch,
|
|
12
12
|
} from "./cli.js";
|
|
13
13
|
import {
|
|
14
|
+
ensureReactivePropertyRef,
|
|
14
15
|
ensureRef,
|
|
15
16
|
register,
|
|
16
17
|
registerRef,
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
} from "./symbols.js";
|
|
48
49
|
import { trace, type TracePhaseInfo } from "./trace.js";
|
|
49
50
|
|
|
51
|
+
export { isDevtoolsConnected } from "../devtools/devtools-server.js";
|
|
50
52
|
export { captureSourceLocation } from "./effects.js";
|
|
51
53
|
export type {
|
|
52
54
|
EffectDebugInfo,
|
|
@@ -62,6 +64,7 @@ export type {
|
|
|
62
64
|
} from "./render.js";
|
|
63
65
|
export {
|
|
64
66
|
isConsoleTraceEnabled,
|
|
67
|
+
isDebugEnabled,
|
|
65
68
|
isDevtoolsEnabled,
|
|
66
69
|
trace,
|
|
67
70
|
TracePhase,
|
|
@@ -84,6 +87,7 @@ export const debug = {
|
|
|
84
87
|
update,
|
|
85
88
|
registerRef,
|
|
86
89
|
ensureRef,
|
|
90
|
+
ensureReactivePropertyRef,
|
|
87
91
|
track,
|
|
88
92
|
trigger,
|
|
89
93
|
reset,
|