@react-perfscope/ui 0.7.1 → 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 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +105 -20
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ function mountShadow(vnode, opts = {}) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// src/app.tsx
|
|
60
|
+
import { Component } from "preact";
|
|
60
61
|
import { useState as useState4, useRef as useRef3, useEffect as useEffect2 } from "preact/hooks";
|
|
61
62
|
|
|
62
63
|
// src/i18n.ts
|
|
@@ -255,8 +256,8 @@ function isLang(v) {
|
|
|
255
256
|
return v === "en" || v === "ko";
|
|
256
257
|
}
|
|
257
258
|
function readStoredLang() {
|
|
258
|
-
if (typeof localStorage === "undefined") return "en";
|
|
259
259
|
try {
|
|
260
|
+
if (typeof localStorage === "undefined") return "en";
|
|
260
261
|
const v = localStorage.getItem(STORAGE_KEY);
|
|
261
262
|
return isLang(v) ? v : "en";
|
|
262
263
|
} catch {
|
|
@@ -273,11 +274,11 @@ function I18nProvider({ children }) {
|
|
|
273
274
|
const [lang, setLangState] = useState(() => readStoredLang());
|
|
274
275
|
const setLang = useCallback((l) => {
|
|
275
276
|
setLangState(l);
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
try {
|
|
278
|
+
if (typeof localStorage !== "undefined") {
|
|
278
279
|
localStorage.setItem(STORAGE_KEY, l);
|
|
279
|
-
} catch {
|
|
280
280
|
}
|
|
281
|
+
} catch {
|
|
281
282
|
}
|
|
282
283
|
}, []);
|
|
283
284
|
const value = useMemo(() => ({ lang, setLang, t: STRINGS[lang] }), [lang, setLang]);
|
|
@@ -410,12 +411,12 @@ function showArrow(id, from, to, color = DEFAULT_BORDER) {
|
|
|
410
411
|
const minX = Math.min(from.x, to.x);
|
|
411
412
|
const minY = Math.min(from.y, to.y);
|
|
412
413
|
const w = Math.max(2, Math.abs(to.x - from.x));
|
|
413
|
-
const
|
|
414
|
+
const h4 = Math.max(2, Math.abs(to.y - from.y));
|
|
414
415
|
svg.style.left = `${minX}px`;
|
|
415
416
|
svg.style.top = `${minY}px`;
|
|
416
417
|
svg.setAttribute("width", `${w}`);
|
|
417
|
-
svg.setAttribute("height", `${
|
|
418
|
-
svg.setAttribute("viewBox", `0 0 ${w} ${
|
|
418
|
+
svg.setAttribute("height", `${h4}`);
|
|
419
|
+
svg.setAttribute("viewBox", `0 0 ${w} ${h4}`);
|
|
419
420
|
svg.style.opacity = "1";
|
|
420
421
|
while (svg.firstChild) svg.removeChild(svg.firstChild);
|
|
421
422
|
const fx = from.x - minX;
|
|
@@ -497,7 +498,7 @@ var WEB_VITAL_THRESHOLDS = {
|
|
|
497
498
|
TTFB: [800, 1800]
|
|
498
499
|
};
|
|
499
500
|
function webVitalRating(name, value) {
|
|
500
|
-
const [good, needs] = WEB_VITAL_THRESHOLDS[name];
|
|
501
|
+
const [good, needs] = WEB_VITAL_THRESHOLDS[name] ?? [Infinity, Infinity];
|
|
501
502
|
if (value <= good) return "good";
|
|
502
503
|
if (value <= needs) return "needs";
|
|
503
504
|
return "poor";
|
|
@@ -558,15 +559,15 @@ var WEB_VITAL_UNIT = {
|
|
|
558
559
|
TTFB: "ms"
|
|
559
560
|
};
|
|
560
561
|
function formatVitalValue(name, value) {
|
|
561
|
-
|
|
562
|
+
const unit = WEB_VITAL_UNIT[name] ?? "ms";
|
|
563
|
+
if (unit !== "ms") return value.toFixed(3);
|
|
562
564
|
if (value >= 1e3) return (value / 1e3).toFixed(2) + "s";
|
|
563
|
-
return value.toFixed(0);
|
|
565
|
+
return value.toFixed(0) + "ms";
|
|
564
566
|
}
|
|
565
567
|
function VitalChip({ s }) {
|
|
566
568
|
const { t } = useI18n();
|
|
567
569
|
const rating = webVitalRating(s.name, s.value);
|
|
568
570
|
const color = RATING_COLOR[rating];
|
|
569
|
-
const unit = WEB_VITAL_UNIT[s.name];
|
|
570
571
|
return /* @__PURE__ */ jsxs2(
|
|
571
572
|
"span",
|
|
572
573
|
{
|
|
@@ -598,10 +599,7 @@ function VitalChip({ s }) {
|
|
|
598
599
|
}
|
|
599
600
|
),
|
|
600
601
|
/* @__PURE__ */ jsx2("strong", { style: { color: "#e6e6e6" }, children: s.name }),
|
|
601
|
-
/* @__PURE__ */
|
|
602
|
-
formatVitalValue(s.name, s.value),
|
|
603
|
-
unit !== "s" ? unit : ""
|
|
604
|
-
] })
|
|
602
|
+
/* @__PURE__ */ jsx2("span", { style: { color }, children: formatVitalValue(s.name, s.value) })
|
|
605
603
|
]
|
|
606
604
|
}
|
|
607
605
|
);
|
|
@@ -3251,6 +3249,65 @@ function Panel(props) {
|
|
|
3251
3249
|
|
|
3252
3250
|
// src/app.tsx
|
|
3253
3251
|
import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
|
|
3252
|
+
var PanelErrorBoundary = class extends Component {
|
|
3253
|
+
constructor() {
|
|
3254
|
+
super(...arguments);
|
|
3255
|
+
this.state = { error: null };
|
|
3256
|
+
}
|
|
3257
|
+
static getDerivedStateFromError(error) {
|
|
3258
|
+
return { error };
|
|
3259
|
+
}
|
|
3260
|
+
componentDidCatch(error) {
|
|
3261
|
+
console.warn("[react-perfscope] panel crashed:", error);
|
|
3262
|
+
}
|
|
3263
|
+
render() {
|
|
3264
|
+
if (this.state.error === null) return this.props.children;
|
|
3265
|
+
return /* @__PURE__ */ jsxs10(
|
|
3266
|
+
"div",
|
|
3267
|
+
{
|
|
3268
|
+
role: "alert",
|
|
3269
|
+
style: {
|
|
3270
|
+
position: "fixed",
|
|
3271
|
+
bottom: "16px",
|
|
3272
|
+
right: "16px",
|
|
3273
|
+
zIndex: 2147483647,
|
|
3274
|
+
display: "flex",
|
|
3275
|
+
alignItems: "center",
|
|
3276
|
+
gap: "8px",
|
|
3277
|
+
padding: "8px 12px",
|
|
3278
|
+
background: "#141414",
|
|
3279
|
+
border: "1px solid #ff3b30",
|
|
3280
|
+
borderRadius: "8px",
|
|
3281
|
+
color: "#e6e6e6",
|
|
3282
|
+
fontSize: "12px",
|
|
3283
|
+
fontFamily: "SF Mono, Menlo, Consolas, monospace"
|
|
3284
|
+
},
|
|
3285
|
+
children: [
|
|
3286
|
+
/* @__PURE__ */ jsx10("span", { children: "react-perfscope panel crashed \u2014 see console" }),
|
|
3287
|
+
/* @__PURE__ */ jsx10(
|
|
3288
|
+
"button",
|
|
3289
|
+
{
|
|
3290
|
+
"aria-label": "Close panel",
|
|
3291
|
+
onClick: () => {
|
|
3292
|
+
this.setState({ error: null });
|
|
3293
|
+
this.props.onClose();
|
|
3294
|
+
},
|
|
3295
|
+
style: {
|
|
3296
|
+
background: "none",
|
|
3297
|
+
border: "1px solid #444",
|
|
3298
|
+
borderRadius: "4px",
|
|
3299
|
+
color: "#e6e6e6",
|
|
3300
|
+
cursor: "pointer",
|
|
3301
|
+
padding: "2px 8px"
|
|
3302
|
+
},
|
|
3303
|
+
children: "\xD7"
|
|
3304
|
+
}
|
|
3305
|
+
)
|
|
3306
|
+
]
|
|
3307
|
+
}
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
};
|
|
3254
3311
|
function App(props) {
|
|
3255
3312
|
const { recorder, position = "bottom-right", resolveFrame, finalize } = props;
|
|
3256
3313
|
const [recording, setRecording] = useState4(false);
|
|
@@ -3279,12 +3336,23 @@ function App(props) {
|
|
|
3279
3336
|
startedAtRef.current = performance.now();
|
|
3280
3337
|
setElapsedMs(0);
|
|
3281
3338
|
setResult(null);
|
|
3282
|
-
|
|
3339
|
+
try {
|
|
3340
|
+
recorder.start();
|
|
3341
|
+
} catch (err) {
|
|
3342
|
+
console.warn("[react-perfscope] failed to start recording:", err);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3283
3345
|
setRecording(true);
|
|
3284
3346
|
} else {
|
|
3285
|
-
const r = recorder.stop();
|
|
3286
|
-
const token = ++resultTokenRef.current;
|
|
3287
3347
|
setRecording(false);
|
|
3348
|
+
let r;
|
|
3349
|
+
try {
|
|
3350
|
+
r = recorder.stop();
|
|
3351
|
+
} catch (err) {
|
|
3352
|
+
console.warn("[react-perfscope] failed to stop recording:", err);
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
const token = ++resultTokenRef.current;
|
|
3288
3356
|
setResult(r);
|
|
3289
3357
|
if (finalize) {
|
|
3290
3358
|
finalize(r).then((enriched) => {
|
|
@@ -3307,14 +3375,31 @@ function App(props) {
|
|
|
3307
3375
|
position
|
|
3308
3376
|
}
|
|
3309
3377
|
),
|
|
3310
|
-
result !== null && /* @__PURE__ */ jsx10(Panel, { result, position, onClose, resolveFrame })
|
|
3378
|
+
result !== null && /* @__PURE__ */ jsx10(PanelErrorBoundary, { onClose, children: /* @__PURE__ */ jsx10(Panel, { result, position, onClose, resolveFrame }) })
|
|
3311
3379
|
] });
|
|
3312
3380
|
}
|
|
3313
3381
|
|
|
3314
3382
|
// src/mount.tsx
|
|
3315
3383
|
import { jsx as jsx11 } from "preact/jsx-runtime";
|
|
3316
3384
|
function mount(opts) {
|
|
3317
|
-
|
|
3385
|
+
if (!opts.host && !document.body) {
|
|
3386
|
+
let torn = false;
|
|
3387
|
+
let unmount = null;
|
|
3388
|
+
const onReady = () => {
|
|
3389
|
+
if (torn || !document.body) return;
|
|
3390
|
+
unmount = mountNow(opts, document.body);
|
|
3391
|
+
};
|
|
3392
|
+
document.addEventListener("DOMContentLoaded", onReady, { once: true });
|
|
3393
|
+
return () => {
|
|
3394
|
+
torn = true;
|
|
3395
|
+
document.removeEventListener("DOMContentLoaded", onReady);
|
|
3396
|
+
unmount?.();
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
return mountNow(opts, opts.host ?? document.body);
|
|
3400
|
+
}
|
|
3401
|
+
function mountNow(opts, host) {
|
|
3402
|
+
const { recorder, position = "bottom-right", resolveFrame, finalize } = opts;
|
|
3318
3403
|
if (host.querySelector("[data-perfscope-host]")) {
|
|
3319
3404
|
console.warn("[react-perfscope] mount(): a widget is already mounted here \u2014 ignoring this call");
|
|
3320
3405
|
return () => {
|