Skip to content

push-based/awesome-web-performance-snippets

Repository files navigation

Awesome Web Performance Snippets Awesome

Third Party Sources

Included scripts from other open source authors and repositories:

How to use a snippet?

Copy this code snippet into the DevTools console Tab to use it
// ex: katespade.com - list firsty party subdomains in HOSTS array
const HOSTS = ["assets.katespade.com"];
function getScriptInfo() {
    const resourceListEntries = performance.getEntriesByType("resource");
    // set for first party scripts
    const first = [];
    // set for third party scripts
    const third = [];
    resourceListEntries.forEach((resource) => {
        // check for initiator type
        const value = "initiatorType" in resource;
        if (value) {
            if (resource.initiatorType === "script") {
                const { host } = new URL(resource.name);
                // check if resource url host matches location.host = first party script
                if (host === location.host || HOSTS.includes(host)) {
                    first.push({ ...resource.toJSON(), type: "First Party" });
                }
                else {
                    // add to third party script
                    third.push({ ...resource.toJSON(), type: "Third Party" });
                }
            }
        }
    });
    const scripts = {
        firstParty: [{ name: "no data" }],
        thirdParty: [{ name: "no data" }],
    };
    if (first.length) {
        scripts.firstParty = first;
    }
    if (third.length) {
        scripts.thirdParty = third;
    }
    return scripts;
}
const { firstParty, thirdParty } = getScriptInfo();
console.groupCollapsed("FIRST PARTY SCRIPTS");
console.table(firstParty);
console.groupEnd();
console.groupCollapsed("THIRD PARTY SCRIPTS");
console.table(thirdParty);
console.groupEnd();

