Keep both eyes on performance!

The premise...

Lab Data

🥊 vs 🥊

Real User Data

Webpack

Lab Data

1

module.exports = () => ({
    performance: {
        hints: "error",
        maxEntrypointSize: 100000,
        maxAssetSize: 50000,
        assetFilter: (assetFilename) => {
          return assetFilename.endsWith('.js');
        }
    }
});

👍 multi-page apps

code form iampava

module.exports = () => ({
  entry: {
    setup: './src/javascript/setup.js',
    homePage: './src/javascript/pages/home.page.js',
    servicesPage: './src/javascript/pages/services.page.js',
    eventsPage: './src/javascript/pages/events.page.js',
    articlePage: './src/javascript/pages/article.page.js',

    aboutPage: './src/sass/pages/about.page.scss',
    blogPage: './src/sass/pages/blog.page.scss',
    extrasPage: './src/sass/pages/extras.page.scss',
    cliwPage: './src/sass/pages/cliw.page.scss',
    "inspiring-devsPage": './src/sass/pages/inspiring-devs.page.scss',
    twPage: './src/sass/pages/cliw.page.scss',
    "privacyPage": './src/sass/pages/privacy.page.scss',
    "404Page": './src/sass/pages/404.page.scss'
  }
});

👎 SPA's

If you're brave...

$ webpack --profile --json > compilation-stats.json
{ 
  "errors": [],
  "warnings": [],
  "version": "4.18.1",
  "hash": "2419f19b0aeb4fd26975",
  "time": 184159,
  "builtAt": 1541418496752,
  "publicPath": "/dist/",
  "outputPath": "E:\\Pava\\DevDrive\\devdrive\\dist",
  "assetsByChunkName": {
    "main": [
      "devdrive.e78ca197f90b7292d8b6.css",
      "devdrive.11146fb5cd336c32b239.js"
    ]
  },
  "assets": [
    {
      "name": "53.devdrive.d8b9fabd587a70ef96dc.js",
      "size": 1832,
      "chunks": [
        53
      ],
      "chunkNames": [],
      "emitted": true
    },
    {
      "name": "editor.worker.js",
      "size": 92299,
      "chunks": [],
      "chunkNames": [],
      "emitted": true
    },
    {
      "name": "json.worker.js",
      "size": 184644,
      "chunks": [],
      "chunkNames": [],
      "emitted": true
    },
    {
      "name": "css.worker.js",
      "size": 632206,
      "chunks": [],
      "chunkNames": [],
      "emitted": true,
      "isOverSizeLimit": true
    },
    {
      "name": "html.worker.js",
      "size": 238632,
      "chunks": [],
      "chunkNames": [],
      "emitted": true
    },
    {
      "name": "0.devdrive.8e5cd84205ed9e67b68a.js",
      "size": 2025,
      "chunks": [
        0
      ],
      "chunkNames": [],
      "emitted": true
    },
    {
      "name": "1.devdrive.7ca0c623cb93a924fde4.js",
      "size": 74991,
      "chunks": [
        1
      ],
      "chunkNames": [],
      "emitted": true
    }
}

Bundlesize 📦

Lab Data

2

{
  "bundlesize": [{
    "path": "./dist/**/*.css",
    "compression": "none",
    "maxSize": "50 kB"
   },
   {
     "path": "./dist/**/!(*.worker).js",
     "compression": "none",
     "maxSize": "300 kB"
   },
   {
     "path": "./dist/**/*_high.jpg",
     "maxSize": "800 kB"
   },
   {
     "path": "./dist/**/*_med.jpg",
     "maxSize": "200 kB"
   },
   {
     "path": "./dist/**/*_low.jpg",
     "maxSize": "100 kB"
   },
   {
     "path": "./dist/**/!(*_high|*_med|*_low|*_seo*).jpg",
     "compression": "none",
     "maxSize": "100 kB"
   }],
  "scripts": {
    "perf-test": "bundlesize"
  }
}

Problem

Total JS/page
{
1.chunk.js
2.chunk.js
< 300 kB
< 300 kB
< ? kB

Lighthouse

CLI
Lab Data

3

$ lighthouse http://localhost:1234 
  --output json --output-path ./audit.json
{
"first-contentful-paint": {
      "id": "first-contentful-paint",
      "title": "First Contentful Paint",
      "description": "First Contentful Paint marks the time at which the first text or image is painted. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint).",
      "score": 0.93,
      "scoreDisplayMode": "numeric",
      "rawValue": 2135.077,
      "displayValue": "2.1 s"
    }
}

FCP

{
"first-cpu-idle": {
      "id": "first-cpu-idle",
      "title": "First CPU Idle",
      "description": "First CPU Idle marks the first time at which the page's main thread is quiet enough to handle input. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-interactive).",
      "score": 0.96,
      "scoreDisplayMode": "numeric",
      "rawValue": 2810,
      "displayValue": "2.8 s"
    },
}

TTI

{
"total-byte-weight": {
      "id": "total-byte-weight",
      "title": "Avoids enormous network payloads",
      "description": "Large network payloads cost users real money and are highly correlated with long load times. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/network-payloads).",
      "score": 1,
      "scoreDisplayMode": "numeric",
      "rawValue": 323016,
      "displayValue": "Total size was 315 KB",
      "details": {
        "type": "table",
        "headings": [
          {
            "key": "url",
            "itemType": "url",
            "text": "URL"
          },
          {
            "key": "totalBytes",
            "itemType": "bytes",
            "text": "Size (KB)"
          }
        ],
        "items": [
          {
            "url": "http://localhost:1234/assets/images/landing/bkg_landing_low.jpg",
            "totalBytes": 77018,
            "totalMs": 19.218258017116728
          },
          {
            "url": "http://localhost:1234/dist/devdrive.11146fb5cd336c32b239.js",
            "totalBytes": 74000,
            "totalMs": 18.465178182588975
          },
          {
            "url": "https://use.fontawesome.com/releases/v5.1.1/webfonts/fa-brands-400.woff2",
            "totalBytes": 63671,
            "totalMs": 15.887788649508416
          },
          {
            "url": "http://localhost:1234/dist/assets/fonts/lato-v14-latin-regular.woff2",
            "totalBytes": 23723,
            "totalMs": 5.919586784129166
          },
          {
            "url": "http://localhost:1234/dist/assets/fonts/lato-v14-latin-700.woff2",
            "totalBytes": 23227,
            "totalMs": 5.795820184418839
          },
          {
            "url": "http://localhost:1234/dist/assets/fonts/comfortaa-v12-latin-regular.woff2",
            "totalBytes": 18347,
            "totalMs": 4.5781165421075665
          },
          {
            "url": "http://localhost:1234/dist/assets/fonts/comfortaa-v12-latin-700.woff2",
            "totalBytes": 18171,
            "totalMs": 4.534199361565193
          },
          {
            "url": "https://use.fontawesome.com/releases/v5.1.1/css/all.css",
            "totalBytes": 11740,
            "totalMs": 2.9294755657242506
          },
          {
            "url": "http://localhost:1234/dist/8.devdrive.ba593a76daa4b3a07407.js",
            "totalBytes": 5001,
            "totalMs": 1.2478967039341549
          },
          {
            "url": "http://localhost:1234/dist/devdrive.e78ca197f90b7292d8b6.css",
            "totalBytes": 3965,
            "totalMs": 0.9893842093779094
          }
        ]
      }
    }
}

Total size

{
"render-blocking-resources": {
      "id": "render-blocking-resources",
      "title": "Eliminate render-blocking resources",
      "description": "Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).",
      "score": 0.88,
      "scoreDisplayMode": "numeric",
      "rawValue": 150,
      "displayValue": "Potential savings of 150 ms",
      "details": {
        "type": "opportunity",
        "headings": [
          {
            "key": "url",
            "valueType": "url",
            "label": "URL"
          },
          {
            "key": "totalBytes",
            "valueType": "bytes",
            "label": "Size (KB)"
          },
          {
            "key": "wastedMs",
            "valueType": "timespanMs",
            "label": "Potential Savings (ms)"
          }
        ],
        "items": [
          {
            "url": "http://localhost:1234/dist/devdrive.e78ca197f90b7292d8b6.css",
            "totalBytes": 3965,
            "wastedMs": 180
          },
          {
            "url": "https://use.fontawesome.com/releases/v5.1.1/css/all.css",
            "totalBytes": 11740,
            "wastedMs": 755
          }
        ],
        "overallSavingsMs": 150
      }
    }
}

Render blocking

{
"uses-optimized-images": {
      "id": "uses-optimized-images",
      "title": "Efficiently encode images",
      "description": "Optimized images load faster and consume less cellular data. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/optimize-images).",
      "score": 0.88,
      "scoreDisplayMode": "numeric",
      "rawValue": 150,
      "displayValue": "Potential savings of 15 KB",
      "warnings": [],
      "details": {
        "type": "opportunity",
        "headings": [
          {
            "key": "url",
            "valueType": "thumbnail",
            "label": ""
          },
          {
            "key": "url",
            "valueType": "url",
            "label": "URL"
          },
          {
            "key": "totalBytes",
            "valueType": "bytes",
            "label": "Size (KB)"
          },
          {
            "key": "wastedBytes",
            "valueType": "bytes",
            "label": "Potential Savings (KB)"
          }
        ],
        "items": [
          {
            "url": "http://localhost:1234/assets/images/landing/bkg_landing_low.jpg",
            "fromProtocol": true,
            "isCrossOrigin": false,
            "totalBytes": 76622,
            "wastedBytes": 15680
          }
        ],
        "overallSavingsMs": 150,
        "overallSavingsBytes": 15680
      }
    }
}

Optimized images

and loads loads more!!!!!!!!!!!!!!!

How I'm using it...

And then we can check the metrics and pass/fail the build!

Landing

Home

New exercise

✅ FCP & FMP <  2sec

✅ JS size <  100 kB

✅ FCP & FMP <  2.5sec

✅ JS size <  250 kB

✅ FCP & FMP <  2.5sec

✅ TTI < 3.5 secs

But still Lab Data... 😞

Let's release it into the wild...

Real User Data

Performance API 😎

Did you know the browser stores performance data than can be accessed via JavaScript?

And because it's The Web we can play with it straight in the browser!

✅ navigate to any webpage

✅ open up the console

performance.getEntriesByType("navigation");

👨‍💻

performance.getEntriesByType("paint");

👨‍💻

performance.getEntriesByType("resource");

👨‍💻

Example

{
    "connectEnd": 410,
    "connectStart": 4,
    "decodedBodySize": 7417,
    "domComplete": 2534,
    "domContentLoadedEventEnd": 1151,
    "domContentLoadedEventStart": 1139,
    "domInteractive": 1114,
    "domainLookupEnd": 4,
    "domainLookupStart": 4,
    "duration": 2534,
    "encodedBodySize": 2078,
    "entryType": "navigation",
    "fetchStart": 0,
    "initiatorType": "navigation",
    "loadEventEnd": 2534,
    "loadEventStart": 2534,
    "name": "https://iampava.com/",
    "nextHopProtocol": "h2",
    "redirectCount": 0,
    "redirectEnd": 0,
    "redirectStart": 0,
    "requestStart": 411,
    "responseEnd": 672,
    "responseStart": 667,
    "secureConnectionStart": 126,
    "serverTiming": Array[],
    "startTime": 0,
    "transferSize": 2287,
    "type": "reload",
    "unloadEventEnd": 720,
    "unloadEventStart": 714,
    "workerStart": 0
}
We can get many of the metrics from Lighthouse! ❤

FP & FCP

TTI

this._performanceObserver = new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
    for (const entry of entries) {
      if (entry.entryType === 'resource') {
        this._networkRequestFinishedCallback(entry);
      }
      if (entry.entryType === 'longtask') {
        this._longTaskFinishedCallback(entry);
      }
    }
  });

this._performanceObserver.observe({entryTypes: ['longtask', 'resource']});

Long tasks

Speaking of long tasks...

Long tasks

let lastTime = Date.now();

(function detectLongTasks() {
    let currentTime = Date.now();
    
    if(currentTime - lastTime > 50) {
      // Oups, long frame
    }
    
    lastTime = currentTime;
    requestAnimationFrame(detectLongTasks);
}());

But wait, there's more! 🔥

What I've shown you before is great for initial metrics, but we can even observe them as the app is being used!

let perfObserver = new PerformanceObserver(list => {
  // do smth with list.getEntries
});

perfObserver.observe({
  entryTypes: ["resource", "longtask"]
});

How would I use this?

How about sending them?

1) Send them on unload event

window.addEventListener("unload", () => {
    // POST them to server
});

But...

user agents typically ignore asynchronous XMLHttpRequests made in an unload handler.

2) Use navigator.sendBeacon to do this asynchronously

window.addEventListener("unload", () => {
    navigator.sendBeacon("/logs", performanceData);
});

Or... to be extra safe

window.addEventListener("unload", () => {
  if('sendBeacon' in navigator) {
    navigator.sendBeacon("/logs", performanceData);  
  } else {
    // Synchronous XHR POST
  }  
});
but now what...?

📖 Nice read:

Thank you!