Skip to content

Commit

Permalink
feat(scan-core): Add cumulative changes tracking and visualisation
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenybai committed Dec 16, 2024
1 parent 21683d9 commit d9d7edc
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 108 deletions.
26 changes: 17 additions & 9 deletions packages/scan/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
flushOutlines,
getOutline,
type PendingOutline,

} from '@web-utils/outline';
import { log, logIntro } from '@web-utils/log';
import {
Expand All @@ -22,7 +21,7 @@ import {
} from '@web-inspect-element/inspect-state-machine';
import { playGeigerClickSound } from '@web-utils/geiger';
import { ICONS } from '@web-assets/svgs/svgs';
import { updateFiberRenderData, type RenderData } from 'src/core/utils';
import { updateFiberRenderData, type RenderData } from 'src/core/utils';
import { initReactScanOverlay } from './web/overlay';
import { createInstrumentation, type Render } from './instrumentation';
import { createToolbar } from './web/toolbar';
Expand Down Expand Up @@ -149,6 +148,7 @@ interface Monitor {
}

interface StoreType {
wasDetailsOpen: Signal<boolean>;
isInIframe: Signal<boolean>;
inspectState: Signal<States>;
monitor: Signal<Monitor | null>;
Expand All @@ -169,6 +169,7 @@ export interface Internals {
}

export const Store: StoreType = {
wasDetailsOpen: signal(true),
isInIframe: signal(
typeof window !== 'undefined' && window.self !== window.top,
),
Expand Down Expand Up @@ -252,8 +253,10 @@ export const reportRender = (fiber: Fiber, renders: Array<Render>) => {
type: null,
};

currentFiberData.count = Number(currentFiberData.count || 0) + Number(renders.length);
currentFiberData.time = Number(currentFiberData.time || 0) + Number(selfTime || 0);
currentFiberData.count =
Number(currentFiberData.count || 0) + Number(renders.length);
currentFiberData.time =
Number(currentFiberData.time || 0) + Number(selfTime || 0);
currentFiberData.renders = renders;

Store.reportData.set(reportFiber, currentFiberData);
Expand All @@ -267,8 +270,10 @@ export const reportRender = (fiber: Fiber, renders: Array<Render>) => {
type: getType(fiber.type) || fiber.type,
};

existingLegacyData.count = Number(existingLegacyData.count || 0) + Number(renders.length);
existingLegacyData.time = Number(existingLegacyData.time || 0) + Number(selfTime || 0);
existingLegacyData.count =
Number(existingLegacyData.count || 0) + Number(renders.length);
existingLegacyData.time =
Number(existingLegacyData.time || 0) + Number(selfTime || 0);
existingLegacyData.renders = renders;

Store.legacyReportData.set(displayName, existingLegacyData);
Expand Down Expand Up @@ -320,7 +325,10 @@ export const start = () => {
cssStyles.textContent = styles;

// Create SVG sprite sheet node directly
const iconSprite = new DOMParser().parseFromString(ICONS, 'image/svg+xml').documentElement;
const iconSprite = new DOMParser().parseFromString(
ICONS,
'image/svg+xml',
).documentElement;
shadow.appendChild(iconSprite);

// add toolbar root
Expand All @@ -346,8 +354,8 @@ export const start = () => {
const audioContext =
typeof window !== 'undefined'
? new (window.AudioContext ||
// @ts-expect-error -- This is a fallback for Safari
window.webkitAudioContext)()
// @ts-expect-error -- This is a fallback for Safari
window.webkitAudioContext)()
: null;

if (!Store.monitor.value) {
Expand Down
175 changes: 131 additions & 44 deletions packages/scan/src/core/web/inspect-element/view-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import {
const EXPANDED_PATHS = new Set<string>();
const fadeOutTimers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>();

export const renderPropsAndState = (
didRender: boolean,
fiber: any,
) => {
export const cumulativeChanges = {
props: new Map<string, number>(),
state: new Map<string, number>(),
context: new Map<string, number>(),
};

export const renderPropsAndState = (didRender: boolean, fiber: any) => {
const propContainer = Store.inspectState.value.propContainer;

if (!propContainer) {
Expand All @@ -33,8 +36,120 @@ export const renderPropsAndState = (

const changedProps = new Set(getChangedProps(fiber));
const changedState = new Set(getChangedState(fiber));
const changedContext = new Set<string>();

changedProps.forEach((key) => {
cumulativeChanges.props.set(
key,
(cumulativeChanges.props.get(key) ?? 0) + 1,
);
});

changedState.forEach((key) => {
cumulativeChanges.state.set(
key,
(cumulativeChanges.state.get(key) ?? 0) + 1,
);
});

changedContext.forEach((key) => {
cumulativeChanges.context.set(
key,
(cumulativeChanges.context.get(key) ?? 0) + 1,
);
});

propContainer.innerHTML = '';

const changedItems: Array<string> = [];

if (cumulativeChanges.props.size > 0) {
cumulativeChanges.props.forEach((count, key) => {
changedItems.push(`Prop: ${key} ×${count}`);
});
}

if (cumulativeChanges.state.size > 0) {
cumulativeChanges.state.forEach((count, key) => {
changedItems.push(`State: ${key} ×${count}`);
});
}

if (cumulativeChanges.context.size > 0) {
cumulativeChanges.context.forEach((count, key) => {
changedItems.push(`Context: ${key} ×${count}`);
});
}

const whatChangedSection = document.createElement('details');
whatChangedSection.className = 'react-scan-what-changed';
whatChangedSection.style.backgroundColor = '#b8860b';
whatChangedSection.style.color = '#ffff00';
whatChangedSection.style.padding = '5px';
whatChangedSection.open = Store.wasDetailsOpen.value;

const summary = document.createElement('summary');
summary.textContent = 'What changes?';
summary.className = 'font-bold';
whatChangedSection.appendChild(summary);

if (cumulativeChanges.props.size > 0) {
const propsHeader = document.createElement('div');
propsHeader.textContent = 'Props:';
const propsList = document.createElement('ul');
propsList.style.listStyleType = 'disc';
propsList.style.paddingLeft = '20px';

cumulativeChanges.props.forEach((count, key) => {
const li = document.createElement('li');
li.textContent = `${key} ×${count}`;
propsList.appendChild(li);
});

whatChangedSection.appendChild(propsHeader);
whatChangedSection.appendChild(propsList);
}

if (cumulativeChanges.state.size > 0) {
const stateHeader = document.createElement('div');
stateHeader.textContent = 'State:';
const stateList = document.createElement('ul');
stateList.style.listStyleType = 'disc';
stateList.style.paddingLeft = '20px';

cumulativeChanges.state.forEach((count, key) => {
const li = document.createElement('li');
li.textContent = `${key} ×${count}`;
stateList.appendChild(li);
});

whatChangedSection.appendChild(stateHeader);
whatChangedSection.appendChild(stateList);
}

if (cumulativeChanges.context.size > 0) {
const contextHeader = document.createElement('div');
contextHeader.textContent = 'Context:';
const contextList = document.createElement('ul');
contextList.style.listStyleType = 'disc';
contextList.style.paddingLeft = '20px';

cumulativeChanges.context.forEach((count, key) => {
const li = document.createElement('li');
li.textContent = `${key} ×${count}`;
contextList.appendChild(li);
});

whatChangedSection.appendChild(contextHeader);
whatChangedSection.appendChild(contextList);
}

whatChangedSection.addEventListener('toggle', () => {
Store.wasDetailsOpen.value = whatChangedSection.open;
});

propContainer.appendChild(whatChangedSection);

const inspector = document.createElement('div');
inspector.className = 'react-scan-inspector';

Expand Down Expand Up @@ -142,21 +257,13 @@ export const renderPropsAndState = (
}, null);
}

sections.sort((a, b) => {
if (a.hasChanges && !b.hasChanges) return -1;
if (!a.hasChanges && b.hasChanges) return 1;
return 0;
});

sections.forEach((section) => content.appendChild(section.element));

inspector.appendChild(content);

propContainer.appendChild(inspector);
};

const lastChangedAt = new Map<string, number>();

const renderSection = (
componentName: string,
didRender: boolean,
Expand All @@ -166,33 +273,11 @@ const renderSection = (
data: any,
changedKeys: Set<string> = new Set(),
) => {

const section = document.createElement('div');
section.className = 'react-scan-section';
section.dataset.section = title;

const entries = Object.entries(data).sort(([keyA], [keyB]) => {
const pathA = getPath(componentName, title.toLowerCase(), '', keyA);
const pathB = getPath(componentName, title.toLowerCase(), '', keyB);

if (
changedKeys.has(keyA) ||
(changedAt.has(pathA) && Date.now() - changedAt.get(pathA)! < 450)
) {
lastChangedAt.set(pathA, Date.now());
}
if (
changedKeys.has(keyB) ||
(changedAt.has(pathB) && Date.now() - changedAt.get(pathB)! < 450)
) {
lastChangedAt.set(pathB, Date.now());
}

const aLastChanged = lastChangedAt.get(pathA) ?? 0;
const bLastChanged = lastChangedAt.get(pathB) ?? 0;

return bLastChanged - aLastChanged;
});
const entries = Object.entries(data);

entries.forEach(([key, value]) => {
const el = createPropertyElement(
Expand Down Expand Up @@ -241,9 +326,10 @@ const tryOrElse = <T, E>(cb: () => T, val: E) => {
};

const isPromise = (value: any): value is Promise<unknown> => {
return value &&
(value instanceof Promise ||
(typeof value === 'object' && 'then' in value));
return (
value &&
(value instanceof Promise || (typeof value === 'object' && 'then' in value))
);
};

export const createPropertyElement = (
Expand Down Expand Up @@ -273,10 +359,11 @@ export const createPropertyElement = (
container.className = 'react-scan-property';

const isExpandable =
!isPromise(value) && (
(Array.isArray(value) && value.length > 0) ||
(typeof value === 'object' && value !== null && Object.keys(value).length > 0)
);
!isPromise(value) &&
((Array.isArray(value) && value.length > 0) ||
(typeof value === 'object' &&
value !== null &&
Object.keys(value).length > 0));

const currentPath = getPath(componentName, section, parentPath, key);
const prevValue = lastRendered.get(currentPath);
Expand Down Expand Up @@ -322,7 +409,6 @@ export const createPropertyElement = (
preview.dataset.key = key;
preview.dataset.section = section;


preview.innerHTML = `
${isBadRender ? '<span class="react-scan-warning">⚠️</span>' : ''}
<span class="react-scan-key">${key}:&nbsp;</span><span class="${getValueClassName(
Expand Down Expand Up @@ -482,7 +568,8 @@ export const createPropertyElement = (
const updateValue = () => {
const newValue = input.value;
value = typeof value === 'number' ? Number(newValue) : newValue;
(valueElement as HTMLElement).dataset.text = getValuePreview(value);
(valueElement as HTMLElement).dataset.text =
getValuePreview(value);

tryOrElse(() => {
input.replaceWith(valueElement);
Expand Down
Loading

0 comments on commit d9d7edc

Please sign in to comment.