/*
Choose which properties to display
https://developer.mozilla.org/en-US/docs/Web/API/console/table

console.groupCollapsed("FIRST PARTY SCRIPTS");
console.table(firstParty, ["name", "nextHopProtocol"]);
console.groupEnd();
console.groupCollapsed("THIRD PARTY SCRIPTS", ["name", "nextHopProtocol"]);
console.table(thirdParty);
console.groupEnd();
*/
Copy this code snippet into the DevTools console Tab to use it
function createUniqueLists(firstParty, thirdParty) {
    function getUniqueListBy(arr, key) {
        return [...new Map(arr.map((item) => [item[key], item])).values()];
    }
    const firstPartyList = getUniqueListBy(firstParty, ["name"]);
    const thirdPartyList = getUniqueListBy(thirdParty, ["name"]);
    return { firstPartyList, thirdPartyList };
}
const { firstPartyList, thirdPartyList } = createUniqueLists(firstParty, thirdParty);
function calculateTimings(party, type) {
    const partyChoice = party === "first" ? firstParty : thirdParty;
    const timingChoices = {
        DNS_TIME: ["domainLookupEnd", "domainLookupStart"],
        TCP_HANDSHAKE: ["connectEnd", "connectStart"],
        RESPONSE_TIME: ["responseEnd", "responseStart"],
        SECURE_CONNECTION_TIME: ["connectEnd", "secureConnectionStart", 0],
        FETCH_UNTIL_RESPONSE: ["responseEnd", "fetchStart", 0],
        REQ_START_UNTIL_RES_END: ["responseEnd", "requestStart", 0],
        START_UNTIL_RES_END: ["responseEnd", "startTime", 0],
        REDIRECT_TIME: ["redirectEnd", "redirectStart"],
    };
    function handleChoices(timingEnd, timingStart, num) {
        if (!num) {
            return timingEnd - timingStart;
        }
        if (timingStart > 0) {
            return timingEnd - timingStart;
        }
        return 0;
    }
    const timings = partyChoice.map((script) => {
        const [timingEnd, timingStart, num] = timingChoices[type];
        const endValue = script[timingEnd];
        const startValue = script[timingStart];
        return {
            name: script.name,
            [type]: handleChoices(endValue, startValue, num),
        };
    });
    return timings;
}
// Available Options
const timingOptions = [
    "DNS_TIME",
    "TCP_HANDSHAKE",
    "RESPONSE_TIME",
    "SECURE_CONNECTION_TIME",
    "FETCH_UNTIL_RESPONSE",
    "REQ_START_UNTIL_RES_END",
    "START_UNTIL_RES_END",
    "REDIRECT_TIME",
];
// run em all!
// https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API#timing_resource_loading_phases
timingOptions.forEach((timing) => {
    console.groupCollapsed(`FIRST PARTY: ${timing}`);
    console.table(calculateTimings("first", timing));
    console.groupEnd();
    console.groupCollapsed(`THIRD PARTY: ${timing}`);
    console.table(calculateTimings("third", timing));
    console.groupEnd();
});
// choose your battle - arg1 is string either "first" or "third", arg2 is string timing option listed above.
console.table(calculateTimings("first", "REQ_START_UNTIL_RES_END"));
Copy this code snippet into the DevTools console Tab to use it
(function () {
    var ct = document.createElement('style');
    ct.innerText = `
    /*!==========================================================================
   #CT.CSS
   ========================================================================== */

/*!
 * ct.css – Let’s take a look inside your <head>…
 *
 * © Harry Roberts 2021 – twitter.com/csswizardry
 */





/**
 * It’s slightly easier to remember topics than it is colours. Set up some
 * custom properties for use later on.
 */

head {
  --ct-is-problematic: solid;
  --ct-is-affected: dashed;
  --ct-notify: #0bce6b;
  --ct-warn: #ffa400;
  --ct-error: #ff4e42;
}/**
 * Show the <head> and set up the items we might be interested in.
 */

head,
head script,
head script:not([src])[async],
head script:not([src])[defer],
head style, head [rel="stylesheet"],
head script ~ meta[http-equiv="content-security-policy"],
head > meta[charset]:not(:nth-child(-n+5)) {
  display: block;
}

head script,
head style, head [rel="stylesheet"],
head title,
head script ~ meta[http-equiv="content-security-policy"],
head > meta[charset]:not(:nth-child(-n+5)) {
  margin: 5px;
  padding: 5px;
  border-width: 5px;
  background-color: white;
  color: #333;
}

head ::before,
head script, head style {
  font: 16px/1.5 monospace, monospace;
  display: block;
}

head ::before {
  font-weight: bold;
}/**
 * External Script and Style
 */

head script[src],
head link[rel="stylesheet"] {
  border-style: var(--ct-is-problematic);
  border-color: var(--ct-warn);
}

  head script[src]::before {
    content: "[Blocking Script – " attr(src) "]"
  }

  head link[rel="stylesheet"]::before {
    content: "[Blocking Stylesheet – " attr(href) "]"
  }/**
 * Inline Script and Style.
 */

head style:not(:empty),
head script:not(:empty) {
  max-height: 5em;
  overflow: auto;
  background-color: #ffd;
  white-space: pre;
  border-color: var(--ct-notify);
  border-style: var(--ct-is-problematic);
}

  head script:not(:empty)::before {
    content: "[Inline Script] ";
  }

  head style:not(:empty)::before {
    content: "[Inline Style] ";
  }/**
 * Blocked Title.
 *
 * These selectors are generally more complex because the Key Selector (\`title\`)
 * depends on the specific conditions of preceding JS--we can’t cast a wide net
 * and narrow it down later as we can when targeting elements directly.
 */

head script[src]:not([async]):not([defer]):not([type=module]) ~ title,
head script:not(:empty) ~ title {
  display: block;
  border-style: var(--ct-is-affected);
  border-color: var(--ct-error);
}

  head script[src]:not([async]):not([defer]):not([type=module]) ~ title::before,
  head script:not(:empty) ~ title::before {
    content: "[<title> blocked by JS] ";
  }/**
 * Blocked Scripts.
 *
 * These selectors are generally more complex because the Key Selector
 * (\`script\`) depends on the specific conditions of preceding CSS--we can’t cast
 * a wide net and narrow it down later as we can when targeting elements
 * directly.
 */

head [rel="stylesheet"]:not([media="print"]):not(.ct) ~ script,
head style:not(:empty) ~ script {
  border-style: var(--ct-is-affected);
  border-color: var(--ct-warn);
}

  head [rel="stylesheet"]:not([media="print"]):not(.ct) ~ script::before,
  head style:not(:empty) ~ script::before {
    content: "[JS blocked by CSS – " attr(src) "]";
  }/**
 * Using both \`async\` and \`defer\` is redundant (an anti-pattern, even). Let’s
 * flag that.
 */

head script[src][src][async][defer] {
  display: block;
  border-style: var(--ct-is-problematic);
  border-color: var(--ct-warn);
}

  head script[src][src][async][defer]::before {
    content: "[async and defer is redundant: prefer defer – " attr(src) "]";
  }/**
 * Async and defer simply do not work on inline scripts. It won’t do any harm,
 * but it’s useful to know about.
 */
head script:not([src])[async],
head script:not([src])[defer] {
  border-style: var(--ct-is-problematic);
  border-color: var(--ct-warn);
}

  head script:not([src])[async]::before {
    content: "The async attribute is redundant on inline scripts"
  }

  head script:not([src])[defer]::before {
    content: "The defer attribute is redundant on inline scripts"
  }/**
 * Third Party blocking resources.
 *
 * Expect false-positives here… it’s a crude proxy at best.
 *
 * Selector-chaining (e.g. \`[src][src]\`) is used to bump up specificity.
 */

head script[src][src][src^="//"],
head script[src][src][src^="http"],
head [rel="stylesheet"][href^="//"],
head [rel="stylesheet"][href^="http"] {
  border-style: var(--ct-is-problematic);
  border-color: var(--ct-error);
}

  head script[src][src][src^="//"]::before,
  head script[src][src][src^="http"]::before {
    content: "[Third Party Blocking Script – " attr(src) "]";
  }

  head [rel="stylesheet"][href^="//"]::before,
  head [rel="stylesheet"][href^="http"]::before {
    content: "[Third Party Blocking Stylesheet – " attr(href) "]";
  }/**
 * Mid-HEAD CSP disables the Preload Scanner
 */

head script ~ meta[http-equiv="content-security-policy"] {
  border-style: var(--ct-is-problematic);
  border-color: var(--ct-error);
}

  head script ~ meta[http-equiv="content-security-policy"]::before {
    content: "[Meta CSP defined after JS]"
  }/**
 * Charset should appear as early as possible
 */
head > meta[charset]:not(:nth-child(-n+5)) {
  border-style: var(--ct-is-problematic);
  border-color: var(--ct-warn);
}

head > meta[charset]:not(:nth-child(-n+5))::before {
  content: "[Charset should appear as early as possible]";
}/**
 * Hide all irrelevant or non-matching scripts and styles (including ct.css).
 *
 * We’re done!
 */

link[rel="stylesheet"][media="print"],
link[rel="stylesheet"].ct, style.ct,
script[async], script[defer], script[type=module] {
  display: none;
}
    `;
    ct.classList.add('ct');
    document.head.appendChild(ct);
}());
Copy this code snippet into the DevTools console Tab to use it
function getImgs(sortBy) {
    const imgs = [];
    const resourceListEntries = performance.getEntriesByType("resource");
    resourceListEntries.forEach(({ name, transferSize, encodedBodySize, decodedBodySize, initiatorType, }) => {
        if (initiatorType == "img") {
            imgs.push({
                name,
                transferSize,
                decodedBodySize,
                encodedBodySize,
            });
        }
    });
    const imgList = imgs.sort((a, b) => {
        return b[sortBy] - a[sortBy];
    });
    return imgList;
}
console.table(getImgs("encodedBodySize"));
Copy this code snippet into the DevTools console Tab to use it
function checkImgSrcset(selector) {
    selector = selector || prompt('Img selector (e.g. div.test > img)');
    let lastSrc = '';
    const switches = [];
    const el = document.querySelector(selector);
    if (!el) {
        throw (`Could not fnd any element with selector ${selector}`);
    }
    const resizeObserver = new ResizeObserver((entries) => {
        const clientWidth = document.body.clientWidth;
        for (const entry of entries) {
            const img = entry.target;
            if (lastSrc !== img.currentSrc) {
                lastSrc = img.currentSrc;
                lastSrc && loadImg(lastSrc).then(i => {
                    switches.push({
                        clientWidth,
                        element: el,
                        src: lastSrc,
                        intrinsicWith: i.width,
                        intrinsicHeight: i.height,
                        renderedWith: el.clientWidth,
                        renderedHeight: el.clientHeight,
                        sizeDiff: ((i.width * i.height) / (el.clientWidth * el.clientHeight))
                    });
                    highlightElement(switches);
                    logData(switches);
                });
                highlightElement(switches);
                logData(switches);
            }
        }
    });
    resizeObserver.observe(el);
}
function logData(data) {
    console.clear();
    console.table(prepareTable(data));
}
function highlightElement(arr) {
    arr.forEach(o => {
        const { element, intrinsicWith, intrinsicHeight } = o;
        if (element && intrinsicWith && intrinsicHeight) {
            const d = ((intrinsicWith * intrinsicHeight) / (element.clientWidth * element.clientHeight));
            // for over-size border for under-size opacity?
            element.style.border = 1 + 'px solid red';
            element.style.opacity = 0.5 * d;
        }
    });
}
function prepareTable(arr) {
    return arr
        .map(({ element, ...inTable }) => ({
        dpr: window.devicePixelRatio,
        clientWidth: inTable.clientWidth + 'px',
        src: inTable.src,
        intrinsicSize: inTable.intrinsicWith + 'x' + inTable.intrinsicHeight + 'px',
        renderedSize: inTable.renderedWith + 'x' + inTable.renderedHeight + 'px',
        sizeDiff: inTable.sizeDiff.toFixed(2)
    }));
}
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image;
        img.onload = function () {
            resolve(img);
        };
        img.onerror = (e) => reject(e);
        img.src = url;
    });
}
;
checkImgSrcset();
Copy this code snippet into the DevTools console Tab to use it
const bgUrlChecker = /(url\(["'])([A-Za-z0-9$.:/_\-~]*)(["']\))(?!data:$)/g;
const base64UrlChecker = /(url\(["'])(data:)([A-Za-z0-9$.:/_\-~]*)/g;
const srcChecker = /(src=["'])([A-Za-z0-9$.:/_\-~]*)(["'])(?!data:$)/g;
const bgSRule = 'background';
const bgImgSRule = 'background-image';
const msgNotLazyLoaded = "❌ not lazy loaded";
const msgNotEagerLoaded = "❌ not eager loaded";
const msgDontUseBgImage = "❌ don't use bg image";
const msgDontUseBgDataImage = "❌ don't use data:<format>";
const msgNotDisplayed = "⚠ fetched but not displayed";
const msgUnknown = "⚠ Case not implemented";
const msgOk = "🆗";
function fixUsage(imgs) {
    let l = '';
    imgs.forEach(i => {
        switch (i.error) {
            case msgNotEagerLoaded:
                l = "eager";
                break;
            case msgNotLazyLoaded:
                l = "lazy";
                break;
        }
        l && i.tag.setAttribute('loading', l);
    });
}
function highlightElements(imgs) {
    let s = '';
    imgs.forEach(i => {
        switch (i.error) {
            case msgNotEagerLoaded:
                s = 'outline: 3px red solid;';
                break;
            case msgNotLazyLoaded:
                s = 'outline: 3px red dotted;';
                break;
            case msgDontUseBgDataImage:
                s = 'outline: 3px red dashed;';
                break;
            case msgDontUseBgImage:
                s = 'outline: 3px red dashed;';
                break;
        }
        s && i.tag.setAttribute('style', s);
    });
}
function isInViewPort(tag) {
    return tag.offsetTop < window.innerHeight &&
        tag.offsetTop > -tag.offsetHeight &&
        tag.offsetLeft > -tag.offsetWidth &&
        tag.offsetLeft < window.innerWidth;
}
function styles(tag, pseudoElt) {
    return window.getComputedStyle(tag, pseudoElt || null);
}
function getImgRelevantRules(tag) {
    const res = {
        withBgImgNodes: new Map(),
        withBgDataImgNodes: new Map()
    };
    let matchBgB64 = base64UrlChecker.exec(tag.attributes.src);
    if (matchBgB64) {
        res.withBgImgNodes.set(matchBgB64[3], tag);
    }
    [null, '::before', '::after']
        .map((pseudoElt) => {
        const backgroundVal = styles(tag, pseudoElt).getPropertyValue(bgSRule);
        const backgroundImageVal = styles(tag, pseudoElt).getPropertyValue(bgImgSRule);
        let matchBg = bgUrlChecker.exec(backgroundVal) || bgUrlChecker.exec(backgroundImageVal);
        let matchBgB64 = base64UrlChecker.exec(backgroundVal) || base64UrlChecker.exec(backgroundImageVal);
        if (matchBg) {
            res.withBgImgNodes.set(matchBg[2], tag);
        }
        else if (matchBgB64) {
            res.withBgDataImgNodes.set(matchBgB64[3], tag);
        }
    });
    return res;
}
function getNetworkImgs() {
    const imgs = new Map();
    const resourceListEntries = performance.getEntriesByType("resource");
    resourceListEntries.forEach(({ name, transferSize, initiatorType, }) => {
        if (initiatorType == "img") {
            imgs.set(name, {
                name,
                transferSize
            });
        }
    });
    return imgs;
}
function getImgsWithBackground(doc) {
    const withBgImgNames = new Set();
    const withBgImgNodes = new Map();
    const withBgDataImgNames = new Set();
    const withBgDataImgNodes = new Map();
    Array.from(doc.querySelectorAll('body *'))
        .forEach((tag) => {
        const badRules = getImgRelevantRules(tag);
        Array.from(badRules.withBgImgNodes.entries()).forEach(([url, _]) => {
            withBgImgNodes.set(url, tag);
            withBgImgNames.add(url);
        });
        Array.from(badRules.withBgDataImgNodes.entries()).forEach(([url, _]) => {
            withBgDataImgNodes.set(url, tag);
            withBgDataImgNames.add(url);
        });
    });
    return { withBgImgNodes, withBgImgNames, withBgDataImgNodes, withBgDataImgNames };
}
function findImagesAndLoadingAttribute(doc) {
    const imgs = doc.querySelectorAll('img');
    const lazyLoadedAboveTheFoldNodes = new Map();
    const lazyLoadedAboveTheFoldNames = new Set();
    const eagerLoadedBelowTheFoldNodes = new Map();
    const eagerLoadedBelowTheFoldNames = new Set();
    imgs.forEach((tag) => {
        const inViewPort = isInViewPort(tag);
        const url = tag.attributes.src ? tag.attributes.src.value : null;
        // Ignore images without URL since they might be handled by custom javaScript lazy loading technique.
        if (!url)
            return;
        const isLazy = tag.attributes.loading === 'lazy';
        if (isLazy && inViewPort) {
            lazyLoadedAboveTheFoldNodes.set(url, tag);
            lazyLoadedAboveTheFoldNames.add(url);
        }
        else if (!isLazy && !inViewPort) {
            eagerLoadedBelowTheFoldNodes.set(url, tag);
            eagerLoadedBelowTheFoldNames.add(url);
        }
    });
    return {
        lazyLoadedAboveTheFoldNames,
        lazyLoadedAboveTheFoldNodes,
        eagerLoadedBelowTheFoldNames,
        eagerLoadedBelowTheFoldNodes
    };
}
const { lazyLoadedAboveTheFoldNodes, lazyLoadedAboveTheFoldNames, eagerLoadedBelowTheFoldNodes, eagerLoadedBelowTheFoldNames } = findImagesAndLoadingAttribute(document);
const { withBgDataImgNames, withBgDataImgNodes, withBgImgNames, withBgImgNodes } = getImgsWithBackground(document);
const networkImgs = getNetworkImgs();
const allNames = Array.from(new Set([
    ...lazyLoadedAboveTheFoldNames,
    ...eagerLoadedBelowTheFoldNames,
    ...withBgImgNames,
    ...withBgDataImgNames
]));
function enrichSizeUsage(imgData) {
    return Promise.all(imgData.map((i, idx) => {
        return new Promise((r) => {
            const img = new Image;
            const wRetain = i.tag.width;
            const hRetain = i.tag.height;
            img.onload = function () {
                // mutation!
                imgData[idx].imgDisplayDiff = `${wRetain}/${hRetain} to ${img.width}/${img.height}`;
                r();
            };
            img.onerror = r;
            img.src = i.url;
        });
    })).then(() => imgData);
}
function enrichData() {
    return Array.from(allNames).map((url) => {
        let imgData = {
            tag: 'n/a',
            url,
            error: '',
            transferSize: '?'
        };
        let errorDetected = true;
        switch (true) {
            case eagerLoadedBelowTheFoldNames.has(url):
                imgData.tag = eagerLoadedBelowTheFoldNodes.get(url);
                imgData.error = msgNotLazyLoaded;
                break;
            case lazyLoadedAboveTheFoldNames.has(url):
                imgData.tag = lazyLoadedAboveTheFoldNodes.get(url);
                imgData.error = msgNotEagerLoaded;
                break;
            case withBgImgNames.has(url):
                imgData.tag = withBgImgNodes.get(url);
                imgData.error = msgDontUseBgImage;
                break;
            case withBgDataImgNames.has(url):
                imgData.tag = withBgDataImgNodes.get(url);
                imgData.error = msgDontUseBgDataImage;
                imgData.transferSize = url.length * 1.02;
                break;
            default:
                errorDetected = false;
        }
        if (networkImgs.has(url)) {
            const { transferSize, decodedBodySize, encodedBodySize } = networkImgs.get(url);
            imgData = { ...imgData, transferSize, decodedBodySize };
            if (!errorDetected) {
                imgData.error = msgOk;
            }
        }
        return imgData;
    });
}
const d = enrichData();
highlightElements(d);
fixUsage(d);
enrichSizeUsage(d).then(console.table);
Copy this code snippet into the DevTools console Tab to use it
function genColor() {
    let n = (Math.random() * 0xfffff * 1000000).toString(16);
    return "#" + n.slice(0, 6);
}
// console.log(shifts) to see full list of shifts above threshold
const shifts = [];
// threshold ex: 0.05
// Layout Shifts will be grouped by color.
// All nodes attributed to the shift will have a border with the corresponding color
// Shift value will be added above parent node.
// Will have all details related to that shift in dropdown
// Useful for single page applications and finding shifts after initial load
function findShifts(threshold) {
    return new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
            if (entry.value > threshold && !entry.hadRecentInput) {
                const color = genColor();
                shifts.push(entry);
                console.log(shifts);
                const valueNode = document.createElement("details");
                valueNode.innerHTML = `
<summary>Layout Shift: ${entry.value}</summary>
<pre>${JSON.stringify(entry, null, 2)}</pre>
`;
                valueNode.style = `color: ${color};`;
                entry.sources.forEach((source) => {
                    source.node.parentNode.insertBefore(valueNode, source.node);
                    source.node.style = `border: 2px ${color} solid`;
                });
            }
        });
    });
}
findShifts(0.05).observe({ entryTypes: ["layout-shift"] });
Copy this code snippet into the DevTools console Tab to use it
try {
    let cumulativeLayoutShiftScore = 0;
    const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
            if (!entry.hadRecentInput) {
                cumulativeLayoutShiftScore += entry.value;
            }
        }
    });
    observer.observe({ type: "layout-shift", buffered: true });
    document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === "hidden") {
            observer.takeRecords();
            observer.disconnect();
            console.log(`CLS: ${cumulativeLayoutShiftScore}`);
        }
    });
}
catch (e) {
    console.log(`Browser doesn't support this API`);
}
Copy this code snippet into the DevTools console Tab to use it
const b = document.body;
b.style.zoom === '1' ? b.style.zoom = '1.01' : b.style.zoom = '1';
Copy this code snippet into the DevTools console Tab to use it
console.log(Array.from(document.querySelectorAll('style'))
    .map(a => a.innerText)
    .reduce((a, b) => a + b));
Copy this code snippet into the DevTools console Tab to use it
function getAttributeDirectives() {
    const { name, showSummaryInDOM, appPrefixes, mode } = initializeFlow();
    /**
     * Filter out nodes that don't have an attribute
     */
    function filterAttribute(attribute, prefixes) {
        return Array.isArray(prefixes)
            ? prefixes.some((p) => attribute.name ? attribute.name.startsWith(p.toLowerCase()) : false)
            : attribute.name
                ? attribute.name.startsWith(prefixes.toLowerCase())
                : false;
    }
    function initializeFlow() {
        /**
         * Clearing summary items from DOM.
         */
        const summaries = document.querySelectorAll(".customSummaryItem");
        summaries.forEach((i) => i.remove());
        const mode = prompt("Mode: summary or directive");
        switch (mode) {
            case "directive":
                return {
                    mode,
                    name: prompt("Directive name"),
                    showSummaryInDOM: prompt("Apply summary info to elements? (yes/no)", "no") === "yes"
                        ? true
                        : false,
                };
            case "summary":
                return {
                    mode,
                    appPrefixes: prompt("Directives prefixes, comma separated. (ex: app)")
                        .split(",")
                        .map((p) => p.trim()) || "app",
                    showSummaryInDOM: prompt("Apply summary info to elements? (yes/no)", "no") === "yes"
                        ? true
                        : false,
                };
        }
    }
    /**
     * Set of checks to determine if element is hidden.
     */
    function isHidden(element) {
        return !(element.offsetWidth ||
            element.offsetHeight ||
            element.getClientRects().length);
    }
    // Checks if element is in viewport
    function isInViewport(element) {
        return (element.offsetTop < window.innerHeight &&
            element.offsetTop > -element.offsetHeight &&
            element.offsetLeft > -element.offsetWidth &&
            element.offsetLeft < window.innerWidth);
    }
    /**
     * Adds summary div to references
     */
    function addSummary(nodes, prefixes) {
        nodes.forEach((n) => {
            n.style.position = "relative";
            const node = document.createElement("DIV");
            Object.assign(node.style, {
                position: "absolute",
                top: "0",
                left: "0",
                "z-index": "999999",
                "font-size": "14px",
                display: "flex",
                background: "green",
                color: "#fff",
                padding: "4px",
            });
            node.classList.add("customSummaryItem");
            const text = document.createTextNode(`${[...n.attributes]
                .filter((a) => filterAttribute(a, prefixes))
                .map((a) => a.name).length}`);
            node.appendChild(text);
            n.appendChild(node);
        });
    }
    /**
     * Finds references of the nodes that contain directive with given name
     */
    function findReferences(name) {
        const directives = Array.from(document.querySelectorAll(`[${name}]`)).map((r) => {
            return {
                name,
                hidden: isHidden(r),
                visible: !isHidden(r),
                inViewport: isInViewport(r),
                outOfViewport: !isInViewport(r),
                reference: r,
            };
        });
        return {
            all: directives,
            visible: directives.filter((c) => !c.hidden),
            hidden: directives.filter((c) => c.hidden),
            inViewport: directives.filter((c) => c.inViewport),
            outOfViewport: directives.filter((c) => !c.inViewport),
        };
    }
    /**
     * Get summary data for all directives
     */
    function getAllDirectivesSummary(prefixes) {
        const nodesWithDirectives = Array.from(document.body.querySelectorAll("*")).filter((e) => Array.from(e.attributes).some((a) => filterAttribute(a, prefixes)));
        const directives = 
        // Find unique components names in page
        [
            ...new Set(nodesWithDirectives
                .map((e) => [...e.attributes]
                .filter((a) => filterAttribute(a, prefixes))
                .map((a) => a.name))
                .reduce((acc, val) => [...acc, ...val], [])),
        ]
            .map((name) => getSpecificDirectiveSummary(name))
            .reduce((acc, val) => [...acc, val[0]], []);
        if (showSummaryInDOM) {
            addSummary(nodesWithDirectives, prefixes);
        }
        return [
            {
                name: "📝TOTAL",
                visible: directives
                    .map((c) => c.visible)
                    .reduce((acc, val) => acc + val, 0),
                hidden: directives
                    .map((c) => c.hidden)
                    .reduce((acc, val) => acc + val, 0),
                inViewport: directives
                    .map((c) => c.inViewport)
                    .reduce((acc, val) => acc + val, 0),
                outOfViewport: directives
                    .map((c) => c.outOfViewport)
                    .reduce((acc, val) => acc + val, 0),
                reference: "----",
            },
            ...directives,
        ];
    }
    /**
     * Get summary data for specific directive
     */
    function getSpecificDirectiveSummary(name, showSummary) {
        const { all, visible, hidden, inViewport, outOfViewport } = findReferences(name);
        if (showSummary) {
            addSummary(all.map((e) => e.reference), name);
        }
        return [
            {
                name: `👉 ${name}`,
                visible: visible.length,
                hidden: hidden.length,
                inViewport: inViewport.length,
                outOfViewport: outOfViewport.length,
                reference: {
                    visible,
                    hidden,
                    inViewport,
                    outOfViewport,
                },
            },
            ...all,
        ];
    }
    switch (mode) {
        case "directive":
            return console.table(getSpecificDirectiveSummary(name, showSummaryInDOM));
        case "summary":
            return console.table(getAllDirectivesSummary(appPrefixes));
    }
}
Copy this code snippet into the DevTools console Tab to use it
function index() {
    const { name, showSummaryInDOM, appPrefixes, mode, allNodes, visibleNodes, hiddenNodes, inViewportNodes, outOfViewportNodes, } = initializeFlow();
    /**
     * Flow init
     */
    function initializeFlow() {
        /**
         * Clearing summary items from DOM.
         */
        const summaries = document.querySelectorAll(".customSummaryItem");
        summaries.forEach((i) => i.remove());
        const mode = prompt("Mode: summary or component");
        const allNodes = Array.from(document.body.querySelectorAll("*"));
        const visibleNodes = [];
        const hiddenNodes = [];
        const inViewportNodes = [];
        const outOfViewportNodes = [];
        allNodes.forEach((n) => {
            if (isHidden(n)) {
                hiddenNodes.push(n);
            }
            else {
                visibleNodes.push(n);
            }
            if (isInViewport(n)) {
                inViewportNodes.push(n);
            }
            else {
                outOfViewportNodes.push(n);
            }
        });
        switch (mode) {
            case "component":
                return {
                    mode,
                    name: prompt("Component name"),
                    showSummaryInDOM: prompt("Apply summary info to elements? (yes/no)", "no") === "yes"
                        ? true
                        : false,
                    allNodes,
                    visibleNodes,
                    hiddenNodes,
                    inViewportNodes,
                    outOfViewportNodes,
                };
            case "summary":
                return {
                    mode,
                    appPrefixes: prompt("Components prefixes, comma separated. (ex: app)")
                        .split(",")
                        .map((p) => p.trim()) || "app",
                    showSummaryInDOM: prompt("Apply summary info to elements? (yes/no)", "no") === "yes"
                        ? true
                        : false,
                    allNodes,
                    visibleNodes,
                    hiddenNodes,
                    inViewportNodes,
                    outOfViewportNodes,
                };
        }
    }
    /**
     * Set of checks to determine if element is hidden.
     */
    function isHidden(element) {
        return !(element.offsetWidth ||
            element.offsetHeight ||
            element.getClientRects().length);
    }
    /**
     * Checks if element is in viewport.
     */
    function isInViewport(element) {
        return (element.offsetTop < window.innerHeight &&
            element.offsetTop > -element.offsetHeight &&
            element.offsetLeft > -element.offsetWidth &&
            element.offsetLeft < window.innerWidth);
    }
    /**
     * Adds summary div to references
     */
    function addSummary(nodes) {
        nodes.forEach((n) => {
            n.references.self.style.position = "relative";
            const node = document.createElement("DIV");
            const totalNode = document.createElement("SPAN");
            const visibleNode = document.createElement("SPAN");
            const hiddenNode = document.createElement("SPAN");
            const totalText = document.createTextNode(` Total: ${n.visibleNodes + n.hiddenNodes} `);
            const visibleText = document.createTextNode(` Visible: ${n.visibleNodes} `);
            const hiddenText = document.createTextNode(` Hidden: ${n.hiddenNodes} `);
            /**
             * Appending styles
             */
            Object.assign(node.style, {
                position: "absolute",
                top: "0",
                left: "0",
                "z-index": "999999",
                "font-size": "14px",
                display: "flex",
            });
            Object.assign(totalNode.style, { background: "black", color: "#fff" });
            Object.assign(visibleNode.style, { background: "green", color: "#fff" });
            Object.assign(hiddenNode.style, { background: "red", color: "#fff" });
            totalNode.appendChild(totalText);
            visibleNode.appendChild(visibleText);
            hiddenNode.appendChild(hiddenText);
            node.appendChild(totalNode);
            node.appendChild(visibleNode);
            node.appendChild(hiddenNode);
            node.classList.add("customSummaryItem");
            n.references.self.appendChild(node);
        });
    }
    /**
     * Finds references of the component with given name
     */
    function findReferences(name, showSummary) {
        const components = Array.from(document.querySelectorAll(name)).map((r) => {
            const childNodes = [r, ...r.querySelectorAll("*")];
            const hiddenNodes = [];
            const visibleNodes = [];
            const inViewportNodes = [];
            const outOfViewportNodes = [];
            childNodes.forEach((c) => {
                if (isHidden(c)) {
                    hiddenNodes.push(c);
                }
                else {
                    visibleNodes.push(c);
                }
                if (isInViewport(c)) {
                    inViewportNodes.push(c);
                }
                else {
                    outOfViewportNodes.push(c);
                }
            });
            return {
                name: r.nodeName,
                nodes: childNodes.length,
                visibleNodes: visibleNodes.length,
                hiddenNodes: hiddenNodes.length,
                inViewportNodes: inViewportNodes.length,
                outOfViewportNodes: outOfViewportNodes.length,
                hidden: isHidden(r),
                visible: !isHidden(r),
                inViewport: isInViewport(r),
                outOfViewport: !isInViewport(r),
                references: {
                    self: r,
                    visibleNodes,
                    hiddenNodes,
                    inViewportNodes,
                    outOfViewportNodes,
                },
            };
        });
        if (showSummary) {
            addSummary(components);
        }
        return {
            all: components,
            visible: components.filter((c) => !c.hidden),
            hidden: components.filter((c) => c.hidden),
            inViewport: components.filter((c) => c.inViewport),
            outOfViewport: components.filter((c) => !c.inViewport),
        };
    }
    /**
     * Get summary data for all components
     */
    function getAllComponentsSummary(prefixes) {
        const components = [
            ...new Set(allNodes
                .filter((e) => Array.isArray(prefixes)
                ? prefixes.some((p) => e.nodeName.startsWith(p.toUpperCase()))
                : e.nodeName.startsWith(prefix.toUpperCase()))
                .map((e) => e.nodeName)),
        ]
            .map((name) => getSpecificComponentSummary(name))
            .reduce((acc, val) => [...acc, val[0]], []);
        return [
            {
                name: "📝TOTAL",
                visible: components
                    .map((c) => c.visible)
                    .reduce((acc, val) => acc + val, 0),
                hidden: components
                    .map((c) => c.hidden)
                    .reduce((acc, val) => acc + val, 0),
                inViewport: components
                    .map((c) => c.inViewport)
                    .reduce((acc, val) => acc + val, 0),
                outOfViewport: components
                    .map((c) => c.outOfViewport)
                    .reduce((acc, val) => acc + val, 0),
                nodes: allNodes.length,
                visibleNodes: visibleNodes.length,
                hiddenNodes: hiddenNodes.length,
                inViewportNodes: inViewportNodes.length,
                outOfViewportNodes: outOfViewportNodes.length,
                references: "----",
            },
            ...components,
        ];
    }
    /**
     * Get summary data for provided component name
     */
    function getSpecificComponentSummary(name) {
        const { all, visible, hidden, inViewport, outOfViewport } = findReferences(name.toUpperCase(), showSummaryInDOM);
        return [
            {
                name: `👉 ${name.toUpperCase()}`,
                // Components counters
                visible: visible.length,
                hidden: hidden.length,
                inViewport: inViewport.length,
                outOfViewport: outOfViewport.length,
                // Nodes counters
                nodes: all.map((r) => r.nodes).reduce((acc, val) => acc + val, 0),
                visibleNodes: all
                    .map((r) => (!r.hidden ? r.visibleNodes : 0))
                    .reduce((acc, val) => acc + val, 0),
                hiddenNodes: all
                    .map((r) => (r.hidden ? r.nodes : r.hiddenNodes))
                    .reduce((acc, val) => acc + val, 0),
                inViewportNodes: all
                    .map((r) => r.inViewportNodes)
                    .reduce((acc, val) => acc + val, 0),
                outOfViewportNodes: all
                    .map((r) => r.outOfViewportNodes)
                    .reduce((acc, val) => acc + val, 0),
                // References
                references: {
                    visible,
                    hidden,
                    inViewport,
                    outOfViewport,
                },
            },
            ...all,
        ];
    }
    switch (mode) {
        case "component":
            return console.table(getSpecificComponentSummary(name));
        case "summary":
            return console.table(getAllComponentsSummary(appPrefixes));
    }
}
Copy this code snippet into the DevTools console Tab to use it
function getDOMEventListeners() {
    // Get all elements with event listeners
    const elements = [document, ...document.querySelectorAll("*")]
        .map((e) => {
        const elementListeners = window.getEventListeners(e);
        return {
            element: e,
            listeners: Object.keys(elementListeners)
                .map((key) => ({
                [key]: elementListeners[key],
            }))
                .reduce((acc, listener) => ({
                ...acc,
                ...listener,
            }), {}),
        };
    })
        .filter((el) => Object.keys(el.listeners).length);
    // Find unique listeners names
    const names = new Set(elements
        .map((e) => Object.keys(e.listeners))
        .reduce((acc, listener) => [...acc, ...listener], []));
    // Form output table
    const table = [...names].reduce((acc, n) => {
        const withListener = elements.filter((e) => e.listeners[n]);
        const total = withListener.reduce((acc, e) => acc + e.listeners[n].length, 0);
        const activeListeners = withListener.reduce((acc, e) => acc + e.listeners[n].filter((l) => !l.passive).length, 0);
        const activeReferences = withListener.reduce((acc, e) => e.listeners[n].filter((l) => !l.passive).length ? [...acc, e] : acc, []);
        const passiveListeners = withListener.reduce((acc, e) => acc + e.listeners[n].filter((l) => l.passive).length, 0);
        const passiveReferences = withListener.reduce((acc, e) => e.listeners[n].filter((l) => l.passive).length ? [...acc, e] : acc, []);
        const onceListeners = withListener.reduce((acc, e) => acc + e.listeners[n].filter((l) => l.once).length, 0);
        const onceReferences = withListener.reduce((acc, e) => e.listeners[n].filter((l) => l.once).length ? [...acc, e] : acc, []);
        return [
            ...acc,
            {
                name: n,
                total,
                activeListeners,
                activeListeners,
                passiveListeners,
                onceListeners,
                references: {
                    active: activeReferences,
                    passive: passiveReferences,
                    once: onceReferences,
                },
            },
        ];
    }, []);
    console.table([
        {
            name: "📝TOTAL",
            total: table.reduce((acc, val) => acc + val.total, 0),
            activeListeners: table.reduce((acc, val) => acc + val.activeListeners, 0),
            passiveListeners: table.reduce((acc, val) => acc + val.passiveListeners, 0),
            onceListeners: table.reduce((acc, val) => acc + val.onceListeners, 0),
            references: "----",
        },
        ...table,
    ]);
}
Copy this code snippet into the DevTools console Tab to use it
function index(root = document.body) {
    const allNodes = [...root.querySelectorAll("*")];
    const notProcessed = allNodes.filter((n) => isHidden(n));
    const processed = allNodes.filter((n) => !isHidden(n));
    const visibility = processed.filter((n) => isVisibilityHidden(n));
    const opacity = processed.filter((n) => isOpacity0(n));
    const dimensions = processed.filter((n) => isHeightWidthOverflow(n));
    const transform = processed.filter((n) => isTransformHidden(n));
    const opacityFilter = processed.filter((n) => isFilterOpacity(n));
    /**
     * Finds elements that are not affecting layout of the page and will not be included in styles recalculation
     */
    function isHidden(element) {
        return !(element.offsetWidth ||
            element.offsetHeight ||
            element.getClientRects().length);
    }
    /**
     * This elements are still processed during style recalculation
     */
    function isVisibilityHidden(element) {
        return window.getComputedStyle(element).visibility === "hidden";
    }
    /**
     * This elements are still processed during style recalculation
     */
    function isOpacity0(element) {
        return window.getComputedStyle(element).opacity === "0";
    }
    /**
     * This elements are still processed during style recalculation
     */
    function isHeightWidthOverflow(element) {
        const styles = window.getComputedStyle(element);
        return (((styles.height === "0" || styles.height === "0px") &&
            styles.overflow === "hidden") ||
            ((styles.width === "0" || styles.width === "0px") &&
                styles.overflow === "hidden") ||
            ((styles.height === "0" ||
                (styles.height === "0px" && styles.width === "0") ||
                styles.width === "0px") &&
                styles.overflow === "hidden"));
    }
    /**
     * This elements are still processed during style recalculation
     */
    function isTransformHidden(element) {
        return element.style.tranform === "scale(0)";
    }
    /**
     * This elements are still processed during style recalculation
     */
    function isFilterOpacity(element) {
        return element.style.filter === "opacity(0)";
    }
    /**
     * This elements are still processed during style recalculation
     */
    function getReferences(nodes) {
        return nodes.map((n) => ({
            self: n,
            children: n.querySelectorAll("*"),
        }));
    }
    function getSummary(name, nodes) {
        const children = nodes
            .map((n) => n.querySelectorAll("*").length + 1)
            .reduce((acc, val) => acc + val, 0);
        return {
            "hiding method": name,
            nodes: nodes.length,
            children,
            "potential savings (%)": Number(parseFloat((children / processed.length) * 100).toFixed(2)),
            references: getReferences(nodes),
        };
    }
    console.table([
        {
            name: `📝TOTAL`,
            nodes: allNodes.length,
            processed: processed.length,
            notProcessed: notProcessed.length,
        },
    ]);
    const summary = [
        getSummary("visibility: none", visibility),
        getSummary("opacity: 0", opacity),
        getSummary("height: 0 || width: 0 && overflow: hidden", dimensions),
        getSummary("transform: scale(0)", transform),
        getSummary("filter: opacity(0)", opacityFilter),
    ];
    return console.table([
        {
            "hiding method": "👉SUMMARY",
            nodes: summary.reduce((acc, val) => acc + val.nodes, 0),
            children: summary.reduce((acc, val) => acc + val.children, 0),
            "potential savings (%)": Number(summary
                .reduce((acc, val) => acc + val["potential savings (%)"], 0)
                .toFixed(2)),
            references: "----",
        },
        ...summary,
    ]);
}
Copy this code snippet into the DevTools console Tab to use it
/**
 * PerformanceObserver
 */
