Skip to content

push-based/awesome-web-performance-snippets

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

142 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

 
 
 

Contributors