Skip to content

Commit 90328f3

Browse files
committed
feat(scan): replay collection runtime
collect detailed browser timing on interaction implement react scan rrweb replayer plugin refactor monitoring interaction and component collection
1 parent f982809 commit 90328f3

27 files changed

+4716
-384
lines changed

packages/scan/package.json

+37-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
{
22
"name": "react-scan",
3-
"version": "0.0.54",
3+
"version": "0.0.1083",
44
"description": "Scan your React app for renders",
5-
"keywords": ["react", "react-scan", "react scan", "render", "performance"],
5+
"keywords": [
6+
"react",
7+
"react-scan",
8+
"react scan",
9+
"render",
10+
"performance"
11+
],
612
"homepage": "https://react-scan.million.dev",
713
"bugs": {
814
"url": "https://github.com/aidenybai/react-scan/issues"
@@ -161,17 +167,27 @@
161167
"types": "dist/index.d.ts",
162168
"typesVersions": {
163169
"*": {
164-
"monitoring": ["./dist/core/monitor/index.d.ts"],
165-
"monitoring/next": ["./dist/core/monitor/params/next.d.ts"],
170+
"monitoring": [
171+
"./dist/core/monitor/index.d.ts"
172+
],
173+
"monitoring/next": [
174+
"./dist/core/monitor/params/next.d.ts"
175+
],
166176
"monitoring/react-router-legacy": [
167177
"./dist/core/monitor/params/react-router-v5.d.ts"
168178
],
169179
"monitoring/react-router": [
170180
"./dist/core/monitor/params/react-router-v6.d.ts"
171181
],
172-
"monitoring/remix": ["./dist/core/monitor/params/remix.d.ts"],
173-
"monitoring/astro": ["./dist/core/monitor/params/astro/index.ts"],
174-
"react-component-name/vite": ["./dist/react-component-name/vite.d.ts"],
182+
"monitoring/remix": [
183+
"./dist/core/monitor/params/remix.d.ts"
184+
],
185+
"monitoring/astro": [
186+
"./dist/core/monitor/params/astro/index.ts"
187+
],
188+
"react-component-name/vite": [
189+
"./dist/react-component-name/vite.d.ts"
190+
],
175191
"react-component-name/webpack": [
176192
"./dist/react-component-name/webpack.d.ts"
177193
],
@@ -187,11 +203,20 @@
187203
"react-component-name/rollup": [
188204
"./dist/react-component-name/rollup.d.ts"
189205
],
190-
"react-component-name/astro": ["./dist/react-component-name/astro.d.ts"]
206+
"react-component-name/astro": [
207+
"./dist/react-component-name/astro.d.ts"
208+
]
191209
}
192210
},
193211
"bin": "bin/cli.js",
194-
"files": ["dist", "bin", "package.json", "README.md", "LICENSE", "auto.d.ts"],
212+
"files": [
213+
"dist",
214+
"bin",
215+
"package.json",
216+
"README.md",
217+
"LICENSE",
218+
"auto.d.ts"
219+
],
195220
"scripts": {
196221
"build": "npm run build:css && NODE_ENV=production tsup",
197222
"postbuild": "pnpm copy-astro && node ../../scripts/version-warning.mjs",
@@ -217,6 +242,7 @@
217242
"@clack/prompts": "^0.8.2",
218243
"@preact/signals": "^1.3.1",
219244
"@rollup/pluginutils": "^5.1.3",
245+
"@rrweb/types": "2.0.0-alpha.18",
220246
"@types/node": "^20.17.9",
221247
"bippy": "^0.0.25",
222248
"esbuild": "^0.24.0",
@@ -225,6 +251,8 @@
225251
"mri": "^1.2.0",
226252
"playwright": "^1.49.0",
227253
"preact": "^10.25.1",
254+
"rrweb": "2.0.0-alpha.4",
255+
"rrweb-snapshot": "2.0.0-alpha.4",
228256
"tsx": "^4.0.0"
229257
},
230258
"devDependencies": {

packages/scan/src/auto-monitor.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'bippy'; // implicit init RDT hook
22
import { Store } from 'src';
33
import { scanMonitoring } from 'src/core/monitor';
4-
import { initPerformanceMonitoring } from 'src/core/monitor/performance';
4+
// import { initPerformanceMonitoring } from 'src/core/monitor/performance';
55
import { Device } from 'src/core/monitor/types';
66

77
if (typeof window !== 'undefined') {
@@ -28,9 +28,10 @@ if (typeof window !== 'undefined') {
2828
route: '<mock-route>',
2929
commit: '<mock-commit>',
3030
branch: '<mock-branch>',
31+
interactionListeningForRenders: null,
3132
};
32-
scanMonitoring({
33-
enabled: true,
34-
});
35-
initPerformanceMonitoring();
33+
// scanMonitoring({
34+
// enabled: true,
35+
// });
36+
// initPerformanceMonitoring();
3637
}

packages/scan/src/auto.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import 'bippy'; // implicit init RDT hook
22
import { scan } from './index';
33

44
if (typeof window !== 'undefined') {
5-
scan();
5+
scan({
6+
dangerouslyForceRunInProduction: true,
7+
});
68
window.reactScan = scan;
79
}
810

packages/scan/src/core/index.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ export type MonitoringOptions = Pick<
172172
interface Monitor {
173173
pendingRequests: number;
174174
interactions: Array<InternalInteraction>;
175+
interactionListeningForRenders:
176+
| ((fiber: Fiber, renders: Array<Render>) => void)
177+
| null;
175178
session: ReturnType<typeof getSession>;
176179
url: string | null;
177180
route: string | null;
@@ -381,12 +384,14 @@ export const reportRender = (fiber: Fiber, renders: Array<Render>) => {
381384

382385
// Get data from both current and alternate fibers
383386
const currentData = Store.reportData.get(reportFiber);
384-
const alternateData = fiber.alternate ? Store.reportData.get(fiber.alternate) : null;
387+
const alternateData = fiber.alternate
388+
? Store.reportData.get(fiber.alternate)
389+
: null;
385390

386391
// More efficient null checks and Math.max
387392
const existingCount = Math.max(
388393
(currentData && currentData.count) || 0,
389-
(alternateData && alternateData.count) || 0
394+
(alternateData && alternateData.count) || 0,
390395
);
391396

392397
// Create single shared object for both fibers
@@ -395,7 +400,7 @@ export const reportRender = (fiber: Fiber, renders: Array<Render>) => {
395400
time: selfTime || 0,
396401
renders,
397402
displayName,
398-
type: getType(fiber.type) || null
403+
type: getType(fiber.type) || null,
399404
};
400405

401406
// Store in both fibers
@@ -461,7 +466,12 @@ const updateScheduledOutlines = (fiber: Fiber, renders: Array<Render>) => {
461466
for (let i = 0, len = renders.length; i < len; i++) {
462467
const render = renders[i];
463468
const domFiber = getNearestHostFiber(fiber);
464-
if (!domFiber || !domFiber.stateNode || !(domFiber.stateNode instanceof Element)) continue;
469+
if (
470+
!domFiber ||
471+
!domFiber.stateNode ||
472+
!(domFiber.stateNode instanceof Element)
473+
)
474+
continue;
465475

466476
if (ReactScanInternals.scheduledOutlines.has(fiber)) {
467477
const existingOutline = ReactScanInternals.scheduledOutlines.get(fiber)!;
@@ -512,6 +522,10 @@ export const getIsProduction = () => {
512522
return isProduction;
513523
};
514524

525+
export const attachReplayCanvas = () => {
526+
startFlushOutlineInterval();
527+
};
528+
515529
export const start = () => {
516530
if (typeof window === 'undefined') return;
517531

@@ -540,6 +554,13 @@ export const start = () => {
540554

541555
const instrumentation = createInstrumentation('devtools', {
542556
onActive() {
557+
const rdtHook = getRDTHook();
558+
for (const renderer of rdtHook.renderers.values()) {
559+
const buildType = detectReactBuildType(renderer);
560+
if (buildType === 'production') {
561+
isProduction = true;
562+
}
563+
}
543564
const existingRoot = document.querySelector('react-scan-root');
544565
if (existingRoot) {
545566
return;
@@ -556,7 +577,9 @@ export const start = () => {
556577
void audioContext.resume();
557578
};
558579

559-
window.addEventListener('pointerdown', createAudioContextOnInteraction, { once: true });
580+
window.addEventListener('pointerdown', createAudioContextOnInteraction, {
581+
once: true,
582+
});
560583

561584
const container = document.createElement('div');
562585
container.id = 'react-scan-root';

packages/scan/src/core/instrumentation.ts

+37-10
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,42 @@ let lastTime = performance.now();
2323
let frameCount = 0;
2424
let initedFps = false;
2525

26-
const updateFPS = () => {
26+
let fpsListeners: Array<(fps: number) => void> = [];
27+
28+
export const listenToFps = (listener: (fps: number) => void) => {
29+
// console.log('oushed', listener);
30+
31+
fpsListeners.push(listener);
32+
33+
return () => {
34+
// console.log('unsub listener');
35+
36+
fpsListeners = fpsListeners.filter(
37+
(currListener) => currListener !== listener,
38+
);
39+
};
40+
};
41+
42+
const updateFPS = (onChange?: (fps: number) => void) => {
2743
frameCount++;
2844
const now = performance.now();
29-
if (now - lastTime >= 1000) {
30-
fps = frameCount;
45+
const timeSinceLastUpdate = now - lastTime;
46+
47+
if (timeSinceLastUpdate >= 500) {
48+
const calculatedFPS = Math.round((frameCount / timeSinceLastUpdate) * 1000);
49+
50+
if (calculatedFPS !== fps) {
51+
for (const listener of fpsListeners) {
52+
listener(calculatedFPS);
53+
}
54+
}
55+
56+
fps = calculatedFPS;
3157
frameCount = 0;
3258
lastTime = now;
3359
}
34-
requestAnimationFrame(updateFPS);
60+
61+
requestAnimationFrame(() => updateFPS(onChange));
3562
};
3663

3764
export const getFPS = () => {
@@ -361,30 +388,30 @@ export const createInstrumentation = (
361388

362389
const changes: Array<RenderChange> = [];
363390

364-
const propsChanges = getChangedPropsDetailed(fiber).map(change => ({
391+
const propsChanges = getChangedPropsDetailed(fiber).map((change) => ({
365392
type: 'props' as const,
366393
name: change.name,
367394
value: change.value,
368395
prevValue: change.prevValue,
369-
unstable: false
396+
unstable: false,
370397
}));
371398

372-
const stateChanges = getStateChanges(fiber).map(change => ({
399+
const stateChanges = getStateChanges(fiber).map((change) => ({
373400
type: 'state' as const,
374401
name: change.name,
375402
value: change.value,
376403
prevValue: change.prevValue,
377404
count: change.count,
378-
unstable: false
405+
unstable: false,
379406
}));
380407

381-
const contextChanges = getContextChanges(fiber).map(change => ({
408+
const contextChanges = getContextChanges(fiber).map((change) => ({
382409
type: 'context' as const,
383410
name: change.name,
384411
value: change.value,
385412
prevValue: change.prevValue,
386413
count: change.count,
387-
unstable: false
414+
unstable: false,
388415
}));
389416

390417
changes.push(...propsChanges, ...stateChanges, ...contextChanges);

0 commit comments

Comments
 (0)