Keep both eyes on performance!
Lab Data
module.exports = () => ({
performance: {
hints: "error",
maxEntrypointSize: 100000,
maxAssetSize: 50000,
assetFilter: (assetFilename) => {
return assetFilename.endsWith('.js');
}
}
});
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'
}
});
$ 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
}
}
Lab Data
{
"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"
}
}
Total JS/page
{
1.chunk.js
2.chunk.js
< 300 kB
< 300 kB
< ? kB
CLI
Lab Data
$ 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
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
Real User Data
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");
{
"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
Long tasks
let lastTime = Date.now();
(function detectLongTasks() {
let currentTime = Date.now();
if(currentTime - lastTime > 50) {
// Oups, long frame
}
lastTime = currentTime;
requestAnimationFrame(detectLongTasks);
}());
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"]
});
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);
});
window.addEventListener("unload", () => {
if('sendBeacon' in navigator) {
navigator.sendBeacon("/logs", performanceData);
} else {
// Synchronous XHR POST
}
});
but now what...?
📖 Nice read: