GithubHelp home page GithubHelp logo

timswinkels / webperf-snippets Goto Github PK

View Code? Open in Web Editor NEW

This project forked from nucliweb/webperf-snippets

0.0 0.0 0.0 67 KB

โšก๏ธ ๐Ÿ’พ Web Performance Snippets

License: MIT License

webperf-snippets's Introduction

โšก๏ธ๐Ÿ’พ Web Performance Snippets

A curated list of snippets to get Web Performance metrics to use in the browser console

Core Web Vitals

Largest Contentful Paint (LCP)

/**
 * 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;") : "";
  });

  /**
   * 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()];
}

Cumulative Layout Shift

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`);
}

Loading

Time To First Byte

Measure the time to first byte, from the document

new PerformanceObserver((entryList) => {
  const [pageNav] = entryList.getEntriesByType('navigation')
  console.log(`TTFB (ms): ${pageNav.responseStart}`)
}).observe({
  type: 'navigation',
  buffered: true
})

Measure the time to first byte of all the resources loaded

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
})

Scripts Loading

List all the <scripts> in the DOM and show a table to see if are loaded async and/or defer

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);

Resources hints

Check is the page has resources hints

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));
});

Find Above The Fold Lazy Loaded Images

List all images that have loading="lazy" or [data-src] (lazy loading via JS) above the fold

function findATFLazyLoadedImages() {
  const lazy = document.querySelectorAll('[loading="lazy"], [data-src]');
  let lazyImages = [];
  lazy.forEach((tag) => {
    const position = parseInt(tag.getBoundingClientRect().top);
    if (position < window.innerHeight && position !== 0) {
      lazyImages = [...lazyImages, tag];
    }
  });
  return lazyImages.length > 0 ? lazyImages : false;
}

console.log(findATFLazyLoadedImages());

Image Info

List all image resources and sort by (name, transferSize, encodedBodySize, decodedBodySize, initiatorType)

More Info

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"));

First And Third Party Script Info

List all scripts using PerformanceResourceTiming API and separating them by first and third party

More Info

Info On CORS

// 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)) {
          const json = resource.toJSON();
          first.push({ ...json, type: "First Party" });
        } else {
          // add to third party script
          const json = resource.toJSON();
          third.push({ ...json, 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();
*/

First And Third Party Script Timings

This relies on the above script

Run First And Third Party Script Info in the console first, then run this

Calculate Load Times - MDN

Info on CORS (why some values are 0)

Note: The properties which are returned as 0 by default when loading a resource from a domain other than the one of the web page itself: redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, and responseStart.


More Info on TAO header - Akamai Developer Resources

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"));

Interaction

Long Task

To determine when long tasks happen, you can use PerformanceObserver and register to observe entries of type longtask:

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`)
}

Layout Shifts

To find more specific information about layout shifts, you can use PerformanceObserver and register to observe entries of type layout-shift:

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"] });

Print al the CLS metrics when load the page and the user interactive with the page:

new PerformanceObserver(entryList => {
    console.log(entryList.getEntries());
}).observe({ type: "layout-shift", buffered: true });

webperf-snippets's People

Contributors

nucliweb avatar jhadev avatar guaca avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.