const po = new PerformanceObserver((list) => {
    let entries = list.getEntries();
    entries = dedupe(entries, "startTime");
    /**
     * Print all entries of LCP
     */
    entries.forEach((item, i) => {
        console.dir(item);
        console.log(`${i + 1} current LCP item : ${item.element}: ${item.startTime}`);
        /**
         * Highlight LCP elements on the page
         */
        item.element ? (item.element.style = "border: 5px dotted blue;") : console.warn('LCP not highlighted');
    });
    /**
     * LCP is the lastEntry in getEntries Array
     */
    const lastEntry = entries[entries.length - 1];
    /**
     * Print final LCP
     */
    console.log(`LCP is: ${lastEntry.startTime}`);
});
/**
 * Start observing for largest-contentful-paint
 * buffered true getEntries prior to this script execution
 */
po.observe({ type: "largest-contentful-paint", buffered: true });
function dedupe(arr, key) {
    return [...new Map(arr.map((item) => [item[key], item])).values()];
}
Copy this code snippet into the DevTools console Tab to use it
try {
    // Create the performance observer.
    const po = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
            // Log the entry and all associated details.
            console.table(entry.toJSON());
        }
    });
    // Start listening for `longtask` entries to be dispatched.
    po.observe({ type: 'longtask', buffered: true });
}
catch (e) {
    console.log(`The browser doesn't support this API`);
}
Copy this code snippet into the DevTools console Tab to use it
const imgs = document.querySelectorAll('img');
Array.from(imgs)
    .forEach(i => i.setAttribute('loading', 'lazy'));
