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!

Keep both eyes on performance

By Pava

Keep both eyes on performance

You've got a fast app... Now what? Well, no time for rest because optimizing it is just the first step. Keeping it fast is the harder part. So, in this talk we'll look at what tools & techniques can help you monitor your performance and keep those metrics in check.

  • 165
Loading comments...

More from Pava