@fluixi/devtools 0.2.0-alpha.5 → 0.2.0-alpha.7
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/callsite.cjs +9 -0
- package/dist/callsite.d.ts.map +1 -1
- package/dist/callsite.js +9 -0
- package/dist/callsite.mjs +9 -0
- package/dist/export.cjs +1 -0
- package/dist/export.mjs +1 -0
- package/dist/graph-view.cjs +12 -3
- package/dist/graph-view.d.ts.map +1 -1
- package/dist/graph-view.js +11 -2
- package/dist/graph-view.mjs +12 -3
- package/dist/hook.cjs +173 -21
- package/dist/hook.mjs +174 -21
- package/dist/index.cjs +199 -29
- package/dist/index.mjs +200 -29
- package/dist/inspector.cjs +6 -2
- package/dist/inspector.js +1 -1
- package/dist/inspector.mjs +6 -2
- package/dist/instrument.cjs +9 -3
- package/dist/instrument.d.ts +16 -1
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.mjs +9 -3
- package/dist/kind-filter.cjs +5 -2
- package/dist/kind-filter.js +3 -3
- package/dist/kind-filter.mjs +5 -2
- package/dist/observe.cjs +172 -21
- package/dist/observe.d.ts.map +1 -1
- package/dist/observe.js +191 -22
- package/dist/observe.mjs +173 -21
- package/dist/palette.cjs +4 -1
- package/dist/palette.d.ts.map +1 -1
- package/dist/palette.js +3 -0
- package/dist/palette.mjs +4 -1
- package/dist/plugins/index.cjs +1 -1
- package/dist/plugins/index.mjs +1 -1
- package/dist/source.cjs +18 -3
- package/dist/source.d.ts +16 -1
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +25 -3
- package/dist/source.mjs +18 -3
- package/dist/tree.cjs +19 -4
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +21 -2
- package/dist/tree.mjs +19 -4
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/wire.cjs +3 -1
- package/dist/wire.d.ts +2 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +2 -0
- package/dist/wire.mjs +3 -1
- package/package.json +2 -2
package/dist/kind-filter.cjs
CHANGED
|
@@ -38,9 +38,12 @@ var FILTERABLE_KINDS = [
|
|
|
38
38
|
"router",
|
|
39
39
|
"handler",
|
|
40
40
|
"context",
|
|
41
|
-
"directive"
|
|
41
|
+
"directive",
|
|
42
|
+
"custom-element",
|
|
43
|
+
"module",
|
|
44
|
+
"service"
|
|
42
45
|
];
|
|
43
|
-
var LABEL = { handler: "route" };
|
|
46
|
+
var LABEL = { handler: "route", "custom-element": "custom element" };
|
|
44
47
|
function createKindFilter(host, options) {
|
|
45
48
|
const doc = host.ownerDocument;
|
|
46
49
|
const kinds = options.kinds ?? FILTERABLE_KINDS;
|
package/dist/kind-filter.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Every kind a graph can hold, in the order they are offered. */
|
|
2
2
|
export const FILTERABLE_KINDS = [
|
|
3
3
|
'state', 'memo', 'effect', 'store', 'resource', 'component', 'provider', 'control', 'router', 'handler',
|
|
4
|
-
'context', 'directive',
|
|
4
|
+
'context', 'directive', 'custom-element', 'module', 'service',
|
|
5
5
|
];
|
|
6
|
-
/**
|
|
7
|
-
const LABEL = { handler: 'route' };
|
|
6
|
+
/** Kinds whose name does not read as it is. */
|
|
7
|
+
const LABEL = { handler: 'route', 'custom-element': 'custom element' };
|
|
8
8
|
/**
|
|
9
9
|
* Builds the control inside `host`. Call `destroy` to remove it, it holds a document
|
|
10
10
|
* listener for closing the panel on an outside click.
|
package/dist/kind-filter.mjs
CHANGED
|
@@ -11,9 +11,12 @@ var FILTERABLE_KINDS = [
|
|
|
11
11
|
"router",
|
|
12
12
|
"handler",
|
|
13
13
|
"context",
|
|
14
|
-
"directive"
|
|
14
|
+
"directive",
|
|
15
|
+
"custom-element",
|
|
16
|
+
"module",
|
|
17
|
+
"service"
|
|
15
18
|
];
|
|
16
|
-
var LABEL = { handler: "route" };
|
|
19
|
+
var LABEL = { handler: "route", "custom-element": "custom element" };
|
|
17
20
|
function createKindFilter(host, options) {
|
|
18
21
|
const doc = host.ownerDocument;
|
|
19
22
|
const kinds = options.kinds ?? FILTERABLE_KINDS;
|
package/dist/observe.cjs
CHANGED
|
@@ -28,6 +28,7 @@ var import_signal2 = require("@fluixi/reactive/signal");
|
|
|
28
28
|
// src/source.ts
|
|
29
29
|
var MARKER = "__fx_source";
|
|
30
30
|
var COMPONENT_MARKER = "__fx_component";
|
|
31
|
+
var CLASS_MARKER = "__fx_class";
|
|
31
32
|
var HOLE_MARKER = "__fx_hole";
|
|
32
33
|
var PROPS_MARKER = "__fx_props";
|
|
33
34
|
var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
|
|
@@ -38,12 +39,16 @@ var shared = host[MARKS] ?? {
|
|
|
38
39
|
components: /* @__PURE__ */ new Map()
|
|
39
40
|
};
|
|
40
41
|
host[MARKS] = shared;
|
|
41
|
-
|
|
42
|
+
shared.classes ??= /* @__PURE__ */ new WeakMap();
|
|
43
|
+
function markReactiveSource(file, line, column, name, self) {
|
|
42
44
|
const location2 = locate(file, line, column);
|
|
43
45
|
if (shared.pending && location2.external && !shared.pending.location?.external) return;
|
|
44
|
-
shared.pending = { name, location: location2 };
|
|
46
|
+
shared.pending = self && typeof self === "object" ? { name, location: location2, self } : { name, location: location2 };
|
|
45
47
|
shared.expiring = false;
|
|
46
48
|
}
|
|
49
|
+
function dropReactiveSource() {
|
|
50
|
+
shared.pending = null;
|
|
51
|
+
}
|
|
47
52
|
function locate(file, line, column) {
|
|
48
53
|
const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
|
|
49
54
|
return external ? { file, line, column, external } : { file, line, column };
|
|
@@ -57,13 +62,19 @@ function markComponentSource(name, file, line, column) {
|
|
|
57
62
|
}
|
|
58
63
|
if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
|
|
59
64
|
}
|
|
65
|
+
function markClassSource(constructor, file, line, column) {
|
|
66
|
+
if (typeof constructor === "function") shared.classes.set(constructor, locate(file, line, column));
|
|
67
|
+
}
|
|
68
|
+
function classSource(constructor) {
|
|
69
|
+
return typeof constructor === "function" ? shared.classes.get(constructor) : void 0;
|
|
70
|
+
}
|
|
60
71
|
function componentSource(name) {
|
|
61
72
|
return shared.components.get(name) ?? void 0;
|
|
62
73
|
}
|
|
63
74
|
function takeReactiveSource() {
|
|
64
75
|
const mark = shared.pending;
|
|
65
76
|
if (mark) {
|
|
66
|
-
if (mark.name !== void 0) shared.pending = { location: mark.location };
|
|
77
|
+
if (mark.name !== void 0 || mark.self) shared.pending = { location: mark.location };
|
|
67
78
|
if (!shared.expiring) {
|
|
68
79
|
shared.expiring = true;
|
|
69
80
|
queueMicrotask(() => {
|
|
@@ -103,6 +114,7 @@ function holeLocation(accessor) {
|
|
|
103
114
|
function installSourceMarker() {
|
|
104
115
|
if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
|
|
105
116
|
if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
|
|
117
|
+
if (typeof host[CLASS_MARKER] !== "function") host[CLASS_MARKER] = markClassSource;
|
|
106
118
|
if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
|
|
107
119
|
if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
|
|
108
120
|
}
|
|
@@ -114,6 +126,15 @@ var INTERNAL = [
|
|
|
114
126
|
"/packages/dom/",
|
|
115
127
|
"/packages/core/",
|
|
116
128
|
"/packages/devtools/",
|
|
129
|
+
"/packages/web-components/",
|
|
130
|
+
"/packages/auth/",
|
|
131
|
+
"/packages/oauth2/",
|
|
132
|
+
"/packages/session/",
|
|
133
|
+
"/packages/router/",
|
|
134
|
+
"/packages/head/",
|
|
135
|
+
"/packages/start/",
|
|
136
|
+
"/packages/server/",
|
|
137
|
+
"/packages/template-runtime/",
|
|
117
138
|
"/@fluixi/",
|
|
118
139
|
"node_modules",
|
|
119
140
|
"fluixi-devtools",
|
|
@@ -664,7 +685,10 @@ var KIND_COLOR = {
|
|
|
664
685
|
binding: "#5eead4",
|
|
665
686
|
setter: "#fb7185",
|
|
666
687
|
group: "#8a90a2",
|
|
667
|
-
element: "#8a90a2"
|
|
688
|
+
element: "#8a90a2",
|
|
689
|
+
"custom-element": "#2dd4bf",
|
|
690
|
+
module: "#facc15",
|
|
691
|
+
service: "#60a5fa"
|
|
668
692
|
};
|
|
669
693
|
var kindVar = (kind) => `--fx-kind-${kind}`;
|
|
670
694
|
var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
|
|
@@ -681,7 +705,11 @@ var TREE_KINDS = /* @__PURE__ */ new Set([
|
|
|
681
705
|
"handler",
|
|
682
706
|
// A component a plugin recognised as providing a context. Still a tag the author wrote,
|
|
683
707
|
// so leaving it out took it and everything it rendered out of the tree.
|
|
684
|
-
"provider"
|
|
708
|
+
"provider",
|
|
709
|
+
// Scopes the runtime reported: what their signals and memos belong to.
|
|
710
|
+
"custom-element",
|
|
711
|
+
"module",
|
|
712
|
+
"service"
|
|
685
713
|
]);
|
|
686
714
|
var TREE_CSS = `
|
|
687
715
|
.fx-tree-row {
|
|
@@ -797,9 +825,14 @@ var PREFIX = {
|
|
|
797
825
|
// A plugin's, and it names them itself.
|
|
798
826
|
directive: "d",
|
|
799
827
|
context: "ctx",
|
|
800
|
-
provider: "p"
|
|
828
|
+
provider: "p",
|
|
829
|
+
// Scopes are always named by what opened them.
|
|
830
|
+
"custom-element": "el",
|
|
831
|
+
module: "mod",
|
|
832
|
+
service: "svc"
|
|
801
833
|
};
|
|
802
834
|
function readProps(props) {
|
|
835
|
+
if (readOnce.has(props)) return readOnce.get(props);
|
|
803
836
|
const at = propsLocations(props);
|
|
804
837
|
const out = [];
|
|
805
838
|
for (const name of Object.keys(props)) {
|
|
@@ -810,13 +843,25 @@ function readProps(props) {
|
|
|
810
843
|
...where ? { at: { line: where[0], column: where[1] } } : {}
|
|
811
844
|
};
|
|
812
845
|
try {
|
|
813
|
-
report.value = formatValue(
|
|
846
|
+
report.value = formatValue(
|
|
847
|
+
(0, import_signal2.createRoot)((dispose) => {
|
|
848
|
+
try {
|
|
849
|
+
return (0, import_signal2.untrack)(() => props[name]);
|
|
850
|
+
} finally {
|
|
851
|
+
dispose();
|
|
852
|
+
}
|
|
853
|
+
}),
|
|
854
|
+
200
|
|
855
|
+
);
|
|
814
856
|
} catch {
|
|
815
857
|
}
|
|
816
858
|
out.push(report);
|
|
817
859
|
}
|
|
818
|
-
|
|
860
|
+
const reports = out.length ? out : void 0;
|
|
861
|
+
readOnce.set(props, reports);
|
|
862
|
+
return reports;
|
|
819
863
|
}
|
|
864
|
+
var readOnce = /* @__PURE__ */ new WeakMap();
|
|
820
865
|
function observeGraph(options = {}) {
|
|
821
866
|
installSourceMarker();
|
|
822
867
|
const origin = options.origin ?? (typeof location === "object" ? location.origin : void 0);
|
|
@@ -837,6 +882,10 @@ function observeGraph(options = {}) {
|
|
|
837
882
|
const boundTo = /* @__PURE__ */ new WeakMap();
|
|
838
883
|
const drivenBy = /* @__PURE__ */ new WeakMap();
|
|
839
884
|
let reaches = /* @__PURE__ */ new Map();
|
|
885
|
+
const SCOPE_KINDS = /* @__PURE__ */ new Set(["custom-element", "module", "service"]);
|
|
886
|
+
const scopeIds = /* @__PURE__ */ new WeakMap();
|
|
887
|
+
const scopeOfTarget = /* @__PURE__ */ new WeakMap();
|
|
888
|
+
const fieldsOf = /* @__PURE__ */ new WeakMap();
|
|
840
889
|
let described = null;
|
|
841
890
|
const rendered = /* @__PURE__ */ new Map();
|
|
842
891
|
const elementsIn = (value) => {
|
|
@@ -933,7 +982,9 @@ function observeGraph(options = {}) {
|
|
|
933
982
|
// Mapped, unlike `mark`: these two came off a stack, so they are positions in the
|
|
934
983
|
// compiled file until a host translates them.
|
|
935
984
|
...site && !site.external ? { usedAt: mapped(site.location) } : {},
|
|
936
|
-
|
|
985
|
+
// Not inside a scope: an element's prop is made by the package for the element, and
|
|
986
|
+
// the package's line says less than the scope it sits in already does.
|
|
987
|
+
...site && site.external && !mark?.location && !report.scope ? { source: mapped(site.location) } : {}
|
|
937
988
|
};
|
|
938
989
|
const owner = ancestor(report.parents);
|
|
939
990
|
if (owner !== void 0) node.createdIn = owner;
|
|
@@ -943,6 +994,28 @@ function observeGraph(options = {}) {
|
|
|
943
994
|
node.createdIn = originId;
|
|
944
995
|
if (from.source) node.source = from.source;
|
|
945
996
|
}
|
|
997
|
+
const scopeId = report.scope ? scopeIds.get(report.scope) : void 0;
|
|
998
|
+
if (scopeId !== void 0) {
|
|
999
|
+
node.scope = scopeId;
|
|
1000
|
+
const creator = node.createdIn !== void 0 ? graph.get(node.createdIn) : void 0;
|
|
1001
|
+
const insideAlready = node.createdIn === scopeId || creator?.scope === scopeId;
|
|
1002
|
+
if (!insideAlready) node.createdIn = scopeId;
|
|
1003
|
+
} else if (node.createdIn !== void 0) {
|
|
1004
|
+
const creator = graph.get(node.createdIn);
|
|
1005
|
+
const inherited = creator && SCOPE_KINDS.has(creator.kind) ? creator.id : creator?.scope;
|
|
1006
|
+
if (inherited !== void 0) node.scope = inherited;
|
|
1007
|
+
}
|
|
1008
|
+
const self = mark?.self;
|
|
1009
|
+
if (self) {
|
|
1010
|
+
const list = fieldsOf.get(self);
|
|
1011
|
+
if (list) list.push(id);
|
|
1012
|
+
else fieldsOf.set(self, [id]);
|
|
1013
|
+
const open = scopeOfTarget.get(self);
|
|
1014
|
+
if (open !== void 0 && graph.has(open)) {
|
|
1015
|
+
node.scope = open;
|
|
1016
|
+
node.createdIn = open;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
946
1019
|
if (kind !== "effect" && typeof handle === "function") {
|
|
947
1020
|
const read = handle;
|
|
948
1021
|
node.read = () => (0, import_signal2.untrack)(read);
|
|
@@ -1078,20 +1151,90 @@ function observeGraph(options = {}) {
|
|
|
1078
1151
|
internals.delete(target);
|
|
1079
1152
|
const id = ids.get(target);
|
|
1080
1153
|
if (id === void 0) return;
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1154
|
+
forget(id);
|
|
1155
|
+
},
|
|
1156
|
+
scope(scope, info, parent) {
|
|
1157
|
+
dropReactiveSource();
|
|
1158
|
+
const id = seq++;
|
|
1159
|
+
const kind = info.kind === "element" ? "custom-element" : info.kind;
|
|
1160
|
+
const parentId = parent ? scopeIds.get(parent) : void 0;
|
|
1161
|
+
const found = currentCallSite(origin);
|
|
1162
|
+
const site = found && !/\0|__x00__|^virtual:/.test(found.location.file ?? "") ? found : void 0;
|
|
1163
|
+
const node = {
|
|
1164
|
+
id,
|
|
1165
|
+
label: info.name,
|
|
1166
|
+
kind,
|
|
1167
|
+
// The author named the element, the module and the service; none of them is plumbing.
|
|
1168
|
+
authored: true,
|
|
1169
|
+
deps: /* @__PURE__ */ new Set(),
|
|
1170
|
+
writes: /* @__PURE__ */ new Set(),
|
|
1171
|
+
runs: 0,
|
|
1172
|
+
lastPulse: 0,
|
|
1173
|
+
...info.detail ? { value: info.detail, detail: info.detail } : {},
|
|
1174
|
+
...parentId !== void 0 ? { scope: parentId, createdIn: parentId } : {}
|
|
1175
|
+
};
|
|
1176
|
+
const target = info.target;
|
|
1177
|
+
if (kind === "custom-element") {
|
|
1178
|
+
const defined = target ? classSource(target.constructor) : void 0;
|
|
1179
|
+
if (defined) node.declaredAt = defined;
|
|
1180
|
+
} else if (site && !site.external) {
|
|
1181
|
+
node.usedAt = mapped(site.location);
|
|
1182
|
+
}
|
|
1183
|
+
scopeIds.set(scope, id);
|
|
1184
|
+
if (target && typeof target === "object" && target.nodeType === 1) {
|
|
1185
|
+
rendered.set(id, [new WeakRef(target)]);
|
|
1186
|
+
}
|
|
1187
|
+
graph.set(id, node);
|
|
1188
|
+
history.created(id, node.createdIn, node.label);
|
|
1189
|
+
if (target && typeof target === "object") {
|
|
1190
|
+
scopeOfTarget.set(target, id);
|
|
1191
|
+
for (const field of fieldsOf.get(target) ?? []) {
|
|
1192
|
+
const made = graph.get(field);
|
|
1193
|
+
if (!made) continue;
|
|
1194
|
+
made.scope = id;
|
|
1195
|
+
made.createdIn = id;
|
|
1196
|
+
revs.set(field, ++revision);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
scopeEnded(scope) {
|
|
1201
|
+
const id = scopeIds.get(scope);
|
|
1202
|
+
if (id === void 0) return;
|
|
1203
|
+
const ended = /* @__PURE__ */ new Set([id]);
|
|
1204
|
+
for (let grew = true; grew; ) {
|
|
1205
|
+
grew = false;
|
|
1206
|
+
for (const [other, node] of graph) {
|
|
1207
|
+
if (!ended.has(other) && node.scope !== void 0 && ended.has(node.scope)) {
|
|
1208
|
+
ended.add(other);
|
|
1209
|
+
grew = true;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
for (const gone of ended) forget(gone);
|
|
1214
|
+
},
|
|
1215
|
+
scopeAdopted(scope, parent) {
|
|
1216
|
+
const node = graph.get(scopeIds.get(scope) ?? -1);
|
|
1217
|
+
const parentId = scopeIds.get(parent);
|
|
1218
|
+
if (!node || parentId === void 0 || parentId === node.id) return;
|
|
1219
|
+
node.scope = parentId;
|
|
1220
|
+
node.createdIn = parentId;
|
|
1221
|
+
revs.set(node.id, ++revision);
|
|
1093
1222
|
}
|
|
1094
1223
|
});
|
|
1224
|
+
function forget(id) {
|
|
1225
|
+
removed.push({ id, rev: ++revision });
|
|
1226
|
+
revs.delete(id);
|
|
1227
|
+
if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
|
|
1228
|
+
history.disposed(id, graph.get(id)?.label);
|
|
1229
|
+
graph.delete(id);
|
|
1230
|
+
handed.delete(id);
|
|
1231
|
+
raws.delete(id);
|
|
1232
|
+
cores.delete(id);
|
|
1233
|
+
rendered.delete(id);
|
|
1234
|
+
setters.delete(id);
|
|
1235
|
+
runsAtLastSample.delete(id);
|
|
1236
|
+
unnamed.delete(id);
|
|
1237
|
+
}
|
|
1095
1238
|
const edges = (id, node) => {
|
|
1096
1239
|
const sources = cores.get(id)?.sources;
|
|
1097
1240
|
node.deps.clear();
|
|
@@ -1111,6 +1254,14 @@ function observeGraph(options = {}) {
|
|
|
1111
1254
|
revs.set(id, ++revision);
|
|
1112
1255
|
};
|
|
1113
1256
|
const now = Date.now();
|
|
1257
|
+
for (const [id, node] of graph) {
|
|
1258
|
+
if (node.kind !== "custom-element" || node.usedAt) continue;
|
|
1259
|
+
const element = rendered.get(id)?.[0]?.deref();
|
|
1260
|
+
const written = element ? element.__fx_loc : void 0;
|
|
1261
|
+
if (!written?.file) continue;
|
|
1262
|
+
node.usedAt = { file: written.file, line: written.line, column: written.column };
|
|
1263
|
+
revs.set(id, ++revision);
|
|
1264
|
+
}
|
|
1114
1265
|
const bindings = /* @__PURE__ */ new Map();
|
|
1115
1266
|
const drives = /* @__PURE__ */ new Map();
|
|
1116
1267
|
const seen = /* @__PURE__ */ new Map();
|
package/dist/observe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAUA,OAAO,
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../src/observe.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAmE,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAG9D,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAmDhE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnC,gFAAgF;IAChF,MAAM,IAAI,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACpC,6FAA6F;IAC7F,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACpC,4EAA4E;IAC5E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,8EAA8E;IAC9E,UAAU,IAAI,UAAU,EAAE,CAAC;IAC3B,qEAAqE;IACrE,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;IACnC,wEAAwE;IACxE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7C,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IACrD,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,sBAAsB,GAAG,SAAS,CAAC;CACtF;AAgGD,wBAAgB,YAAY,CAAC,OAAO,GAAE,cAAmB,GAAG,aAAa,CAk2BxE"}
|
package/dist/observe.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// The graph it fills is the same Map<number, RNode> createGraphView already draws, so a
|
|
10
10
|
// renderer does not care which of the two produced it.
|
|
11
|
-
import { observeReactiveNodes, untrack, } from '@fluixi/reactive/signal';
|
|
12
|
-
import { componentSource, holeLocation, installSourceMarker, propsLocations, takeReactiveSource } from './source.js';
|
|
11
|
+
import { createRoot, observeReactiveNodes, untrack, } from '@fluixi/reactive/signal';
|
|
12
|
+
import { classSource, componentSource, dropReactiveSource, holeLocation, installSourceMarker, propsLocations, takeReactiveSource } from './source.js';
|
|
13
13
|
import { currentCallSite } from './callsite.js';
|
|
14
14
|
import { KIND_OF_BUILTIN } from './instrument.js';
|
|
15
15
|
import { formatValue, shorten } from './format.js';
|
|
@@ -81,20 +81,33 @@ const PREFIX = {
|
|
|
81
81
|
directive: 'd',
|
|
82
82
|
context: 'ctx',
|
|
83
83
|
provider: 'p',
|
|
84
|
+
// Scopes are always named by what opened them.
|
|
85
|
+
'custom-element': 'el',
|
|
86
|
+
module: 'mod',
|
|
87
|
+
service: 'svc',
|
|
84
88
|
};
|
|
85
89
|
/**
|
|
86
90
|
* Start watching. Nodes made before this returns are not in the graph, so attach before the
|
|
87
91
|
* app's modules load, which in practice means before the entry import.
|
|
88
92
|
*/
|
|
89
93
|
/**
|
|
90
|
-
* What a component was handed, read for display.
|
|
94
|
+
* What a component was handed, read for display, once per props object.
|
|
91
95
|
*
|
|
92
96
|
* Every prop is a getter over whatever the caller wrote, so reading one runs that
|
|
93
97
|
* expression. Untracked, or the read would subscribe whatever is sampling to it and the
|
|
94
|
-
* panel would drive the graph by looking at it.
|
|
95
|
-
*
|
|
98
|
+
* panel would drive the graph by looking at it. And only once: JSX in a prop compiles to a
|
|
99
|
+
* getter that makes a new memo on each read, which the next sample pulls, building the
|
|
100
|
+
* component again, and any other prop can reach such a getter (`when={local.trailing != null}`).
|
|
101
|
+
* Read on every sample, each one added another copy of those components to the graph.
|
|
102
|
+
* `children` is skipped: reading it builds the subtree.
|
|
103
|
+
*
|
|
104
|
+
* Any other getter can build one too (`Show`'s `fallback`), and what it builds is live: its
|
|
105
|
+
* effects run outside the component, so a context it reads is missing, and they keep running
|
|
106
|
+
* and throwing on every change. Each read happens in a root disposed as soon as it returns.
|
|
96
107
|
*/
|
|
97
108
|
function readProps(props) {
|
|
109
|
+
if (readOnce.has(props))
|
|
110
|
+
return readOnce.get(props);
|
|
98
111
|
const at = propsLocations(props);
|
|
99
112
|
const out = [];
|
|
100
113
|
for (const name of Object.keys(props)) {
|
|
@@ -106,15 +119,26 @@ function readProps(props) {
|
|
|
106
119
|
...(where ? { at: { line: where[0], column: where[1] } } : {}),
|
|
107
120
|
};
|
|
108
121
|
try {
|
|
109
|
-
report.value = formatValue(
|
|
122
|
+
report.value = formatValue(createRoot((dispose) => {
|
|
123
|
+
try {
|
|
124
|
+
return untrack(() => props[name]);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
dispose();
|
|
128
|
+
}
|
|
129
|
+
}), 200);
|
|
110
130
|
}
|
|
111
131
|
catch {
|
|
112
132
|
// A getter that throws is still a prop worth naming.
|
|
113
133
|
}
|
|
114
134
|
out.push(report);
|
|
115
135
|
}
|
|
116
|
-
|
|
136
|
+
const reports = out.length ? out : undefined;
|
|
137
|
+
readOnce.set(props, reports);
|
|
138
|
+
return reports;
|
|
117
139
|
}
|
|
140
|
+
/** Each props object's reports, from its first read. */
|
|
141
|
+
const readOnce = new WeakMap();
|
|
118
142
|
export function observeGraph(options = {}) {
|
|
119
143
|
installSourceMarker();
|
|
120
144
|
const origin = options.origin ?? (typeof location === 'object' ? location.origin : undefined);
|
|
@@ -175,6 +199,14 @@ export function observeGraph(options = {}) {
|
|
|
175
199
|
const drivenBy = new WeakMap();
|
|
176
200
|
/** Per node, the elements it drives. Rebuilt each sample, like the descriptions are. */
|
|
177
201
|
let reaches = new Map();
|
|
202
|
+
/** The kinds a scope node can be. */
|
|
203
|
+
const SCOPE_KINDS = new Set(['custom-element', 'module', 'service']);
|
|
204
|
+
/** Each open scope's node, by the identity the runtime handed over. */
|
|
205
|
+
const scopeIds = new WeakMap();
|
|
206
|
+
/** The open scope standing for an object, an element, by that object. */
|
|
207
|
+
const scopeOfTarget = new WeakMap();
|
|
208
|
+
/** Nodes a class field made, by the instance: they join its scope when it opens. */
|
|
209
|
+
const fieldsOf = new WeakMap();
|
|
178
210
|
/** Cleared on each sample: the page has moved on. */
|
|
179
211
|
let described = null;
|
|
180
212
|
/**
|
|
@@ -343,7 +375,9 @@ export function observeGraph(options = {}) {
|
|
|
343
375
|
// Mapped, unlike `mark`: these two came off a stack, so they are positions in the
|
|
344
376
|
// compiled file until a host translates them.
|
|
345
377
|
...(site && !site.external ? { usedAt: mapped(site.location) } : {}),
|
|
346
|
-
|
|
378
|
+
// Not inside a scope: an element's prop is made by the package for the element, and
|
|
379
|
+
// the package's line says less than the scope it sits in already does.
|
|
380
|
+
...(site && site.external && !mark?.location && !report.scope ? { source: mapped(site.location) } : {}),
|
|
347
381
|
};
|
|
348
382
|
const owner = ancestor(report.parents);
|
|
349
383
|
if (owner !== undefined)
|
|
@@ -357,6 +391,39 @@ export function observeGraph(options = {}) {
|
|
|
357
391
|
if (from.source)
|
|
358
392
|
node.source = from.source;
|
|
359
393
|
}
|
|
394
|
+
// Made inside an element, module or service: it belongs to that scope, and hangs off it
|
|
395
|
+
// unless what created it already sits inside the same scope, which keeps the nesting.
|
|
396
|
+
const scopeId = report.scope ? scopeIds.get(report.scope) : undefined;
|
|
397
|
+
if (scopeId !== undefined) {
|
|
398
|
+
node.scope = scopeId;
|
|
399
|
+
const creator = node.createdIn !== undefined ? graph.get(node.createdIn) : undefined;
|
|
400
|
+
const insideAlready = node.createdIn === scopeId || creator?.scope === scopeId;
|
|
401
|
+
if (!insideAlready)
|
|
402
|
+
node.createdIn = scopeId;
|
|
403
|
+
}
|
|
404
|
+
else if (node.createdIn !== undefined) {
|
|
405
|
+
// Made after the scope closed, by something inside it: a memo read lazily, an effect
|
|
406
|
+
// re-running, a row a list adds. It belongs where its creator belongs.
|
|
407
|
+
const creator = graph.get(node.createdIn);
|
|
408
|
+
const inherited = creator && SCOPE_KINDS.has(creator.kind) ? creator.id : creator?.scope;
|
|
409
|
+
if (inherited !== undefined)
|
|
410
|
+
node.scope = inherited;
|
|
411
|
+
}
|
|
412
|
+
// A class field: made in the constructor, before the element opened its scope. It belongs
|
|
413
|
+
// to the instance, so it goes in that scope now if it is open, or when it opens.
|
|
414
|
+
const self = mark?.self;
|
|
415
|
+
if (self) {
|
|
416
|
+
const list = fieldsOf.get(self);
|
|
417
|
+
if (list)
|
|
418
|
+
list.push(id);
|
|
419
|
+
else
|
|
420
|
+
fieldsOf.set(self, [id]);
|
|
421
|
+
const open = scopeOfTarget.get(self);
|
|
422
|
+
if (open !== undefined && graph.has(open)) {
|
|
423
|
+
node.scope = open;
|
|
424
|
+
node.createdIn = open;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
360
427
|
// An effect returns nothing worth showing, so it has no reader and pulses on its runs.
|
|
361
428
|
if (kind !== 'effect' && typeof handle === 'function') {
|
|
362
429
|
// Not read here: a memo is lazy, and forcing it from inside its own creation would
|
|
@@ -537,22 +604,110 @@ export function observeGraph(options = {}) {
|
|
|
537
604
|
const id = ids.get(target);
|
|
538
605
|
if (id === undefined)
|
|
539
606
|
return;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
607
|
+
forget(id);
|
|
608
|
+
},
|
|
609
|
+
scope(scope, info, parent) {
|
|
610
|
+
// Whatever mark is pending was left by the code around this scope, not by what it makes.
|
|
611
|
+
dropReactiveSource();
|
|
612
|
+
const id = seq++;
|
|
613
|
+
const kind = info.kind === 'element' ? 'custom-element' : info.kind;
|
|
614
|
+
const parentId = parent ? scopeIds.get(parent) : undefined;
|
|
615
|
+
// Where it was opened: the line that defined the module, built the service, or, for
|
|
616
|
+
// an element, the one that put it on the page. A bundler's virtual module (the
|
|
617
|
+
// generated app config, `\0virtual:…`) is nowhere a reader can go, so it is left out.
|
|
618
|
+
const found = currentCallSite(origin);
|
|
619
|
+
const site = found && !/\0|__x00__|^virtual:/.test(found.location.file ?? '') ? found : undefined;
|
|
620
|
+
const node = {
|
|
621
|
+
id,
|
|
622
|
+
label: info.name,
|
|
623
|
+
kind,
|
|
624
|
+
// The author named the element, the module and the service; none of them is plumbing.
|
|
625
|
+
authored: true,
|
|
626
|
+
deps: new Set(),
|
|
627
|
+
writes: new Set(),
|
|
628
|
+
runs: 0,
|
|
629
|
+
lastPulse: 0,
|
|
630
|
+
...(info.detail ? { value: info.detail, detail: info.detail } : {}),
|
|
631
|
+
...(parentId !== undefined ? { scope: parentId, createdIn: parentId } : {}),
|
|
632
|
+
};
|
|
633
|
+
const target = info.target;
|
|
634
|
+
if (kind === 'custom-element') {
|
|
635
|
+
// The stack here is whatever connected it, often the `define` that upgraded it, so it
|
|
636
|
+
// says nothing about where the tag is written: that comes off the element at sample.
|
|
637
|
+
// Where it is defined is the class declaration, which the compiler marked.
|
|
638
|
+
const defined = target ? classSource(target.constructor) : undefined;
|
|
639
|
+
if (defined)
|
|
640
|
+
node.declaredAt = defined;
|
|
641
|
+
}
|
|
642
|
+
else if (site && !site.external) {
|
|
643
|
+
// Only the app's own line: a package's is where the package opened the scope for it,
|
|
644
|
+
// which says nothing about this module or service.
|
|
645
|
+
node.usedAt = mapped(site.location);
|
|
646
|
+
}
|
|
647
|
+
scopeIds.set(scope, id);
|
|
648
|
+
if (target && typeof target === 'object' && target.nodeType === 1) {
|
|
649
|
+
rendered.set(id, [new WeakRef(target)]);
|
|
650
|
+
}
|
|
651
|
+
graph.set(id, node);
|
|
652
|
+
history.created(id, node.createdIn, node.label);
|
|
653
|
+
if (target && typeof target === 'object') {
|
|
654
|
+
scopeOfTarget.set(target, id);
|
|
655
|
+
for (const field of fieldsOf.get(target) ?? []) {
|
|
656
|
+
const made = graph.get(field);
|
|
657
|
+
if (!made)
|
|
658
|
+
continue;
|
|
659
|
+
made.scope = id;
|
|
660
|
+
made.createdIn = id;
|
|
661
|
+
revs.set(field, ++revision);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
scopeEnded(scope) {
|
|
666
|
+
const id = scopeIds.get(scope);
|
|
667
|
+
if (id === undefined)
|
|
668
|
+
return;
|
|
669
|
+
// What it made goes with it. An element's prop signals have no owner to dispose them,
|
|
670
|
+
// so they outlived the element as cards pointing at a scope that no longer existed.
|
|
671
|
+
const ended = new Set([id]);
|
|
672
|
+
for (let grew = true; grew;) {
|
|
673
|
+
grew = false;
|
|
674
|
+
for (const [other, node] of graph) {
|
|
675
|
+
if (!ended.has(other) && node.scope !== undefined && ended.has(node.scope)) {
|
|
676
|
+
ended.add(other);
|
|
677
|
+
grew = true;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
for (const gone of ended)
|
|
682
|
+
forget(gone);
|
|
683
|
+
},
|
|
684
|
+
scopeAdopted(scope, parent) {
|
|
685
|
+
const node = graph.get(scopeIds.get(scope) ?? -1);
|
|
686
|
+
const parentId = scopeIds.get(parent);
|
|
687
|
+
if (!node || parentId === undefined || parentId === node.id)
|
|
688
|
+
return;
|
|
689
|
+
node.scope = parentId;
|
|
690
|
+
node.createdIn = parentId;
|
|
691
|
+
revs.set(node.id, ++revision);
|
|
554
692
|
},
|
|
555
693
|
});
|
|
694
|
+
/** Drop a node everywhere it is kept, and log it for a consumer catching up. */
|
|
695
|
+
function forget(id) {
|
|
696
|
+
removed.push({ id, rev: ++revision });
|
|
697
|
+
revs.delete(id);
|
|
698
|
+
if (removed.length > REMOVED_LOG)
|
|
699
|
+
removed.splice(0, removed.length - REMOVED_LOG);
|
|
700
|
+
// The label goes with it: nothing is left to look the name up in.
|
|
701
|
+
history.disposed(id, graph.get(id)?.label);
|
|
702
|
+
graph.delete(id);
|
|
703
|
+
handed.delete(id);
|
|
704
|
+
raws.delete(id);
|
|
705
|
+
cores.delete(id);
|
|
706
|
+
rendered.delete(id);
|
|
707
|
+
setters.delete(id);
|
|
708
|
+
runsAtLastSample.delete(id);
|
|
709
|
+
unnamed.delete(id);
|
|
710
|
+
}
|
|
556
711
|
/** What this node read on its last run, as ids: the edges the core already holds. */
|
|
557
712
|
const edges = (id, node) => {
|
|
558
713
|
const sources = cores.get(id)?.sources;
|
|
@@ -576,6 +731,20 @@ export function observeGraph(options = {}) {
|
|
|
576
731
|
revs.set(id, ++revision);
|
|
577
732
|
};
|
|
578
733
|
const now = Date.now();
|
|
734
|
+
// Where each custom element's tag is written. Stamped by the runtime when the template
|
|
735
|
+
// makes or hydrates it, which can be after its scope opened: an element the server
|
|
736
|
+
// rendered upgrades on import, before hydration gets to it.
|
|
737
|
+
for (const [id, node] of graph) {
|
|
738
|
+
if (node.kind !== 'custom-element' || node.usedAt)
|
|
739
|
+
continue;
|
|
740
|
+
const element = rendered.get(id)?.[0]?.deref();
|
|
741
|
+
const written = element ? element.__fx_loc : undefined;
|
|
742
|
+
if (!written?.file)
|
|
743
|
+
continue;
|
|
744
|
+
node.usedAt = { file: written.file, line: written.line, column: written.column };
|
|
745
|
+
// A new revision without counting as a change: nothing ran, so nothing should pulse.
|
|
746
|
+
revs.set(id, ++revision);
|
|
747
|
+
}
|
|
579
748
|
// What the dom reads, per node. Recounted rather than kept: a binding's sources change
|
|
580
749
|
// with every run, and a stale count is worse than none.
|
|
581
750
|
const bindings = new Map();
|