Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/scan/src/auto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './polyfills';
// Prioritize bippy side-effect
import 'bippy';

Expand Down
29 changes: 2 additions & 27 deletions packages/scan/src/core/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,31 +187,6 @@ export function fastSerialize(value: unknown, depth = 0): string {
return str;
}

export const getPropsChanges = (fiber: Fiber) => {
const changes: Array<Change> = [];

const prevProps = fiber.alternate?.memoizedProps || {};
const nextProps = fiber.memoizedProps || {};

const allKeys = new Set([
...Object.keys(prevProps),
...Object.keys(nextProps),
]);
for (const propName in allKeys) {
// const prevValue = prevProps?.[propName];
const nextValue = nextProps?.[propName];

const change: Change = {
type: ChangeReason.Props,
name: propName,
value: nextValue,
};
changes.push(change);
}

return changes;
};

export const getStateChanges = (fiber: Fiber): StateChange[] => {
if (!fiber) return [];
const changes: StateChange[] = [];
Expand Down Expand Up @@ -457,10 +432,10 @@ export function getRenderData(fiber: Fiber) {
}

export function setRenderData(fiber: Fiber, value: RenderData) {
const type = getType(fiber.type)
const type = getType(fiber.type);
const id = getFiberIdentifier(fiber);
let keyMap = renderDataMap.get(type as object);

if (!keyMap) {
keyMap = new Map();
renderDataMap.set(type as object, keyMap);
Expand Down
40 changes: 30 additions & 10 deletions packages/scan/src/core/notifications/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Fiber,
getDisplayName,
getTimings,
hasMemoCache,
isHostFiber,
traverseFiber,
} from 'bippy';
Expand Down Expand Up @@ -69,6 +70,8 @@ export type FiberRenders = Record<
parents: Set<string>;
selfTime: number;
totalTime: number;
hasMemoCache: boolean;
wasFiberRenderMount: boolean;
nodeInfo: Array<{
selfTime: number;
element: Element;
Expand Down Expand Up @@ -471,20 +474,11 @@ const getTargetInteractionDetails = (target: Element) => {

const componentPath = getInteractionPath(associatedFiber);

// const childrenTree = collectFiberSubtree(associatedFiber, 20); // this can be expensive if not limited

// const firstChildSvg = Object.entries(childrenTree).find(([name, {isSvg }]) => isSvg)

// const firstSvg =
// associatedFiber.type === "svg"
// ? getFirstNameFromAncestor(associatedFiber)
// : Object.entries(childrenTree).find(([name, {isSvg }]) => isSvg)

// lowkey i have an idea
return {
componentPath,
childrenTree: {},
componentName,
elementFiber: associatedFiber,
};
};

Expand Down Expand Up @@ -898,6 +892,8 @@ export const listenForRenders = (
};
fiberRenders[displayName] = {
renderCount: 1,
hasMemoCache: hasMemoCache(fiber),
wasFiberRenderMount: wasFiberRenderMount(fiber),
parents: parents,
selfTime,
totalTime,
Expand Down Expand Up @@ -933,6 +929,9 @@ export const listenForRenders = (
changesCounts: new Map<string | number, number>(),
};

existing.wasFiberRenderMount =
existing.wasFiberRenderMount || wasFiberRenderMount(fiber);
existing.hasMemoCache = existing.hasMemoCache || hasMemoCache(fiber);
existing.changes = {
fiberProps: mergeSectionData(
existing.changes?.fiberProps || emptySection,
Expand Down Expand Up @@ -993,3 +992,24 @@ const mergeSectionData = (

return mergedSection;
};

const wasFiberRenderMount = (fiber: Fiber) => {
if (!fiber.alternate) {
return true;
}

const prevFiber = fiber.alternate;

const wasMounted =
prevFiber &&
prevFiber.memoizedState != null &&
prevFiber.memoizedState.element != null &&
prevFiber.memoizedState.isDehydrated !== true;

const isMounted =
fiber.memoizedState != null &&
fiber.memoizedState.element != null &&
fiber.memoizedState.isDehydrated !== true;

return !wasMounted && isMounted;
};
1 change: 1 addition & 0 deletions packages/scan/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './polyfills';
// Bippy has a side-effect that installs the hook.
import 'bippy';

Expand Down
9 changes: 9 additions & 0 deletions packages/scan/src/polyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
if (!Array.prototype.toSorted) {
Object.defineProperty(Array.prototype, 'toSorted', {
value: function <T>(this: Array<T>, compareFn?: (a: T, b: T) => number): Array<T> {
return [...this].sort(compareFn);
},
writable: true,
configurable: true,
});
}
23 changes: 19 additions & 4 deletions packages/scan/src/web/views/notifications/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createContext } from 'preact';
import { SetStateAction } from 'preact/compat';
import { Dispatch, useContext } from 'preact/hooks';
import { HIGH_SEVERITY_FPS_DROP_TIME } from '~core/notifications/event-tracking';
import { getFiberFromElement } from '../inspector/utils';
import { hasMemoCache } from 'bippy';

export type GroupedFiberRender = {
id: string;
Expand All @@ -18,6 +20,8 @@ export type GroupedFiberRender = {
elements: Array<Element>; // can't do a weak set because need to iterate over them......
deletedAll: boolean;
parents: Set<string>;
hasMemoCache: boolean;
wasFiberRenderMount: boolean;
};
export const getComponentName = (path: Array<string>) => {
const filteredPath = path.filter((item) => item.length > 2);
Expand Down Expand Up @@ -72,11 +76,22 @@ export type InteractionTiming = {
frameDraw: number | null;
};

export const isRenderMemoizable = (gropedFiberRender: GroupedFiberRender) => {
export const isRenderMemoizable = (
groupedFiberRender: GroupedFiberRender,
): boolean => {
if (groupedFiberRender.wasFiberRenderMount) {
// no amount of memoization can prevent a mount render
return false;
}
// this shouldn't be needed, it implies we either are tracking renders wrong, are tracking changes wrong, or are not tracking some other "state" that can cause re-renders, but its a better fallback than failing
if (groupedFiberRender.hasMemoCache) {
return false;
}

return (
gropedFiberRender.changes.context.length === 0 &&
gropedFiberRender.changes.props.length === 0 &&
gropedFiberRender.changes.state.length === 0
groupedFiberRender.changes.context.length === 0 &&
groupedFiberRender.changes.props.length === 0 &&
groupedFiberRender.changes.state.length === 0
);
};

Expand Down
2 changes: 2 additions & 0 deletions packages/scan/src/web/views/notifications/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const getGroupedFiberRenders = (fiberRenders: FiberRenders) => {
name: render.nodeInfo[0].name, // invariant, at least one exists,
deletedAll: false,
parents: render.parents,
hasMemoCache: render.hasMemoCache,
wasFiberRenderMount: render.wasFiberRenderMount,
// it would be nice if we calculated the % of components memoizable, but this would have to be calculated downstream before it got aggregated
elements: render.nodeInfo.map((node) => node.element),
changes: {
Expand Down
2 changes: 1 addition & 1 deletion packages/website/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const config: Config = {
700: 'oklch(41.78% 0.117 287.1 / <alpha-value>)',
800: 'oklch(34.52% 0.047 290.52 / <alpha-value>)',
900: 'oklch(24.54% 0.004 308.28 / <alpha-value>)',
950: 'oklch(18.22% 0 NaN / <alpha-value>)',
950: 'oklch(18.22% 0 308.28 / <alpha-value>)',
},
},
},
Expand Down