@react-perfscope/core 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +105 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -8
- package/dist/index.d.ts +0 -8
- package/dist/index.js +105 -47
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -78,8 +78,14 @@ import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
|
78
78
|
|
|
79
79
|
// src/self-requests.ts
|
|
80
80
|
var selfRequests = /* @__PURE__ */ new Set();
|
|
81
|
+
var MAX_SELF_REQUESTS = 2e3;
|
|
81
82
|
function markSelfRequest(url) {
|
|
83
|
+
selfRequests.delete(url);
|
|
82
84
|
selfRequests.add(url);
|
|
85
|
+
if (selfRequests.size > MAX_SELF_REQUESTS) {
|
|
86
|
+
const oldest = selfRequests.values().next().value;
|
|
87
|
+
if (oldest !== void 0) selfRequests.delete(oldest);
|
|
88
|
+
}
|
|
83
89
|
}
|
|
84
90
|
function isSelfRequest(url) {
|
|
85
91
|
return selfRequests.has(url);
|
|
@@ -88,23 +94,25 @@ function isSelfRequest(url) {
|
|
|
88
94
|
// src/sourcemap.ts
|
|
89
95
|
var CHROME_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
|
|
90
96
|
var FIREFOX_FRAME = /^(.*?)@(.+?):(\d+):(\d+)$/;
|
|
97
|
+
function lookupFrame(tracer, frame) {
|
|
98
|
+
const pos = originalPositionFor(tracer, { line: frame.line, column: frame.col - 1 });
|
|
99
|
+
if (pos.source == null || pos.line == null || pos.column == null) {
|
|
100
|
+
return frame;
|
|
101
|
+
}
|
|
102
|
+
const resolved = {
|
|
103
|
+
file: pos.source,
|
|
104
|
+
line: pos.line,
|
|
105
|
+
col: pos.column + 1
|
|
106
|
+
};
|
|
107
|
+
if (pos.name) resolved.fnName = pos.name;
|
|
108
|
+
else if (frame.fnName) resolved.fnName = frame.fnName;
|
|
109
|
+
return resolved;
|
|
110
|
+
}
|
|
91
111
|
async function resolveFrame(frame, fetchMap) {
|
|
92
112
|
try {
|
|
93
113
|
const map = await fetchMap(frame.file);
|
|
94
114
|
if (!map) return frame;
|
|
95
|
-
|
|
96
|
-
const pos = originalPositionFor(tracer, { line: frame.line, column: frame.col });
|
|
97
|
-
if (pos.source == null || pos.line == null || pos.column == null) {
|
|
98
|
-
return frame;
|
|
99
|
-
}
|
|
100
|
-
const resolved = {
|
|
101
|
-
file: pos.source,
|
|
102
|
-
line: pos.line,
|
|
103
|
-
col: pos.column
|
|
104
|
-
};
|
|
105
|
-
if (pos.name) resolved.fnName = pos.name;
|
|
106
|
-
else if (frame.fnName) resolved.fnName = frame.fnName;
|
|
107
|
-
return resolved;
|
|
115
|
+
return lookupFrame(new TraceMap(map), frame);
|
|
108
116
|
} catch (err) {
|
|
109
117
|
console.warn("[react-perfscope] resolveFrame failed:", err);
|
|
110
118
|
return frame;
|
|
@@ -124,13 +132,25 @@ function attachLazyStack(target, raw, skipTopFrames = 0) {
|
|
|
124
132
|
}
|
|
125
133
|
});
|
|
126
134
|
}
|
|
135
|
+
var MAX_CACHED_MAPS = 50;
|
|
136
|
+
function sourceCacheKey(url) {
|
|
137
|
+
const q = url.indexOf("?");
|
|
138
|
+
const h = url.indexOf("#");
|
|
139
|
+
const end = Math.min(q === -1 ? url.length : q, h === -1 ? url.length : h);
|
|
140
|
+
return url.slice(0, end);
|
|
141
|
+
}
|
|
127
142
|
function createSourceMapResolver(opts = {}) {
|
|
128
143
|
const f = opts.fetch ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
|
|
129
144
|
const cache = /* @__PURE__ */ new Map();
|
|
130
|
-
function
|
|
145
|
+
function fetchTracerFor(sourceUrl) {
|
|
131
146
|
if (!f) return Promise.resolve(null);
|
|
132
|
-
const
|
|
133
|
-
|
|
147
|
+
const key = sourceCacheKey(sourceUrl);
|
|
148
|
+
const existing = cache.get(key);
|
|
149
|
+
if (existing) {
|
|
150
|
+
cache.delete(key);
|
|
151
|
+
cache.set(key, existing);
|
|
152
|
+
return existing;
|
|
153
|
+
}
|
|
134
154
|
const promise = (async () => {
|
|
135
155
|
try {
|
|
136
156
|
markSelfRequest(sourceUrl);
|
|
@@ -146,24 +166,31 @@ function createSourceMapResolver(opts = {}) {
|
|
|
146
166
|
const header = ref.slice(0, commaIdx);
|
|
147
167
|
const payload = ref.slice(commaIdx + 1);
|
|
148
168
|
const decoded = header.includes("base64") ? typeof atob === "function" ? atob(payload) : globalThis.Buffer.from(payload, "base64").toString("utf8") : decodeURIComponent(payload);
|
|
149
|
-
return JSON.parse(decoded);
|
|
169
|
+
return new TraceMap(JSON.parse(decoded));
|
|
150
170
|
}
|
|
151
171
|
const mapUrl = new URL(ref, sourceUrl).href;
|
|
152
172
|
markSelfRequest(mapUrl);
|
|
153
173
|
const mapRes = await f(mapUrl);
|
|
154
174
|
if (!mapRes || !mapRes.ok) return null;
|
|
155
|
-
return await mapRes.json();
|
|
175
|
+
return new TraceMap(await mapRes.json());
|
|
156
176
|
} catch {
|
|
177
|
+
cache.delete(key);
|
|
157
178
|
return null;
|
|
158
179
|
}
|
|
159
180
|
})();
|
|
160
|
-
cache.set(
|
|
181
|
+
cache.set(key, promise);
|
|
182
|
+
if (cache.size > MAX_CACHED_MAPS) {
|
|
183
|
+
const oldest = cache.keys().next().value;
|
|
184
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
185
|
+
}
|
|
161
186
|
return promise;
|
|
162
187
|
}
|
|
163
188
|
return {
|
|
164
189
|
async resolve(frame) {
|
|
165
190
|
try {
|
|
166
|
-
|
|
191
|
+
const tracer = await fetchTracerFor(frame.file);
|
|
192
|
+
if (!tracer) return frame;
|
|
193
|
+
return lookupFrame(tracer, frame);
|
|
167
194
|
} catch {
|
|
168
195
|
return frame;
|
|
169
196
|
}
|
|
@@ -354,6 +381,7 @@ function createLongTasksCollector() {
|
|
|
354
381
|
return {
|
|
355
382
|
kind: "long-task",
|
|
356
383
|
activate(emit) {
|
|
384
|
+
if (active) return;
|
|
357
385
|
if (typeof PerformanceObserver === "undefined") {
|
|
358
386
|
console.warn("[react-perfscope] PerformanceObserver not supported; long-tasks disabled");
|
|
359
387
|
return;
|
|
@@ -405,6 +433,20 @@ function createLongTasksCollector() {
|
|
|
405
433
|
};
|
|
406
434
|
}
|
|
407
435
|
|
|
436
|
+
// src/self-measurement.ts
|
|
437
|
+
var depth = 0;
|
|
438
|
+
function withSelfMeasurement(fn) {
|
|
439
|
+
depth++;
|
|
440
|
+
try {
|
|
441
|
+
return fn();
|
|
442
|
+
} finally {
|
|
443
|
+
depth--;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function inSelfMeasurement() {
|
|
447
|
+
return depth > 0;
|
|
448
|
+
}
|
|
449
|
+
|
|
408
450
|
// src/collectors/forced-reflow.ts
|
|
409
451
|
var LAYOUT_GETTERS = [
|
|
410
452
|
"offsetWidth",
|
|
@@ -467,7 +509,7 @@ function createForcedReflowCollector() {
|
|
|
467
509
|
Object.defineProperty(proto, key, {
|
|
468
510
|
configurable: true,
|
|
469
511
|
get() {
|
|
470
|
-
if (active) {
|
|
512
|
+
if (active && !inSelfMeasurement()) {
|
|
471
513
|
if (!consumePendingMutations()) {
|
|
472
514
|
return originalGet.call(this);
|
|
473
515
|
}
|
|
@@ -492,7 +534,7 @@ function createForcedReflowCollector() {
|
|
|
492
534
|
configurable: true,
|
|
493
535
|
writable: true,
|
|
494
536
|
value: function patchedLayoutMethod(...args) {
|
|
495
|
-
if (active) {
|
|
537
|
+
if (active && !inSelfMeasurement()) {
|
|
496
538
|
if (!consumePendingMutations()) {
|
|
497
539
|
return original.apply(this, args);
|
|
498
540
|
}
|
|
@@ -582,7 +624,7 @@ function collectPerfscopeRects() {
|
|
|
582
624
|
function entryIsOnlyFromPerfscope(sources) {
|
|
583
625
|
if (sources.length === 0) return false;
|
|
584
626
|
if (typeof document === "undefined") return false;
|
|
585
|
-
const hostRects = collectPerfscopeRects
|
|
627
|
+
const hostRects = withSelfMeasurement(collectPerfscopeRects);
|
|
586
628
|
if (hostRects.length === 0) return false;
|
|
587
629
|
for (const src of sources) {
|
|
588
630
|
let matched = false;
|
|
@@ -619,6 +661,7 @@ function createLayoutShiftCollector() {
|
|
|
619
661
|
return {
|
|
620
662
|
kind: "layout-shift",
|
|
621
663
|
activate(emit) {
|
|
664
|
+
if (active) return;
|
|
622
665
|
if (typeof PerformanceObserver === "undefined") {
|
|
623
666
|
console.warn("[react-perfscope] PerformanceObserver not supported; layout-shift disabled");
|
|
624
667
|
return;
|
|
@@ -677,6 +720,7 @@ function createNetworkCollector() {
|
|
|
677
720
|
return {
|
|
678
721
|
kind: "network",
|
|
679
722
|
activate(emit) {
|
|
723
|
+
if (active) return;
|
|
680
724
|
if (typeof PerformanceObserver === "undefined") {
|
|
681
725
|
console.warn("[react-perfscope] PerformanceObserver not supported; network disabled");
|
|
682
726
|
return;
|
|
@@ -720,37 +764,40 @@ function createNetworkCollector() {
|
|
|
720
764
|
|
|
721
765
|
// src/collectors/web-vitals.ts
|
|
722
766
|
import { onLCP, onINP, onCLS, onFCP, onTTFB } from "web-vitals";
|
|
767
|
+
var subscribed = false;
|
|
768
|
+
var sinks = /* @__PURE__ */ new Set();
|
|
769
|
+
function ensureSubscribed() {
|
|
770
|
+
if (subscribed) return true;
|
|
771
|
+
try {
|
|
772
|
+
const fan = (name) => (metric) => {
|
|
773
|
+
for (const sink of sinks) sink(name, metric.value);
|
|
774
|
+
};
|
|
775
|
+
onLCP(fan("LCP"));
|
|
776
|
+
onINP(fan("INP"));
|
|
777
|
+
onCLS(fan("CLS"));
|
|
778
|
+
onFCP(fan("FCP"));
|
|
779
|
+
onTTFB(fan("TTFB"));
|
|
780
|
+
subscribed = true;
|
|
781
|
+
} catch (err) {
|
|
782
|
+
console.warn("[react-perfscope] web-vitals collector failed to subscribe:", err);
|
|
783
|
+
}
|
|
784
|
+
return subscribed;
|
|
785
|
+
}
|
|
723
786
|
function createWebVitalsCollector() {
|
|
724
|
-
let active = false;
|
|
725
|
-
let subscribed = false;
|
|
726
787
|
let emit = () => {
|
|
727
788
|
};
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
emit({ kind: "web-vital", name, value: metric.value });
|
|
732
|
-
};
|
|
733
|
-
}
|
|
789
|
+
const sink = (name, value) => {
|
|
790
|
+
emit({ kind: "web-vital", name, value });
|
|
791
|
+
};
|
|
734
792
|
return {
|
|
735
793
|
kind: "web-vital",
|
|
736
794
|
activate(emitFn) {
|
|
795
|
+
if (!ensureSubscribed()) return;
|
|
737
796
|
emit = emitFn;
|
|
738
|
-
|
|
739
|
-
if (subscribed) return;
|
|
740
|
-
try {
|
|
741
|
-
onLCP(makeHandler("LCP"));
|
|
742
|
-
onINP(makeHandler("INP"));
|
|
743
|
-
onCLS(makeHandler("CLS"));
|
|
744
|
-
onFCP(makeHandler("FCP"));
|
|
745
|
-
onTTFB(makeHandler("TTFB"));
|
|
746
|
-
subscribed = true;
|
|
747
|
-
} catch (err) {
|
|
748
|
-
console.warn("[react-perfscope] web-vitals collector failed to subscribe:", err);
|
|
749
|
-
active = false;
|
|
750
|
-
}
|
|
797
|
+
sinks.add(sink);
|
|
751
798
|
},
|
|
752
799
|
deactivate() {
|
|
753
|
-
|
|
800
|
+
sinks.delete(sink);
|
|
754
801
|
}
|
|
755
802
|
};
|
|
756
803
|
}
|
|
@@ -823,6 +870,7 @@ function createHeapCollector() {
|
|
|
823
870
|
return {
|
|
824
871
|
kind: "heap",
|
|
825
872
|
activate() {
|
|
873
|
+
if (timer != null) return;
|
|
826
874
|
samples = [];
|
|
827
875
|
if (!readMemory()) return;
|
|
828
876
|
sample();
|
|
@@ -843,6 +891,7 @@ function createHeapCollector() {
|
|
|
843
891
|
|
|
844
892
|
// src/collectors/interaction.ts
|
|
845
893
|
var DURATION_THRESHOLD = 40;
|
|
894
|
+
var MAX_ENTRIES = 5e3;
|
|
846
895
|
function selectorFor(target) {
|
|
847
896
|
const el = target;
|
|
848
897
|
if (!el || typeof el.tagName !== "string") return void 0;
|
|
@@ -865,13 +914,17 @@ function createInteractionCollector() {
|
|
|
865
914
|
return {
|
|
866
915
|
kind: "interaction",
|
|
867
916
|
activate() {
|
|
917
|
+
if (active) return;
|
|
868
918
|
if (typeof PerformanceObserver === "undefined" || !supportsEventTiming()) return;
|
|
869
919
|
active = true;
|
|
870
920
|
entries = [];
|
|
871
921
|
try {
|
|
872
922
|
observer = new PerformanceObserver((list) => {
|
|
873
923
|
if (!active) return;
|
|
874
|
-
for (const e of list.getEntries())
|
|
924
|
+
for (const e of list.getEntries()) {
|
|
925
|
+
if (entries.length >= MAX_ENTRIES) break;
|
|
926
|
+
entries.push(e);
|
|
927
|
+
}
|
|
875
928
|
});
|
|
876
929
|
observer.observe({ type: "event", buffered: false, durationThreshold: DURATION_THRESHOLD });
|
|
877
930
|
try {
|
|
@@ -895,8 +948,10 @@ function createInteractionCollector() {
|
|
|
895
948
|
}
|
|
896
949
|
},
|
|
897
950
|
finalize(result) {
|
|
951
|
+
const buffered = entries;
|
|
952
|
+
entries = [];
|
|
898
953
|
const byId = /* @__PURE__ */ new Map();
|
|
899
|
-
for (const e of
|
|
954
|
+
for (const e of buffered) {
|
|
900
955
|
const id = e.interactionId;
|
|
901
956
|
if (!id) continue;
|
|
902
957
|
const group = byId.get(id);
|
|
@@ -1095,6 +1150,7 @@ function createSelfProfilingCollector() {
|
|
|
1095
1150
|
return {
|
|
1096
1151
|
kind: "long-task",
|
|
1097
1152
|
activate() {
|
|
1153
|
+
if (profiler) return;
|
|
1098
1154
|
tracePromise = null;
|
|
1099
1155
|
const Ctor = globalThis.Profiler;
|
|
1100
1156
|
if (typeof Ctor !== "function") return;
|
|
@@ -1112,6 +1168,8 @@ function createSelfProfilingCollector() {
|
|
|
1112
1168
|
if (!profiler) return;
|
|
1113
1169
|
try {
|
|
1114
1170
|
tracePromise = profiler.stop();
|
|
1171
|
+
tracePromise.catch(() => {
|
|
1172
|
+
});
|
|
1115
1173
|
} catch (err) {
|
|
1116
1174
|
console.warn("[react-perfscope] self-profiling stop failed:", err);
|
|
1117
1175
|
tracePromise = null;
|