From 8e941233a0bf04b6b7e4662133da633aadb787d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Sat, 12 Aug 2023 04:29:43 +0200
Subject: [PATCH 1/7] Revert the image reformater (moved to Phantomas)
---
lib/metadata/policies.js | 2 +-
lib/tools/redownload/imageReformater.js | 138 ------------------------
lib/tools/redownload/redownload.js | 72 +------------
test/core/imageReformaterTest.js | 122 ---------------------
4 files changed, 2 insertions(+), 332 deletions(-)
delete mode 100644 lib/tools/redownload/imageReformater.js
delete mode 100644 test/core/imageReformaterTest.js
diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js
index 7214982..322e35d 100644
--- a/lib/metadata/policies.js
+++ b/lib/metadata/policies.js
@@ -747,7 +747,7 @@ var policies = {
"unit": 'bytes'
},
"oldImageFormats": {
- "tool": "redownload",
+ "tool": "phantomas",
"label": "Old image formats",
"message": "Measures the number of bytes that could be saved by converting images to newer and more efficient formats. The best image format is generally AVIF and the second best is WebP.
Be careful, you need to provide fallback images for old browsers and search engine bots.
",
"isOkThreshold": 30720,
diff --git a/lib/tools/redownload/imageReformater.js b/lib/tools/redownload/imageReformater.js
deleted file mode 100644
index c882c6c..0000000
--- a/lib/tools/redownload/imageReformater.js
+++ /dev/null
@@ -1,138 +0,0 @@
-var debug = require('debug')('ylt:imageReformater');
-var sharp = require('sharp');
-
-// Disable sharp cache to reduce the "disk is full" error on Amazon Lambda
-sharp.cache(false);
-
-var ImageOptimizer = function() {
-
- // https://www.industrialempathy.com/posts/avif-webp-quality-settings
- const WEBP_QUALITY = 82;
- const AVIF_QUALITY = 64;
-
- async function reformatImage(entry) {
- if (!entry.weightCheck || !entry.weightCheck.bodyBuffer) {
- // No valid file available
- return entry;
- }
-
- var fileSize = entry.weightCheck.uncompressedSize;
- debug('Let\'s try to convert %s to other image formats', entry.url);
- debug('Current file size is %d', fileSize);
-
- var animated = await isAnimated(entry);
- debug('Check if the file is animated: %s', animated);
-
-
- if (isJPEG(entry) || isPNG(entry)) {
- debug('File is %s, let\'s try to convert it to WebP', entry.contentType);
-
- try {
-
- const webpFile = await convertToWebp(entry.weightCheck.bodyBuffer, animated);
-
- if (webpFile) {
- var webpFileSize = webpFile.length;
-
- debug('WebP transformation complete for %s', entry.url);
- debug('WebP size is %d bytes', webpFileSize);
-
- if (webpFile.length > 0 && gainIsEnough(fileSize, webpFileSize)) {
- entry.weightCheck.webpSize = webpFileSize;
- debug('WebP size is %d bytes smaller (-%d%)', fileSize - webpFileSize, Math.round((fileSize - webpFileSize) * 100 / fileSize));
- }
-
- } else {
- debug('Convertion to WebP didn\'t work');
- }
-
- } catch(err) {
- debug('Error while converting to WebP, ignoring');
- }
- }
-
- if (!animated && (isJPEG(entry) || isPNG(entry) || isWebP(entry))) {
- debug('File is %s and is not animated, let\'s try to convert it to AVIF', entry.contentType);
-
- try {
-
- const avifFile = await convertToAvif(entry.weightCheck.bodyBuffer);
-
- if (avifFile) {
- var avifFileSize = avifFile.length;
-
- debug('AVIF transformation complete for %s', entry.url);
- debug('AVIF size is %d bytes', avifFileSize);
-
- if (avifFile.length > 0 && gainIsEnough(fileSize, avifFileSize)) {
- entry.weightCheck.avifSize = avifFileSize;
- debug('AVIF size is %d bytes smaller (-%d%)', fileSize - avifFileSize, Math.round((fileSize - avifFileSize) * 100 / fileSize));
- }
-
- } else {
- debug('Convertion to AVIF didn\'t work');
- }
-
- } catch(err) {
- debug('Error while converting to AVIF, ignoring');
- }
- }
-
- return entry;
- }
-
- async function convertToWebp(bodyBuffer, isAnimated) {
- return sharp(bodyBuffer, {animated: isAnimated})
- .webp({quality: WEBP_QUALITY, alphaQuality: WEBP_QUALITY})
- .toBuffer();
- }
-
- async function convertToAvif(bodyBuffer) {
- return sharp(bodyBuffer)
- .webp({quality: AVIF_QUALITY})
- .toBuffer();
- }
-
- // The gain is estimated of enough value if it's over 2KB or over 20%,
- // but it's ignored if is below 100 bytes
- function gainIsEnough(oldWeight, newWeight) {
- var gain = oldWeight - newWeight;
- var ratio = gain / oldWeight;
- return (gain > 2048 || (ratio > 0.2 && gain > 100));
- }
-
- function isJPEG(entry) {
- return entry.isImage && entry.contentType === 'image/jpeg';
- }
-
- function isPNG(entry) {
- return entry.isImage && entry.contentType === 'image/png';
- }
-
- function isWebP(entry) {
- return entry.isImage && entry.contentType === 'image/webp';
- }
-
- function entryTypeCanBeReformated(entry) {
- return isJPEG(entry) || isPNG(entry) || isWebP(entry);
- }
-
- async function isAnimated(entry) {
- if (isWebP(entry)) {
- const metadata = await sharp(entry.weightCheck.bodyBuffer).metadata();
- return metadata.pages > 1;
- }
- return false;
- }
-
- return {
- reformatImage: reformatImage,
- convertToWebp: convertToWebp,
- convertToAvif: convertToAvif,
- gainIsEnough: gainIsEnough,
- entryTypeCanBeReformated: entryTypeCanBeReformated,
- isAnimated: isAnimated
- };
-};
-
-module.exports = new ImageOptimizer();
\ No newline at end of file
diff --git a/lib/tools/redownload/redownload.js b/lib/tools/redownload/redownload.js
index c567729..58fce88 100644
--- a/lib/tools/redownload/redownload.js
+++ b/lib/tools/redownload/redownload.js
@@ -15,7 +15,6 @@ var request = require('request');
var md5 = require('md5');
var imageOptimizer = require('./imageOptimizer');
-var imageReformater = require('./imageReformater');
var fileMinifier = require('./fileMinifier');
var gzipCompressor = require('./gzipCompressor');
var brotliCompressor = require('./brotliCompressor');
@@ -79,10 +78,6 @@ var Redownload = function() {
.then(imageOptimizer.optimizeImage)
- .then(function(entry) {
- return Q(imageReformater.reformatImage(entry));
- })
-
.then(imageDimensions.getDimensions)
.then(fileMinifier.minifyFile)
@@ -96,7 +91,7 @@ var Redownload = function() {
})
.then(function(newEntry) {
- debug('File %s - Redownloaded, optimized, reformated, minified, compressed, analyzed: done', entry.url);
+ debug('File %s - Redownloaded, optimized, minified, compressed, analyzed: done', entry.url);
// For the progress bar
doneCount ++;
@@ -159,10 +154,6 @@ var Redownload = function() {
offenders.imageOptimization = listImagesNotOptimized(results);
metrics.imageOptimization = offenders.imageOptimization.totalGain;
- // Old image formats
- offenders.oldImageFormats = listImagesWithOldFormats(results);
- metrics.oldImageFormats = offenders.oldImageFormats.totalGain;
-
// Image width
offenders.imagesTooLarge = listImagesTooLarge(results, data.params.options.device);
metrics.imagesTooLarge = offenders.imagesTooLarge.length;
@@ -407,67 +398,6 @@ var Redownload = function() {
return results;
}
- function listImagesWithOldFormats(requests) {
- var results = {
- totalGain: 0,
- images: []
- };
-
- requests.forEach(function(req) {
-
- if (req.weightCheck.bodySize > 0 &&
- imageReformater.entryTypeCanBeReformated(req) &&
- (req.weightCheck.webpSize > 0 || req.weightCheck.avifSize > 0)) {
-
- var image = {
- url: req.url,
- originalWeigth: req.weightCheck.bodySize,
- };
-
- switch (req.contentType) {
- case 'image/jpeg':
- image.originalFormat = 'JPEG';
- break;
- case 'image/png':
- image.originalFormat = 'PNG';
- break;
- case 'image/gif':
- image.originalFormat = 'GIF';
- break;
- case 'image/webp':
- image.originalFormat = 'WebP';
- break;
- case 'image/avif':
- image.originalFormat = 'AVIF';
- break;
- }
-
- if (req.weightCheck.webpSize) {
- image.webpSize = req.weightCheck.webpSize;
- image.webpGain = req.weightCheck.bodySize - req.weightCheck.webpSize;
-
- image.bestFormat = 'WebP';
- image.maxGain = image.webpGain;
- }
-
- if (req.weightCheck.avifSize) {
- image.avifSize = req.weightCheck.avifSize;
- image.avifGain = req.weightCheck.bodySize - req.weightCheck.avifSize;
-
- if (!req.weightCheck.webpSize || req.weightCheck.webpSize > req.weightCheck.avifSize) {
- image.bestFormat = 'AVIF';
- image.maxGain = image.avifGain;
- }
- }
-
- results.totalGain += image.maxGain;
- results.images.push(image);
- }
- });
-
- return results;
- }
-
function listImagesTooLarge(requests, device) {
var results = [];
diff --git a/test/core/imageReformaterTest.js b/test/core/imageReformaterTest.js
deleted file mode 100644
index 07d6541..0000000
--- a/test/core/imageReformaterTest.js
+++ /dev/null
@@ -1,122 +0,0 @@
-var should = require('chai').should();
-var imageReformater = require('../../lib/tools/redownload/imageReformater');
-var fs = require('fs');
-var path = require('path');
-
-describe('imageReformater', function() {
-
- it('should convert a JPEG image to WebP and AVIF', async function() {
- var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg'));
- let entry = {
- isImage: true,
- type: 'image',
- contentType: 'image/jpeg',
- weightCheck: {
- bodyBuffer: fileContent,
- uncompressedSize: fileContent.length
- }
- };
-
- var newEntry = await imageReformater.reformatImage(entry);
-
- newEntry.weightCheck.should.have.a.property('webpSize');
- newEntry.weightCheck.webpSize.should.be.below(fileContent.length);
-
- newEntry.weightCheck.should.have.a.property('avifSize');
- newEntry.weightCheck.avifSize.should.be.below(fileContent.length);
- });
-
- it('should convert a PNG image to WebP and AVIF', async function() {
- var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg'));
- let entry = {
- isImage: true,
- type: 'image',
- contentType: 'image/png',
- weightCheck: {
- bodyBuffer: fileContent,
- uncompressedSize: fileContent.length
- }
- };
-
- var newEntry = await imageReformater.reformatImage(entry);
-
- newEntry.weightCheck.should.have.a.property('webpSize');
- newEntry.weightCheck.webpSize.should.be.below(fileContent.length);
-
- newEntry.weightCheck.should.have.a.property('avifSize');
- newEntry.weightCheck.avifSize.should.be.below(fileContent.length);
- });
-
- it('should convert a WebP image to AVIF', async function() {
- var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg'));
- let entry = {
- isImage: true,
- type: 'image',
- contentType: 'image/webp',
- weightCheck: {
- bodyBuffer: fileContent,
- uncompressedSize: fileContent.length
- }
- };
-
- var newEntry = await imageReformater.reformatImage(entry);
-
- newEntry.weightCheck.should.not.have.a.property('webpSize');
-
- newEntry.weightCheck.should.have.a.property('avifSize');
- newEntry.weightCheck.avifSize.should.be.below(fileContent.length);
- });
-
- it('should recognize an animated WebP', async function() {
- // Test on an animated image
- let fileContent = fs.readFileSync(path.resolve(__dirname, '../www/animated.webp'));
- let entry = {
- isImage: true,
- type: 'image',
- contentType: 'image/webp',
- weightCheck: {
- bodyBuffer: fileContent,
- uncompressedSize: fileContent.length
- }
- };
-
- (await imageReformater.isAnimated(entry)).should.equal(true);
-
- // Test on a not animated image
- fileContent = fs.readFileSync(path.resolve(__dirname, '../www/monster.webp'));
- entry.weightCheck.bodyBuffer = fileContent;
- (await imageReformater.isAnimated(entry)).should.equal(false);
- });
-
- it('should not convert an animated WebP', async function() {
- // Test on an animated image
- let fileContent = fs.readFileSync(path.resolve(__dirname, '../www/animated.webp'));
- let entry = {
- isImage: true,
- type: 'image',
- contentType: 'image/webp',
- weightCheck: {
- bodyBuffer: fileContent,
- uncompressedSize: fileContent.length
- }
- };
-
- var newEntry = await imageReformater.reformatImage(entry);
-
- // Test on a not animated image
- newEntry.weightCheck.should.not.have.a.property('avifSize');
- });
-
- it('should determine if gain is enough', function() {
- imageReformater.gainIsEnough(20000, 10000).should.equal(true);
- imageReformater.gainIsEnough(2000, 1000).should.equal(true);
- imageReformater.gainIsEnough(20000, 21000).should.equal(false);
- imageReformater.gainIsEnough(20000, 40000).should.equal(false);
- imageReformater.gainIsEnough(20000, 19500).should.equal(false);
- imageReformater.gainIsEnough(250, 120).should.equal(true);
- imageReformater.gainIsEnough(200, 120).should.equal(false);
- imageReformater.gainIsEnough(2000, 1900).should.equal(false);
- imageReformater.gainIsEnough(200000, 197000).should.equal(true);
- });
-
-});
From c308850eb3ec4025cbb1bbf76b9c3fdbf55735ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Mon, 14 Aug 2023 01:54:17 +0200
Subject: [PATCH 2/7] WIP new image analizis module in Phantomas
---
lib/metadata/policies.js | 74 +++++++++++++++++++++----
lib/metadata/scoreProfileGeneric.json | 51 ++++++++---------
lib/rulesChecker.js | 9 ++-
lib/tools/phantomas/phantomasWrapper.js | 7 +++
lib/tools/redownload/redownload.js | 24 ++++----
package.json | 2 +-
test/core/redownloadTest.js | 11 ----
7 files changed, 117 insertions(+), 61 deletions(-)
diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js
index 322e35d..2db8df1 100644
--- a/lib/metadata/policies.js
+++ b/lib/metadata/policies.js
@@ -736,30 +736,82 @@ var policies = {
"hasOffenders": true,
"unit": 'bytes'
},
- "imageOptimization": {
- "tool": "redownload",
+ "imagesNotOptimized": {
+ "tool": "phantomas",
"label": "Image optimization",
"message": "This metric measures the number of bytes that could be saved by optimizing images.
Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they're not very good for optimization. Use specialized tools such as Kraken.io or the excellent ImageOptim on Mac. For SVG images, you can use SVGOMG.
The tools in use in YellowLabTools are not set to their maximum optimization power (JPEG quality 85), so you might be able to compress even more!
",
- "isOkThreshold": 20480,
+ "isOkThreshold": 2048,
"isBadThreshold": 204800,
"isAbnormalThreshold": 307200,
"hasOffenders": true,
- "unit": 'bytes'
+ "unit": 'bytes',
+ "valueTransformFn": function(offenders) {
+ let totalGain = 0;
+ offenders.forEach((offender) => {
+ offender.gain = offender.fileSize - offender.newFileSize;
+ totalGain += offender.gain;
+ });
+ return totalGain;
+ },
+ "offendersTransformFn": function(offenders) {
+ return offenders;
+ }
},
- "oldImageFormats": {
+ "imagesOldFormat": {
"tool": "phantomas",
"label": "Old image formats",
- "message": "Measures the number of bytes that could be saved by converting images to newer and more efficient formats. The best image format is generally AVIF and the second best is WebP.
Be careful, you need to provide fallback images for old browsers and search engine bots.
",
- "isOkThreshold": 30720,
+ "message": "This metric goes further than \"Image optimization\". Measures the number of bytes that could be saved by converting images to newer and more efficient formats. The best image format is generally AVIF and the second best is WebP.
Be careful, you need to provide fallback images for old browsers and search engine bots.
",
+ "isOkThreshold": 2048,
"isBadThreshold": 307200,
"isAbnormalThreshold": 512000,
"hasOffenders": true,
- "unit": 'bytes'
+ "unit": 'bytes',
+ "valueTransformFn": function(offenders) {
+ let totalGain = 0;
+ offenders.forEach((offender) => {
+ offender.gain = offender.fileSize - offender.newFileSize;
+ totalGain += offender.gain;
+ });
+ return totalGain;
+ },
+ "offendersTransformFn": function(offenders) {
+ return offenders;
+ }
},
- "imagesTooLarge": {
- "tool": "redownload",
+ "imagesScaledDown": {
+ "tool": "phantomas",
"label": "Oversized images",
- "message": "This is the number of images with a width >1200px on mobile, >1800px on tablet, >2400 on desktop, >3200px on HD desktop. Try reducing their size.
Please ignore if the file is used as a sprite.
",
+ "message": "This rule compares the number of pixels in a loaded images to the number of physical pixels it is displayed on. Then it estimates the number of KB that could be saved by serving it with the correct dimensions.
Of course, it is hard to serve perfect images for all screens. For this reason, this rule is quite permissive.
",
+ "isOkThreshold": 2048,
+ "isBadThreshold": 307200,
+ "isAbnormalThreshold": 512000,
+ "hasOffenders": true,
+ "unit": 'bytes',
+ "valueTransformFn": function(offenders) {
+ let totalGain = 0;
+ offenders.forEach((offender) => {
+ offender.gain = offender.fileSize - offender.newFileSize;
+ totalGain += offender.gain;
+ });
+ return totalGain;
+ },
+ "offendersTransformFn": function(offenders) {
+ return offenders;
+ }
+ },
+ "imagesExcessiveDensity": {
+ "tool": "phantomas",
+ "label": "Excessive image density",
+ "message": "Devices with very high pixel density screen (such as 3x or 4x) are programmed to load high density images. This is the normal behavior, however the human eye barely sees the difference over 2x. This metric alerts you if an image density is > 2.2x.
There is currently no browser functionnality to prevent the issue (for this reason its impact on global score is low). But you can build your own clever solution!
",
+ "isOkThreshold": 0,
+ "isBadThreshold": 10,
+ "isAbnormalThreshold": 20,
+ "hasOffenders": true
+ },
+ "imagesWithIncorrectSizesParam": {
+ "tool": "phantomas",
+ "label": "Incorrect sizes parameter",
+ "message": "When using an adaptative image with a srcset attribute and w values, it is important to correctly set the sizes attribute. Otherwise, the browser might pick the wrong image in the srcset.
The Responsive Image Linter extension for Chrome can help you further.
",
"isOkThreshold": 0,
"isBadThreshold": 5,
"isAbnormalThreshold": 10,
diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json
index 4ecdc42..3fa9add 100644
--- a/lib/metadata/scoreProfileGeneric.json
+++ b/lib/metadata/scoreProfileGeneric.json
@@ -1,24 +1,37 @@
{
+ "globalScore": {
+ "pageWeight": 2,
+ "images": 2,
+ "domComplexity": 1,
+ "javascriptComplexity": 2,
+ "badJavascript": 2,
+ "jQuery": 0.5,
+ "cssComplexity": 0.5,
+ "badCSS": 1,
+ "fonts": 1,
+ "serverConfig": 1
+ },
"categories": {
"pageWeight": {
- "label": "Page weight",
+ "label": "Network",
"policies": {
"totalWeight": 5,
- "imageOptimization": 2,
- "oldImageFormats": 2,
- "imagesTooLarge": 1,
"compression": 2,
- "fileMinification": 2
- }
- },
- "requests": {
- "label": "Requests",
- "policies": {
- "totalRequests": 2,
- "domains": 3,
- "notFound": 2,
+ "fileMinification": 2,
"identicalFiles": 2,
"emptyRequests": 3,
+ "notFound": 2,
+ "domains": 3
+ }
+ },
+ "images": {
+ "label": "Images",
+ "policies": {
+ "imagesNotOptimized": 2,
+ "imagesOldFormat": 2,
+ "imagesScaledDown": 2,
+ "imagesExcessiveDensity": 0.25,
+ "imagesWithIncorrectSizesParam": 1,
"lazyLoadableImagesBelowTheFold": 2,
"hiddenImages": 1
}
@@ -102,17 +115,5 @@
"cachingTooShort": 1
}
}
- },
- "globalScore": {
- "pageWeight": 3,
- "requests": 2,
- "domComplexity": 2,
- "javascriptComplexity": 2,
- "badJavascript": 2,
- "jQuery": 0.5,
- "cssComplexity": 0.5,
- "badCSS": 1,
- "fonts": 1,
- "serverConfig": 1
}
}
\ No newline at end of file
diff --git a/lib/rulesChecker.js b/lib/rulesChecker.js
index eaffd6e..f5f7824 100644
--- a/lib/rulesChecker.js
+++ b/lib/rulesChecker.js
@@ -57,9 +57,16 @@ var RulesChecker = function() {
data.toolsResults[policy.tool].offenders[metricName]) {
offenders = data.toolsResults[policy.tool].offenders[metricName];
}
+
+ // It is possible to declare a transformation function for the main metric value.
+ // The function should
+ if (policy.valueTransformFn) {
+ rule.value = policy.valueTransformFn(offenders);
+ }
+
var offendersObj = {};
-
+
// It is possible to declare a transformation function for the offenders.
// The function should take an array of strings as single parameter and return a string.
if (policy.offendersTransformFn) {
diff --git a/lib/tools/phantomas/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js
index e71039e..ac2ac31 100644
--- a/lib/tools/phantomas/phantomasWrapper.js
+++ b/lib/tools/phantomas/phantomasWrapper.js
@@ -47,6 +47,7 @@ var PhantomasWrapper = function() {
// Mandatory
'analyze-css': true,
+ 'analyze-images': true,
'ignoreSslErrors': true, // until Phantomas 2.1
'ignore-ssl-errors': true // for Phantomas >= 2.2
};
@@ -80,6 +81,12 @@ var PhantomasWrapper = function() {
offenders: results.getAllOffenders()
};
+ // Special rules here
+ if (task.options.device !== 'phone') {
+ delete json.metrics.imagesExcessiveDensity;
+ delete json.offenders.imagesExcessiveDensity;
+ }
+
deferred.resolve(json);
}).
catch(res => {
diff --git a/lib/tools/redownload/redownload.js b/lib/tools/redownload/redownload.js
index 58fce88..dc50300 100644
--- a/lib/tools/redownload/redownload.js
+++ b/lib/tools/redownload/redownload.js
@@ -14,13 +14,13 @@ var async = require('async');
var request = require('request');
var md5 = require('md5');
-var imageOptimizer = require('./imageOptimizer');
+//var imageOptimizer = require('./imageOptimizer');
var fileMinifier = require('./fileMinifier');
var gzipCompressor = require('./gzipCompressor');
var brotliCompressor = require('./brotliCompressor');
var contentTypeChecker = require('./contentTypeChecker');
var fontAnalyzer = require('./fontAnalyzer');
-var imageDimensions = require('./imageDimensions');
+//var imageDimensions = require('./imageDimensions');
var Redownload = function() {
@@ -76,9 +76,9 @@ var Redownload = function() {
.then(contentTypeChecker.checkContentType)
- .then(imageOptimizer.optimizeImage)
+ //.then(imageOptimizer.optimizeImage)
- .then(imageDimensions.getDimensions)
+ //.then(imageDimensions.getDimensions)
.then(fileMinifier.minifyFile)
@@ -151,12 +151,12 @@ var Redownload = function() {
});
// Image compression
- offenders.imageOptimization = listImagesNotOptimized(results);
- metrics.imageOptimization = offenders.imageOptimization.totalGain;
+ //offenders.imageOptimization = listImagesNotOptimized(results);
+ //metrics.imageOptimization = offenders.imageOptimization.totalGain;
// Image width
- offenders.imagesTooLarge = listImagesTooLarge(results, data.params.options.device);
- metrics.imagesTooLarge = offenders.imagesTooLarge.length;
+ //offenders.imagesTooLarge = listImagesTooLarge(results, data.params.options.device);
+ //metrics.imagesTooLarge = offenders.imagesTooLarge.length;
// File minification
offenders.fileMinification = listFilesNotMinified(results);
@@ -341,7 +341,7 @@ var Redownload = function() {
}
- function listImagesNotOptimized(requests) {
+ /*function listImagesNotOptimized(requests) {
var results = {
totalGain: 0,
images: []
@@ -396,9 +396,9 @@ var Redownload = function() {
}
});
return results;
- }
+ }*/
- function listImagesTooLarge(requests, device) {
+ /*function listImagesTooLarge(requests, device) {
var results = [];
requests.forEach(function(req) {
@@ -423,7 +423,7 @@ var Redownload = function() {
});
return results;
- }
+ }*/
function listFilesNotMinified(requests) {
diff --git a/package.json b/package.json
index ef327b9..7192a98 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"md5": "2.3.0",
"meow": "5.0.0",
"parse-color": "1.0.0",
- "phantomas": "2.8.0",
+ "phantomas": "gmetais/phantomas#analyze-image",
"q": "1.5.1",
"request": "2.88.2",
"sharp": "0.32.3",
diff --git a/test/core/redownloadTest.js b/test/core/redownloadTest.js
index ada3e22..4da5f34 100644
--- a/test/core/redownloadTest.js
+++ b/test/core/redownloadTest.js
@@ -82,17 +82,6 @@ describe('redownload', function() {
data.toolsResults.redownload.offenders.totalWeight.byType.image.requests.length.should.equal(2);
data.toolsResults.redownload.offenders.totalWeight.byType.other.requests.length.should.equal(1);
- data.toolsResults.redownload.offenders.should.have.a.property('imageOptimization');
- data.toolsResults.redownload.offenders.imageOptimization.totalGain.should.be.above(0);
- data.toolsResults.redownload.offenders.imageOptimization.images.length.should.equal(2);
-
- data.toolsResults.redownload.offenders.should.have.a.property('oldImageFormats');
- data.toolsResults.redownload.offenders.oldImageFormats.totalGain.should.be.above(0);
- data.toolsResults.redownload.offenders.oldImageFormats.images.length.should.equal(1);
-
- data.toolsResults.redownload.offenders.should.have.a.property('imagesTooLarge');
- data.toolsResults.redownload.offenders.imagesTooLarge.length.should.equal(0);
-
data.toolsResults.redownload.offenders.should.have.a.property('compression');
data.toolsResults.redownload.offenders.compression.totalGain.should.be.above(0);
data.toolsResults.redownload.offenders.compression.files.length.should.equal(5);
From e680f429eb77a81b3091ceeb902d584f93907ed5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Wed, 16 Aug 2023 01:05:13 +0200
Subject: [PATCH 3/7] Re-introduce the Requests Number metric
---
lib/metadata/scoreProfileGeneric.json | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json
index 3fa9add..0962689 100644
--- a/lib/metadata/scoreProfileGeneric.json
+++ b/lib/metadata/scoreProfileGeneric.json
@@ -16,19 +16,20 @@
"label": "Network",
"policies": {
"totalWeight": 5,
+ "totalRequests": 2,
+ "domains": 3,
"compression": 2,
"fileMinification": 2,
"identicalFiles": 2,
"emptyRequests": 3,
"notFound": 2,
- "domains": 3
}
},
"images": {
"label": "Images",
"policies": {
"imagesNotOptimized": 2,
- "imagesOldFormat": 2,
+ "imagesOldFormat": 1,
"imagesScaledDown": 2,
"imagesExcessiveDensity": 0.25,
"imagesWithIncorrectSizesParam": 1,
From 538d734a5b1ad924b06e3a000dbff476f8ad3a48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Wed, 16 Aug 2023 01:07:17 +0200
Subject: [PATCH 4/7] Switch imageExcessiveDensity to KB instead of simple
count
---
lib/metadata/policies.js | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js
index 2db8df1..d399031 100644
--- a/lib/metadata/policies.js
+++ b/lib/metadata/policies.js
@@ -739,7 +739,7 @@ var policies = {
"imagesNotOptimized": {
"tool": "phantomas",
"label": "Image optimization",
- "message": "This metric measures the number of bytes that could be saved by optimizing images.
Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they're not very good for optimization. Use specialized tools such as Kraken.io or the excellent ImageOptim on Mac. For SVG images, you can use SVGOMG.
The tools in use in YellowLabTools are not set to their maximum optimization power (JPEG quality 85), so you might be able to compress even more!
",
+ "message": "This metric measures the number of bytes that could be saved by optimizing images.
Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they're not very good for optimization. Use specialized tools such as Kraken.io or the excellent ImageOptim on Mac. For SVG images, you can use SVGOMG.
The tools in use in YellowLabTools are not set to their maximum optimization power, so you might be able to compress even more!
",
"isOkThreshold": 2048,
"isBadThreshold": 204800,
"isAbnormalThreshold": 307200,
@@ -762,8 +762,8 @@ var policies = {
"label": "Old image formats",
"message": "This metric goes further than \"Image optimization\". Measures the number of bytes that could be saved by converting images to newer and more efficient formats. The best image format is generally AVIF and the second best is WebP.
Be careful, you need to provide fallback images for old browsers and search engine bots.
",
"isOkThreshold": 2048,
- "isBadThreshold": 307200,
- "isAbnormalThreshold": 512000,
+ "isBadThreshold": 512000,
+ "isAbnormalThreshold": 819200,
"hasOffenders": true,
"unit": 'bytes',
"valueTransformFn": function(offenders) {
@@ -802,11 +802,23 @@ var policies = {
"imagesExcessiveDensity": {
"tool": "phantomas",
"label": "Excessive image density",
- "message": "Devices with very high pixel density screen (such as 3x or 4x) are programmed to load high density images. This is the normal behavior, however the human eye barely sees the difference over 2x. This metric alerts you if an image density is > 2.2x.
There is currently no browser functionnality to prevent the issue (for this reason its impact on global score is low). But you can build your own clever solution!
",
+ "message": "This metric measures the number of bytes that could be saved by going further than just resizing images.
Devices with very high pixel density screen (such as 3x or 4x) are programmed to load high density images. This is the normal behavior, however the human eye barely sees the difference over 2x. This metric alerts you if an image density is > 2.2x.
There is currently no browser functionnality to prevent the issue (for this reason its impact on global score is low). But you can build your own clever solution!
",
"isOkThreshold": 0,
"isBadThreshold": 10,
"isAbnormalThreshold": 20,
- "hasOffenders": true
+ "hasOffenders": true,
+ "unit": 'bytes',
+ "valueTransformFn": function(offenders) {
+ let totalGain = 0;
+ offenders.forEach((offender) => {
+ offender.gain = offender.fileSize - offender.newFileSize;
+ totalGain += offender.gain;
+ });
+ return totalGain;
+ },
+ "offendersTransformFn": function(offenders) {
+ return offenders;
+ }
},
"imagesWithIncorrectSizesParam": {
"tool": "phantomas",
@@ -840,7 +852,7 @@ var policies = {
"totalRequests": {
"tool": "redownload",
"label": "Requests number",
- "message": "Each request slows down the page loading, especially on the protocol HTTP/1, but also a little on HTTP/2.
There are several technics to reduce their number:
- Concatenate JS files
- Concatenate CSS files
- Embed or inline small JS or CSS files in the HTML
- Create sprites
- Base64 encode small images in HTML or stylesheets
- Use lazyloading for images
",
+ "message": "Each request slows down the page loading, especially on the protocol HTTP/1, but also a little on HTTP/2 or 3.
There are several technics to reduce their number:
- Concatenate JS files
- Concatenate CSS files
- Embed or inline small JS or CSS files in the HTML
- Create sprites
- Base64 encode small images in HTML or stylesheets
- Use lazyloading for images
",
"isOkThreshold": 80,
"isBadThreshold": 240,
"isAbnormalThreshold": 320,
From 2977506cb826ee4916e5cb97ad9f6ecb11c4d639 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Wed, 16 Aug 2023 07:57:01 +0200
Subject: [PATCH 5/7] Adjustments to excessive image density
---
lib/metadata/policies.js | 26 +++++++++++++-------------
lib/metadata/scoreProfileGeneric.json | 2 +-
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js
index d399031..1dcf3a2 100644
--- a/lib/metadata/policies.js
+++ b/lib/metadata/policies.js
@@ -54,7 +54,7 @@ var policies = {
"isBadThreshold": 2000,
"isAbnormalThreshold": 4000,
"hasOffenders": false,
- "unit": 'ms'
+ "unit": "ms"
},
"DOMaccesses": {
"tool": "domAccessAgregator",
@@ -734,7 +734,7 @@ var policies = {
"isBadThreshold": 3145728,
"isAbnormalThreshold": 5242880,
"hasOffenders": true,
- "unit": 'bytes'
+ "unit": "bytes"
},
"imagesNotOptimized": {
"tool": "phantomas",
@@ -744,7 +744,7 @@ var policies = {
"isBadThreshold": 204800,
"isAbnormalThreshold": 307200,
"hasOffenders": true,
- "unit": 'bytes',
+ "unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
@@ -765,7 +765,7 @@ var policies = {
"isBadThreshold": 512000,
"isAbnormalThreshold": 819200,
"hasOffenders": true,
- "unit": 'bytes',
+ "unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
@@ -786,7 +786,7 @@ var policies = {
"isBadThreshold": 307200,
"isAbnormalThreshold": 512000,
"hasOffenders": true,
- "unit": 'bytes',
+ "unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
@@ -803,11 +803,11 @@ var policies = {
"tool": "phantomas",
"label": "Excessive image density",
"message": "This metric measures the number of bytes that could be saved by going further than just resizing images.
Devices with very high pixel density screen (such as 3x or 4x) are programmed to load high density images. This is the normal behavior, however the human eye barely sees the difference over 2x. This metric alerts you if an image density is > 2.2x.
There is currently no browser functionnality to prevent the issue (for this reason its impact on global score is low). But you can build your own clever solution!
",
- "isOkThreshold": 0,
- "isBadThreshold": 10,
- "isAbnormalThreshold": 20,
+ "isOkThreshold": 102400,
+ "isBadThreshold": 307200,
+ "isAbnormalThreshold": 512000,
"hasOffenders": true,
- "unit": 'bytes',
+ "unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
@@ -837,7 +837,7 @@ var policies = {
"isBadThreshold": 204800,
"isAbnormalThreshold": 409600,
"hasOffenders": true,
- "unit": 'bytes'
+ "unit": "bytes"
},
"fileMinification": {
"tool": "redownload",
@@ -847,7 +847,7 @@ var policies = {
"isBadThreshold": 61440,
"isAbnormalThreshold": 122880,
"hasOffenders": true,
- "unit": 'bytes'
+ "unit": "bytes"
},
"totalRequests": {
"tool": "redownload",
@@ -943,7 +943,7 @@ var policies = {
"isOkThreshold": 0,
"isBadThreshold": 102400,
"isAbnormalThreshold": 204800,
- "unit": 'bytes',
+ "unit": "bytes",
"hasOffenders": true,
"offendersTransformFn": function(offenders) {
return offenders;
@@ -969,7 +969,7 @@ var policies = {
"isBadThreshold": 51200,
"isAbnormalThreshold": 122880,
"hasOffenders": true,
- "unit": 'bytes'
+ "unit": "bytes"
},
"oldHttpProtocol": {
"label": "HTTP protocols",
diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json
index 0962689..5019a70 100644
--- a/lib/metadata/scoreProfileGeneric.json
+++ b/lib/metadata/scoreProfileGeneric.json
@@ -22,7 +22,7 @@
"fileMinification": 2,
"identicalFiles": 2,
"emptyRequests": 3,
- "notFound": 2,
+ "notFound": 2
}
},
"images": {
From 85c685a5ca0866f0b6b0ba5fb361fe515068df44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Wed, 16 Aug 2023 09:37:34 +0200
Subject: [PATCH 6/7] Tag a phantomas version
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 7192a98..da53eb7 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"md5": "2.3.0",
"meow": "5.0.0",
"parse-color": "1.0.0",
- "phantomas": "gmetais/phantomas#analyze-image",
+ "phantomas": "gmetais/phantomas#gmetais-analyze-images",
"q": "1.5.1",
"request": "2.88.2",
"sharp": "0.32.3",
From 0610fd6fbb28e166f7c8db2aee9e8c1679722964 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?=
Date: Fri, 6 Oct 2023 16:18:23 +0200
Subject: [PATCH 7/7] Update Phantomas and increase min NodeJS version
---
package.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index da53eb7..e91a56a 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"yellowlabtools": "./bin/cli.js"
},
"engines": {
- "node": ">= 16.0"
+ "node": ">= 18.0"
},
"main": "./lib/index.js",
"dependencies": {
@@ -40,10 +40,10 @@
"md5": "2.3.0",
"meow": "5.0.0",
"parse-color": "1.0.0",
- "phantomas": "gmetais/phantomas#gmetais-analyze-images",
+ "phantomas": "2.10.0",
"q": "1.5.1",
"request": "2.88.2",
- "sharp": "0.32.3",
+ "sharp": "0.32.6",
"ttf2woff2": "5.0.0",
"uglify-js": "3.17.4",
"woff-tools": "0.1.0"