Skip to content

Commit

Permalink
refactor: split optimized images into WebP and optimize audit (#2216)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce authored and paulirish committed May 16, 2017
1 parent 8047ef3 commit 3c752a0
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 131 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 18 additions & 7 deletions lighthouse-cli/test/fixtures/byte-efficiency/tester.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,56 +65,67 @@ <h2>Byte efficiency tester page</h2>

<div class="images">
<!-- FAIL(optimized): image is not JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is used at full size -->
<!-- FAIL(offscreen): image is offscreen -->
<img style="position: absolute; top: -10000px;" src="lighthouse-unoptimized.jpg">

<!-- Image is cross-origin, which are disabled for now -->
<!-- FAIL(optimized): image is not optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is used at full size -->
<!--<img src="http://localhost:10503/byte-efficiency/lighthouse-unoptimized.jpg">-->

<!-- PASSWARN(optimized): image is JPEG optimized but not WebP -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- FAIL(responsive): image is 25% used at DPR 2 -->
<!-- PASS(offscreen): image is onscreen -->
<img style="width: 256px; height: 170px;" src="lighthouse-1024x680.jpg">

<!-- PASSWARN(optimized): image is JPEG optimized but not WebP -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is fully used at DPR 2 -->
<!-- PASS(offscreen): image is onscreen -->
<img style="width: 240px; height: 160px;" src="lighthouse-480x320.jpg">

<!-- PASS(optimized): image has insignificant WebP savings -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is used at full size -->
<!-- PASS(offscreen): image is onscreen -->
<img src="lighthouse-320x212-poor.jpg">

<!-- PASS(optimized): image is fully optimized -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP optimized -->
<!-- PASSWARN(responsive): image is 25% used at DPR 2 (but small savings) -->
<!-- FAIL(offscreen): image is offscreen -->
<img style="margin-top: 1000px; width: 120px; height: 80px;" src="lighthouse-480x320.webp">

<!-- PASS(optimized): image is vector -->
<!-- PASS(webp): image is vector -->
<!-- PASS(responsive): image is vector -->
<!-- FAIL(offscreen): image is offscreen -->
<img style="width: 100px; height: 100px;" src="large.svg">

<!-- PASS(optimized): image has insignificant WebP savings -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is later used at full size -->
<!-- PASS(offscreen): image is later used onscreen -->
<img style="width: 24px; height: 16px;"src="lighthouse-320x212-poor.jpg?duplicate">

<!-- PASS(optimized): image has insignificant WebP savings -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is used at full size -->
<!-- PASS(offscreen): image is onscreen -->
<img class="onscreen" src="lighthouse-320x212-poor.jpg?duplicate">

<!-- PASS(optimized): image is WebP -->
<!-- PASS(webp): image is WebP optimized -->
<!-- FAIL(responsive): image is used at 1/16 size -->
<!-- PASS(offscreen): image is onscreen -->
<div class="onscreen" style="width: 120px; height: 80px; background: 50% 50% url(lighthouse-480x320.webp?css);"></div>

<!-- PASSWARN(optimized): image is JPEG optimized but not WebP -->
<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is used numerous times in sprite-fashion -->
<!-- PASS(offscreen): image is onscreen -->
<div class="onscreen" style="width: 30px; height: 30px; background: 0% 50% url(lighthouse-480x320.jpg?sprite);"></div>
Expand Down
12 changes: 11 additions & 1 deletion lighthouse-cli/test/smokehouse/byte-efficiency/expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module.exports = [
}
}
},
'uses-optimized-images': {
'uses-webp-images': {
// score: 65,
extendedInfo: {
value: {
Expand All @@ -56,6 +56,16 @@ module.exports = [
}
}
},
'uses-optimized-images': {
// score: 65,
extendedInfo: {
value: {
results: {
length: 1
}
}
}
},
'uses-responsive-images': {
// score: 90,
extendedInfo: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,12 @@ class UnusedBytes extends Audit {
const debugString = result.debugString;
const results = result.results
.map(item => {
const wastedPercent = 100 * item.wastedBytes / item.totalBytes;
item.wastedKb = this.bytesToKbString(item.wastedBytes);
item.wastedMs = this.bytesToMsString(item.wastedBytes, networkThroughput);
item.totalKb = this.bytesToKbString(item.totalBytes);
item.totalMs = this.bytesToMsString(item.totalBytes, networkThroughput);
item.potentialSavings = this.toSavingsString(item.wastedBytes, item.wastedPercent);
item.potentialSavings = this.toSavingsString(item.wastedBytes, wastedPercent);
return item;
})
.sort((itemA, itemB) => itemB.wastedBytes - itemA.wastedBytes);
Expand Down
61 changes: 14 additions & 47 deletions lighthouse-core/audits/byte-efficiency/uses-optimized-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,14 @@
*/
/*
* @fileoverview This audit determines if the images used are sufficiently larger
* than Lighthouse optimized versions of the images (as determined by the gatherer).
* Audit will fail if one of the conditions are met:
* * There is at least one JPEG or bitmap image that was >10KB larger than canvas encoded JPEG.
* * There is at least one image that would have saved more than 100KB by using WebP.
* * The savings of moving all images to WebP is greater than 1MB.
* than JPEG compressed images without metadata at quality 85.
*/
'use strict';

const ByteEfficiencyAudit = require('./byte-efficiency-audit');
const URL = require('../../lib/url-shim');

const IGNORE_THRESHOLD_IN_BYTES = 2048;
const TOTAL_WASTED_BYTES_THRESHOLD = 1000 * 1024;
const JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES = 25 * 1024;
const WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES = 100 * 1024;
const IGNORE_THRESHOLD_IN_BYTES = 4096;

class UsesOptimizedImages extends ByteEfficiencyAudit {
/**
Expand All @@ -42,9 +35,8 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
name: 'uses-optimized-images',
description: 'Optimize images',
informative: true,
helpText: 'Images should be optimized to save network bytes. ' +
'The following images could have smaller file sizes when compressed with ' +
'[WebP](https://developers.google.com/speed/webp/) or JPEG at 80 quality. ' +
helpText: 'Optimized images take less time to download and save cellular data. ' +
'The identified images could have smaller file sizes when compressed as JPEG (q=85). ' +
'[Learn more about image optimization](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization).',
requiredArtifacts: ['OptimizedImages', 'devtoolsLogs']
};
Expand All @@ -69,50 +61,27 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
const images = artifacts.OptimizedImages;

const failedImages = [];
let totalWastedBytes = 0;
let hasAllEfficientImages = true;

const results = images.reduce((results, image) => {
const results = [];
images.forEach(image => {
if (image.failed) {
failedImages.push(image);
return results;
} else if (image.originalSize < Math.max(IGNORE_THRESHOLD_IN_BYTES, image.webpSize)) {
return results;
return;
} else if (/(jpeg|bmp)/.test(image.mimeType) === false ||
image.originalSize < image.jpegSize + IGNORE_THRESHOLD_IN_BYTES) {
return;
}

const url = URL.getURLDisplayName(image.url);
const webpSavings = UsesOptimizedImages.computeSavings(image, 'webp');

if (webpSavings.bytes > WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES) {
hasAllEfficientImages = false;
} else if (webpSavings.bytes < IGNORE_THRESHOLD_IN_BYTES) {
return results;
}

let jpegSavingsLabel;
if (/(jpeg|bmp)/.test(image.mimeType)) {
const jpegSavings = UsesOptimizedImages.computeSavings(image, 'jpeg');
if (jpegSavings.bytes > JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES) {
hasAllEfficientImages = false;
}
if (jpegSavings.bytes > IGNORE_THRESHOLD_IN_BYTES) {
jpegSavingsLabel = this.toSavingsString(jpegSavings.bytes, jpegSavings.percent);
}
}

totalWastedBytes += webpSavings.bytes;
const jpegSavings = UsesOptimizedImages.computeSavings(image, 'jpeg');

results.push({
url,
isCrossOrigin: !image.isSameOrigin,
preview: {url: image.url, mimeType: image.mimeType, type: 'thumbnail'},
totalBytes: image.originalSize,
wastedBytes: webpSavings.bytes,
webpSavings: this.toSavingsString(webpSavings.bytes, webpSavings.percent),
jpegSavings: jpegSavingsLabel
wastedBytes: jpegSavings.bytes,
});
return results;
}, []);
});

let debugString;
if (failedImages.length) {
Expand All @@ -124,12 +93,10 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
{key: 'preview', itemType: 'thumbnail', text: ''},
{key: 'url', itemType: 'url', text: 'URL'},
{key: 'totalKb', itemType: 'text', text: 'Original'},
{key: 'webpSavings', itemType: 'text', text: 'Savings as WebP'},
{key: 'jpegSavings', itemType: 'text', text: 'Savings as JPEG'},
{key: 'potentialSavings', itemType: 'text', text: 'Potential Savings'},
];

return {
passes: hasAllEfficientImages && totalWastedBytes < TOTAL_WASTED_BYTES_THRESHOLD,
debugString,
results,
headings
Expand Down
15 changes: 2 additions & 13 deletions lighthouse-core/audits/byte-efficiency/uses-request-compression.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const URL = require('../../lib/url-shim');

const IGNORE_THRESHOLD_IN_BYTES = 1400;
const IGNORE_THRESHOLD_IN_PERCENT = 0.1;
const TOTAL_WASTED_BYTES_THRESHOLD = 10 * 1024; // 10KB

class ResponsesAreCompressed extends ByteEfficiencyAudit {
/**
Expand All @@ -52,7 +51,6 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
static audit_(artifacts) {
const uncompressedResponses = artifacts.ResponseCompression;

let totalWastedBytes = 0;
const results = [];
uncompressedResponses.forEach(record => {
const originalSize = record.resourceSize;
Expand All @@ -76,29 +74,20 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
return;
}

totalWastedBytes += gzipSavings;
const totalBytes = originalSize;
const gzipSavingsBytes = gzipSavings;
const gzipSavingsPercent = 100 * gzipSavingsBytes / totalBytes;
results.push({
url,
totalBytes,
wastedBytes: gzipSavingsBytes,
wastedPercent: gzipSavingsPercent,
potentialSavings: this.toSavingsString(gzipSavingsBytes, gzipSavingsPercent),
totalBytes: originalSize,
wastedBytes: gzipSavings,
});
});

let debugString;
const headings = [
{key: 'url', itemType: 'url', text: 'Uncompressed resource URL'},
{key: 'totalKb', itemType: 'text', text: 'Original'},
{key: 'potentialSavings', itemType: 'text', text: 'GZIP Savings'},
];

return {
passes: totalWastedBytes < TOTAL_WASTED_BYTES_THRESHOLD,
debugString,
results,
headings,
};
Expand Down
16 changes: 7 additions & 9 deletions lighthouse-core/audits/byte-efficiency/uses-responsive-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,26 +88,25 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
const DPR = artifacts.ViewportDimensions.devicePixelRatio;

let debugString;
const resultsMap = images.reduce((results, image) => {
const resultsMap = new Map();
images.forEach(image => {
// TODO: give SVG a free pass until a detail per pixel metric is available
if (!image.networkRecord || image.networkRecord.mimeType === 'image/svg+xml') {
return results;
return;
}

const processed = UsesResponsiveImages.computeWaste(image, DPR);
if (processed instanceof Error) {
debugString = processed.message;
return results;
return;
}

// Don't warn about an image that was later used appropriately
const existing = results.get(processed.preview.url);
const existing = resultsMap.get(processed.preview.url);
if (!existing || existing.wastedBytes > processed.wastedBytes) {
results.set(processed.preview.url, processed);
resultsMap.set(processed.preview.url, processed);
}

return results;
}, new Map());
});

const results = Array.from(resultsMap.values())
.filter(item => item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES);
Expand All @@ -121,7 +120,6 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {

return {
debugString,
passes: !results.find(item => item.isWasteful),
results,
headings,
};
Expand Down
Loading

0 comments on commit 3c752a0

Please sign in to comment.