Skip to content

Instantly share code, notes, and snippets.

@RobPruzan
Created February 25, 2025 02:32
Show Gist options
  • Save RobPruzan/697be9bd5417f9cb1207116194deb69b to your computer and use it in GitHub Desktop.
Save RobPruzan/697be9bd5417f9cb1207116194deb69b to your computer and use it in GitHub Desktop.
interface StackInfo {
fullStack: string[];
topFrame: string;
sourceFile: string;
}
const getDescriptor = (obj: any, prop: string) => {
let desc;
while (obj && !desc) {
desc = Object.getOwnPropertyDescriptor(obj, prop);
obj = Object.getPrototypeOf(obj);
}
return desc;
};
/**
*
* MEMORY LEAK HERE OF ARRAY<StackInfo>
*/
const processStack = (stack: string): StackInfo => {
const lines = stack.split("\n");
const filtered = lines
.filter((line) => !line.includes("performance-tracker"))
.map((line) => line.trim());
return {
fullStack: filtered,
topFrame: filtered[0],
sourceFile: filtered[0]?.match(/\((.*?)\)/)?.[1] || "unknown",
};
};
const timeOperation = () => {
const startTime = performance.now();
return {
startTime,
trackEnd: () => {
const endTime = performance.now();
const duration = endTime - startTime;
return {
duration,
endTime,
};
},
};
};
export type ReflowStats = {
duration: number;
prop: string;
element: HTMLElement;
stack: StackInfo;
};
let reflowListeners: Array<(reflowStats: ReflowStats) => void> = [];
export const listenForReflows = (
listener: (reflowStats: ReflowStats) => void
) => {
reflowListeners.push(listener);
return () => {
reflowListeners = reflowListeners.filter(
(existingListener) => existingListener !== listener
);
};
};
// TODO: track bounding rect calls too
const setupLayoutTracking = () => {
// const layoutProps = [
// "offsetWidth",
// "offsetHeight",
// "offsetLeft",
// "offsetTop",
// "clientWidth",
// "clientHeight",
// "scrollWidth",
// "scrollHeight",
// "clientLeft",
// "clientTop",
// "scrollLeft",
// "scrollTop",
// ];
// layoutProps.forEach((prop) => {
// const original = getDescriptor(HTMLElement.prototype, prop);
// if (!original) return;
// const descriptor: PropertyDescriptor = {
// get: function (this: HTMLElement) {
// const stack = processStack(new Error().stack || "");
// // might make sense to use date now here
// const start = performance.now();
// const value = original.get ? original.get.call(this) : original.value;
// const duration = performance.now() - start;
// reflowListeners.forEach((listener) => {
// listener({
// duration,
// prop,
// element: this,
// stack,
// });
// });
// return value;
// },
// configurable: true,
// enumerable: original.enumerable,
// };
// if (original.set) {
// descriptor.set = function(this: HTMLElement, value: any) {
// original.set!.call(this, value);
// };
// }
// Object.defineProperty(HTMLElement.prototype, prop, descriptor);
// });
};
export const initPerfTracker = () => {
setupLayoutTracking();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment