@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.cjs
CHANGED
|
@@ -90,6 +90,7 @@ function mountShadow(vnode, opts = {}) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// src/app.tsx
|
|
93
|
+
var import_preact4 = require("preact");
|
|
93
94
|
var import_hooks6 = require("preact/hooks");
|
|
94
95
|
|
|
95
96
|
// src/i18n.ts
|
|
@@ -288,8 +289,8 @@ function isLang(v) {
|
|
|
288
289
|
return v === "en" || v === "ko";
|
|
289
290
|
}
|
|
290
291
|
function readStoredLang() {
|
|
291
|
-
if (typeof localStorage === "undefined") return "en";
|
|
292
292
|
try {
|
|
293
|
+
if (typeof localStorage === "undefined") return "en";
|
|
293
294
|
const v = localStorage.getItem(STORAGE_KEY);
|
|
294
295
|
return isLang(v) ? v : "en";
|
|
295
296
|
} catch {
|
|
@@ -306,11 +307,11 @@ function I18nProvider({ children }) {
|
|
|
306
307
|
const [lang, setLangState] = (0, import_hooks.useState)(() => readStoredLang());
|
|
307
308
|
const setLang = (0, import_hooks.useCallback)((l) => {
|
|
308
309
|
setLangState(l);
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
try {
|
|
311
|
+
if (typeof localStorage !== "undefined") {
|
|
311
312
|
localStorage.setItem(STORAGE_KEY, l);
|
|
312
|
-
} catch {
|
|
313
313
|
}
|
|
314
|
+
} catch {
|
|
314
315
|
}
|
|
315
316
|
}, []);
|
|
316
317
|
const value = (0, import_hooks.useMemo)(() => ({ lang, setLang, t: STRINGS[lang] }), [lang, setLang]);
|
|
@@ -443,12 +444,12 @@ function showArrow(id, from, to, color = DEFAULT_BORDER) {
|
|
|
443
444
|
const minX = Math.min(from.x, to.x);
|
|
444
445
|
const minY = Math.min(from.y, to.y);
|
|
445
446
|
const w = Math.max(2, Math.abs(to.x - from.x));
|
|
446
|
-
const
|
|
447
|
+
const h4 = Math.max(2, Math.abs(to.y - from.y));
|
|
447
448
|
svg.style.left = `${minX}px`;
|
|
448
449
|
svg.style.top = `${minY}px`;
|
|
449
450
|
svg.setAttribute("width", `${w}`);
|
|
450
|
-
svg.setAttribute("height", `${
|
|
451
|
-
svg.setAttribute("viewBox", `0 0 ${w} ${
|
|
451
|
+
svg.setAttribute("height", `${h4}`);
|
|
452
|
+
svg.setAttribute("viewBox", `0 0 ${w} ${h4}`);
|
|
452
453
|
svg.style.opacity = "1";
|
|
453
454
|
while (svg.firstChild) svg.removeChild(svg.firstChild);
|
|
454
455
|
const fx = from.x - minX;
|
|
@@ -530,7 +531,7 @@ var WEB_VITAL_THRESHOLDS = {
|
|
|
530
531
|
TTFB: [800, 1800]
|
|
531
532
|
};
|
|
532
533
|
function webVitalRating(name, value) {
|
|
533
|
-
const [good, needs] = WEB_VITAL_THRESHOLDS[name];
|
|
534
|
+
const [good, needs] = WEB_VITAL_THRESHOLDS[name] ?? [Infinity, Infinity];
|
|
534
535
|
if (value <= good) return "good";
|
|
535
536
|
if (value <= needs) return "needs";
|
|
536
537
|
return "poor";
|
|
@@ -591,15 +592,15 @@ var WEB_VITAL_UNIT = {
|
|
|
591
592
|
TTFB: "ms"
|
|
592
593
|
};
|
|
593
594
|
function formatVitalValue(name, value) {
|
|
594
|
-
|
|
595
|
+
const unit = WEB_VITAL_UNIT[name] ?? "ms";
|
|
596
|
+
if (unit !== "ms") return value.toFixed(3);
|
|
595
597
|
if (value >= 1e3) return (value / 1e3).toFixed(2) + "s";
|
|
596
|
-
return value.toFixed(0);
|
|
598
|
+
return value.toFixed(0) + "ms";
|
|
597
599
|
}
|
|
598
600
|
function VitalChip({ s }) {
|
|
599
601
|
const { t } = useI18n();
|
|
600
602
|
const rating = webVitalRating(s.name, s.value);
|
|
601
603
|
const color = RATING_COLOR[rating];
|
|
602
|
-
const unit = WEB_VITAL_UNIT[s.name];
|
|
603
604
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
604
605
|
"span",
|
|
605
606
|
{
|
|
@@ -631,10 +632,7 @@ function VitalChip({ s }) {
|
|
|
631
632
|
}
|
|
632
633
|
),
|
|
633
634
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { style: { color: "#e6e6e6" }, children: s.name }),
|
|
634
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
635
|
-
formatVitalValue(s.name, s.value),
|
|
636
|
-
unit !== "s" ? unit : ""
|
|
637
|
-
] })
|
|
635
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color }, children: formatVitalValue(s.name, s.value) })
|
|
638
636
|
]
|
|
639
637
|
}
|
|
640
638
|
);
|
|
@@ -3284,6 +3282,65 @@ function Panel(props) {
|
|
|
3284
3282
|
|
|
3285
3283
|
// src/app.tsx
|
|
3286
3284
|
var import_jsx_runtime10 = require("preact/jsx-runtime");
|
|
3285
|
+
var PanelErrorBoundary = class extends import_preact4.Component {
|
|
3286
|
+
constructor() {
|
|
3287
|
+
super(...arguments);
|
|
3288
|
+
this.state = { error: null };
|
|
3289
|
+
}
|
|
3290
|
+
static getDerivedStateFromError(error) {
|
|
3291
|
+
return { error };
|
|
3292
|
+
}
|
|
3293
|
+
componentDidCatch(error) {
|
|
3294
|
+
console.warn("[react-perfscope] panel crashed:", error);
|
|
3295
|
+
}
|
|
3296
|
+
render() {
|
|
3297
|
+
if (this.state.error === null) return this.props.children;
|
|
3298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
3299
|
+
"div",
|
|
3300
|
+
{
|
|
3301
|
+
role: "alert",
|
|
3302
|
+
style: {
|
|
3303
|
+
position: "fixed",
|
|
3304
|
+
bottom: "16px",
|
|
3305
|
+
right: "16px",
|
|
3306
|
+
zIndex: 2147483647,
|
|
3307
|
+
display: "flex",
|
|
3308
|
+
alignItems: "center",
|
|
3309
|
+
gap: "8px",
|
|
3310
|
+
padding: "8px 12px",
|
|
3311
|
+
background: "#141414",
|
|
3312
|
+
border: "1px solid #ff3b30",
|
|
3313
|
+
borderRadius: "8px",
|
|
3314
|
+
color: "#e6e6e6",
|
|
3315
|
+
fontSize: "12px",
|
|
3316
|
+
fontFamily: "SF Mono, Menlo, Consolas, monospace"
|
|
3317
|
+
},
|
|
3318
|
+
children: [
|
|
3319
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "react-perfscope panel crashed \u2014 see console" }),
|
|
3320
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3321
|
+
"button",
|
|
3322
|
+
{
|
|
3323
|
+
"aria-label": "Close panel",
|
|
3324
|
+
onClick: () => {
|
|
3325
|
+
this.setState({ error: null });
|
|
3326
|
+
this.props.onClose();
|
|
3327
|
+
},
|
|
3328
|
+
style: {
|
|
3329
|
+
background: "none",
|
|
3330
|
+
border: "1px solid #444",
|
|
3331
|
+
borderRadius: "4px",
|
|
3332
|
+
color: "#e6e6e6",
|
|
3333
|
+
cursor: "pointer",
|
|
3334
|
+
padding: "2px 8px"
|
|
3335
|
+
},
|
|
3336
|
+
children: "\xD7"
|
|
3337
|
+
}
|
|
3338
|
+
)
|
|
3339
|
+
]
|
|
3340
|
+
}
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
};
|
|
3287
3344
|
function App(props) {
|
|
3288
3345
|
const { recorder, position = "bottom-right", resolveFrame, finalize } = props;
|
|
3289
3346
|
const [recording, setRecording] = (0, import_hooks6.useState)(false);
|
|
@@ -3312,12 +3369,23 @@ function App(props) {
|
|
|
3312
3369
|
startedAtRef.current = performance.now();
|
|
3313
3370
|
setElapsedMs(0);
|
|
3314
3371
|
setResult(null);
|
|
3315
|
-
|
|
3372
|
+
try {
|
|
3373
|
+
recorder.start();
|
|
3374
|
+
} catch (err) {
|
|
3375
|
+
console.warn("[react-perfscope] failed to start recording:", err);
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3316
3378
|
setRecording(true);
|
|
3317
3379
|
} else {
|
|
3318
|
-
const r = recorder.stop();
|
|
3319
|
-
const token = ++resultTokenRef.current;
|
|
3320
3380
|
setRecording(false);
|
|
3381
|
+
let r;
|
|
3382
|
+
try {
|
|
3383
|
+
r = recorder.stop();
|
|
3384
|
+
} catch (err) {
|
|
3385
|
+
console.warn("[react-perfscope] failed to stop recording:", err);
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
const token = ++resultTokenRef.current;
|
|
3321
3389
|
setResult(r);
|
|
3322
3390
|
if (finalize) {
|
|
3323
3391
|
finalize(r).then((enriched) => {
|
|
@@ -3340,14 +3408,31 @@ function App(props) {
|
|
|
3340
3408
|
position
|
|
3341
3409
|
}
|
|
3342
3410
|
),
|
|
3343
|
-
result !== null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Panel, { result, position, onClose, resolveFrame })
|
|
3411
|
+
result !== null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PanelErrorBoundary, { onClose, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Panel, { result, position, onClose, resolveFrame }) })
|
|
3344
3412
|
] });
|
|
3345
3413
|
}
|
|
3346
3414
|
|
|
3347
3415
|
// src/mount.tsx
|
|
3348
3416
|
var import_jsx_runtime11 = require("preact/jsx-runtime");
|
|
3349
3417
|
function mount(opts) {
|
|
3350
|
-
|
|
3418
|
+
if (!opts.host && !document.body) {
|
|
3419
|
+
let torn = false;
|
|
3420
|
+
let unmount = null;
|
|
3421
|
+
const onReady = () => {
|
|
3422
|
+
if (torn || !document.body) return;
|
|
3423
|
+
unmount = mountNow(opts, document.body);
|
|
3424
|
+
};
|
|
3425
|
+
document.addEventListener("DOMContentLoaded", onReady, { once: true });
|
|
3426
|
+
return () => {
|
|
3427
|
+
torn = true;
|
|
3428
|
+
document.removeEventListener("DOMContentLoaded", onReady);
|
|
3429
|
+
unmount?.();
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
return mountNow(opts, opts.host ?? document.body);
|
|
3433
|
+
}
|
|
3434
|
+
function mountNow(opts, host) {
|
|
3435
|
+
const { recorder, position = "bottom-right", resolveFrame, finalize } = opts;
|
|
3351
3436
|
if (host.querySelector("[data-perfscope-host]")) {
|
|
3352
3437
|
console.warn("[react-perfscope] mount(): a widget is already mounted here \u2014 ignoring this call");
|
|
3353
3438
|
return () => {
|