const COUNTERS = [
'createElement',
'createTextNode',
'createComment',
'createFragment',
'cloneNode',
'insertBefore',
'removeChild',
'replaceChildren',
'setAttribute',
'removeAttribute',
'setProperty',
'addEventListener',
'textWrite',
];
const REACTIVE = ['signal', 'derived', 'effect', 'effectRun'];
function zeroed(keys) {
const out = {};
for (const key of keys) out[key] = 0;
return out;
}
let crossings = zeroed(COUNTERS);
let work = zeroed(COUNTERS);
let reactive = zeroed(REACTIVE);
let depth = 0;
function tick(name) {
work[name] += 1;
if (depth === 0) crossings[name] += 1;
}
function resetCounts() {
crossings = zeroed(COUNTERS);
work = zeroed(COUNTERS);
reactive = zeroed(REACTIVE);
}
function snapshot() {
return { crossings, work, reactive };
}
function totalCrossings(counts) {
let total = 0;
for (const key of COUNTERS) total += counts[key];
return total;
}
function wrap(node, name, counter) {
const raw = node[name];
node[name] = function (...args) {
tick(counter);
depth += 1;
try {
return raw.apply(this, args);
} finally {
depth -= 1;
}
};
}
function instrumentNode(node) {
wrap(node, 'insertBefore', 'insertBefore');
wrap(node, 'removeChild', 'removeChild');
wrap(node, 'replaceChildren', 'replaceChildren');
wrap(node, 'cloneNode', 'cloneNode');
if (node.kind === 'element') {
wrap(node, 'setAttribute', 'setAttribute');
wrap(node, 'removeAttribute', 'removeAttribute');
wrap(node, 'addEventListener', 'addEventListener');
wrap(node.style, 'setProperty', 'setProperty');
}
if (node.kind === 'text' || node.kind === 'comment') {
let value = node.nodeValue;
Object.defineProperty(node, 'nodeValue', {
configurable: true,
get() {
return value;
},
set(next) {
tick('textWrite');
value = String(next);
},
});
}
return node;
}
const rawCreateElement = createElement;
const rawCreateTextNode = createTextNode;
const rawCreateComment = createComment;
const rawCreateDocumentFragment = createDocumentFragment;
createElement = function (tag) {
tick('createElement');
depth += 1;
try {
return instrumentNode(rawCreateElement(tag));
} finally {
depth -= 1;
}
};
createTextNode = function (value) {
tick('createTextNode');
depth += 1;
try {
return instrumentNode(rawCreateTextNode(value));
} finally {
depth -= 1;
}
};
createComment = function (value) {
tick('createComment');
depth += 1;
try {
return instrumentNode(rawCreateComment(value));
} finally {
depth -= 1;
}
};
createDocumentFragment = function () {
tick('createFragment');
depth += 1;
try {
return instrumentNode(rawCreateDocumentFragment());
} finally {
depth -= 1;
}
};
document.createElement = createElement;
document.createTextNode = createTextNode;
document.createComment = createComment;
document.createDocumentFragment = createDocumentFragment;
const rawSignal = signal;
const rawDerived = derived;
const rawEffect = effect;
signal = function (initial) {
reactive.signal += 1;
return rawSignal(initial);
};
derived = function (compute) {
reactive.derived += 1;
return rawDerived(compute);
};
effect = function (fn) {
reactive.effect += 1;
return rawEffect(() => {
reactive.effectRun += 1;
return fn();
});
};