Copy this code snippet into the DevTools console Tab to use it
function createElementFromHTMLString(htmlString) {
    var div = document.createElement('div');
    div.innerHTML = htmlString.trim();
    return div.firstChild;
}
function cacheInDom(svgElem, svgId) {
    const node = svgElem.cloneNode(svgElem);
    node?.setAttribute && node.setAttribute('id', svgId);
    svgDomCache.appendChild(node);
}
function modifySvgToUseCache(svgElem, svgId) {
    //svgElem.replaceWith(createElementFromHTMLString(`<svg><use href="#${svgId}"></use></svg>`));
    svgElem.innerHTML = `<use href="#${svgId}"></use>`;
}
let nextCachedSvgId = Math.random();
const svgDomCacheHtml = `<div id="svg-cache" style="
    overflow: hidden;
    width: 0;
    height: 0;
    position: fixed;
    bottom: -2000px;
    contain: content;
    content-visibility: auto;
  "></div>`;
const svgDomCache = createElementFromHTMLString(svgDomCacheHtml);
document.body.appendChild(svgDomCache);
let reusedDomNodes = 0;
const cachedSvgContent = new Set();
document.querySelectorAll('svg').forEach(svg => {
    if (svg.children[0].tagName !== 'use') {
        if (!cachedSvgContent.has(svg.innerHTML)) {
            nextCachedSvgId++;
            cachedSvgContent.add(svg.innerHTML);
            cacheInDom(svg, nextCachedSvgId);
        }
        else {
            reusedDomNodes += svg.querySelectorAll('*').length;
        }
        modifySvgToUseCache(svg, nextCachedSvgId);
    }
    else {
        console.info('already optimized');
    }
});
console.log('Reused DOM nodes: ', reusedDomNodes);
Copy this code snippet into the DevTools console Tab to use it
const bi = document.body.innerHTML;
document.body.innerHTML = '';
setTimeout(() => document.body.innerHTML = bi, 350);
Copy this code snippet into the DevTools console Tab to use it
const rels = [
    "preload",
    "prefetch",
    "preconnect",
    "dns-prefetch",
    "preconnect dns-prefetch",
    "prerender",
    "modulepreload",
];
rels.forEach((element) => {
    const linkElements = document.querySelectorAll(`link[rel="${element}"]`);
    const dot = linkElements.length > 0 ? "🟩" : "🟥";
    console.log(`${dot} ${element}`);
    linkElements.forEach((el) => console.log(el));
});
Copy this code snippet into the DevTools console Tab to use it
const scripts = document.querySelectorAll('script[src]');
const scriptsLoading = [...scripts].map((obj) => {
    let newObj = {};
    newObj = {
        src: obj.src,
        async: obj.async,
        defer: obj.defer,
        'render blocking': obj.async || obj.defer ? '' : '🟥'
    };
    return newObj;
});
console.table(scriptsLoading);
Copy this code snippet into the DevTools console Tab to use it
const scrollHeight = document.documentElement.scrollHeight;
window.scroll({
    top: scrollHeight,
    behavior: 'smooth'
});
// wait for a second, then scroll back up
setTimeout(() => window.scroll({
    top: 0,
    behavior: 'smooth'
}), 3000);
console.log('scroll done!');
Copy this code snippet into the DevTools console Tab to use it
new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const resourcesLoaded = [...entries].map((entry) => {
        let obj = {};
        // Some resources may have a responseStart value of 0, due
        // to the resource being cached, or a cross-origin resource
        // being served without a Timing-Allow-Origin header set.
        if (entry.responseStart > 0) {
            obj = {
                'TTFB (ms)': entry.responseStart,
                Resource: entry.name
            };
        }
        return obj;
    });
    console.table(resourcesLoaded);
}).observe({
    type: 'resource',
    buffered: true
});
Copy this code snippet into the DevTools console Tab to use it
new PerformanceObserver((entryList) => {
    const [pageNav] = entryList.getEntriesByType('navigation');
    console.log(`TTFB (ms): ${pageNav.responseStart}`);
}).observe({
    type: 'navigation',
    buffered: true
});

made with ❤ by push-based.io

About

🤓 A comprehensive collection of web performance snippets

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published