diff --git a/front/src/css/rule.css b/front/src/css/rule.css
index 9b51443..c097224 100644
--- a/front/src/css/rule.css
+++ b/front/src/css/rule.css
@@ -261,8 +261,8 @@
}
.smallPreview {
display: block;
- max-height: 4em;
- max-width: 8em;
+ max-height: 6em;
+ max-width: 16em;
border: 1px solid #000;
margin: 1em auto 0.2em;
}
diff --git a/front/src/less/rule.less b/front/src/less/rule.less
index fe9a0fa..c139a34 100644
--- a/front/src/less/rule.less
+++ b/front/src/less/rule.less
@@ -290,8 +290,8 @@
.smallPreview {
display: block;
- max-height: 4em;
- max-width: 8em;
+ max-height: 6em;
+ max-width: 16em;
border: 1px solid #000;
margin: 1em auto 0.2em;
}
\ No newline at end of file
diff --git a/front/src/views/rule.html b/front/src/views/rule.html
index 8238c17..2348306 100644
--- a/front/src/views/rule.html
+++ b/front/src/views/rule.html
@@ -137,6 +137,12 @@
This is the number of images with a width >800px on mobile or >1500px on desktop. Try
Please ignore if the file is used as a sprite.
Please note that Yellow Lab Tools' engine (PhantomJS) is not compatible with image srcset (unless you use a polyfill). This can lead to incorrect detection.
", + "isOkThreshold": 0, + "isBadThreshold": 5, + "isAbnormalThreshold": 10, + "hasOffenders": true + }, "gzipCompression": { "tool": "redownload", "label": "Gzip compression", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index de49a41..eb453e8 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -5,6 +5,7 @@ "policies": { "totalWeight": 5, "imageOptimization": 2, + "imagesTooLarge": 2, "gzipCompression": 2, "fileMinification": 1 } diff --git a/lib/tools/redownload/imageDimensions.js b/lib/tools/redownload/imageDimensions.js new file mode 100644 index 0000000..c9dff77 --- /dev/null +++ b/lib/tools/redownload/imageDimensions.js @@ -0,0 +1,51 @@ +var debug = require('debug')('ylt:imageDimensions'); +var Q = require('q'); +var sizeOf = require('image-size'); + +var ImageDimensions = function() { + + function getDimensions(entry) { + var deferred = Q.defer(); + + if (!entry.weightCheck || !entry.weightCheck.bodyBuffer) { + // No valid file available + deferred.resolve(entry); + return deferred.promise; + } + + var fileSize = entry.weightCheck.uncompressedSize; + + if (isJPEG(entry) || isPNG(entry)) { + try { + var dimensions = sizeOf(entry.weightCheck.bodyBuffer); + debug('Image dimensions of %s: %sx%s', entry.url, dimensions.width, dimensions.height); + + entry.imageDimensions = { + width: dimensions.width, + height: dimensions.height + }; + } catch(err) { + debug('Error while checking image dimensions:'); + debug(err); + } + } + + deferred.resolve(entry); + + return deferred.promise; + } + + function isJPEG(entry) { + return entry.isImage && entry.contentType === 'image/jpeg'; + } + + function isPNG(entry) { + return entry.isImage && entry.contentType === 'image/png'; + } + + return { + getDimensions: getDimensions + }; +}; + +module.exports = new ImageDimensions(); \ No newline at end of file diff --git a/lib/tools/redownload/redownload.js b/lib/tools/redownload/redownload.js index 824d6ed..9c5b04e 100644 --- a/lib/tools/redownload/redownload.js +++ b/lib/tools/redownload/redownload.js @@ -19,6 +19,7 @@ var fileMinifier = require('./fileMinifier'); var gzipCompressor = require('./gzipCompressor'); var contentTypeChecker = require('./contentTypeChecker'); var fontAnalyzer = require('./fontAnalyzer'); +var imageDimensions = require('./imageDimensions'); var Redownload = function() { @@ -69,6 +70,8 @@ var Redownload = function() { .then(imageOptimizer.optimizeImage) + .then(imageDimensions.getDimensions) + .then(fileMinifier.minifyFile) .then(gzipCompressor.compressFile) @@ -142,9 +145,14 @@ var Redownload = function() { // Image compression - offenders.imageOptimization = listImageNotOptimized(results); + offenders.imageOptimization = listImagesNotOptimized(results); metrics.imageOptimization = offenders.imageOptimization.totalGain; + // Image width + var isMobile = data.params.options.device === 'phone'; + offenders.imagesTooLarge = listImagesTooLarge(results, isMobile); + metrics.imagesTooLarge = offenders.imagesTooLarge.length; + // File minification offenders.fileMinification = listFilesNotMinified(results); metrics.fileMinification = offenders.fileMinification.totalGain; @@ -273,7 +281,7 @@ var Redownload = function() { } - function listImageNotOptimized(requests) { + function listImagesNotOptimized(requests) { var results = { totalGain: 0, images: [] @@ -305,6 +313,26 @@ var Redownload = function() { return results; } + function listImagesTooLarge(requests, isMobile) { + var results = []; + + requests.forEach(function(req) { + if (req.weightCheck.bodySize > 0 && + req.imageDimensions && + ((isMobile && req.imageDimensions.width > 800) || req.imageDimensions.width > 1500)) { + + results.push({ + url: req.url, + weight: req.weightCheck.bodySize, + width: req.imageDimensions.width, + height: req.imageDimensions.height + }); + } + }); + + return results; + } + function listFilesNotMinified(requests) { var results = { diff --git a/package.json b/package.json index 8e40168..095cb7d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "ejs": "2.5.7", "express": "4.16.2", "fontkit": "1.7.7", + "image-size": "0.7.1", "imagemin": "5.3.1", "imagemin-jpegoptim": "5.2.0", "imagemin-jpegtran": "5.0.2", diff --git a/test/core/imageDimensionsTest.js b/test/core/imageDimensionsTest.js new file mode 100644 index 0000000..3dcf58d --- /dev/null +++ b/test/core/imageDimensionsTest.js @@ -0,0 +1,90 @@ +var should = require('chai').should(); +var imageDimensions = require('../../lib/tools/redownload/imageDimensions'); +var fs = require('fs'); +var path = require('path'); + +describe('imageDimensions', function() { + + it('should detect png image dimensions', function(done) { + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/png-image.png')); + + var entry = { + method: 'GET', + url: 'http://localhost:8388/an-image.png', + requestHeaders: { + 'User-Agent': 'something', + Referer: 'http://www.google.fr/', + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate' + }, + status: 200, + isImage: true, + type: 'image', + contentType: 'image/png', + contentLength: 999, + weightCheck: { + bodyBuffer: fileContent, + totalWeight: 999, + headersSize: 200, + bodySize: 999, + isCompressed: false, + uncompressedSize: 999 + } + }; + + imageDimensions.getDimensions(entry) + + .then(function(newEntry) { + newEntry.should.have.a.property('imageDimensions'); + newEntry.imageDimensions.should.have.a.property('width').that.equals(664); + newEntry.imageDimensions.should.have.a.property('height').that.equals(314); + done(); + }) + + .fail(function(err) { + done(err); + }); + }); + + it('should detect a jpg image dimensions', function(done) { + var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/jpeg-image.jpg')); + + var entry = { + method: 'GET', + url: 'http://localhost:8388/an-image.jpg', + requestHeaders: { + 'User-Agent': 'something', + Referer: 'http://www.google.fr/', + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate' + }, + status: 200, + isImage: true, + type: 'image', + contentType: 'image/jpeg', + contentLength: 999, + weightCheck: { + bodyBuffer: fileContent, + totalWeight: 999, + headersSize: 200, + bodySize: 999, + isCompressed: false, + uncompressedSize: 999 + } + }; + + imageDimensions.getDimensions(entry) + + .then(function(newEntry) { + newEntry.should.have.a.property('imageDimensions'); + newEntry.imageDimensions.should.have.a.property('width').that.equals(285); + newEntry.imageDimensions.should.have.a.property('height').that.equals(427); + done(); + }) + + .fail(function(err) { + done(err); + }); + }); + +}); diff --git a/test/core/redownloadTest.js b/test/core/redownloadTest.js index f4890ee..a3c048a 100644 --- a/test/core/redownloadTest.js +++ b/test/core/redownloadTest.js @@ -109,6 +109,11 @@ describe('redownload', function() { ]; var data = { + params: { + options: { + device: 'phone' + } + }, toolsResults: { phantomas: { metrics: { @@ -140,6 +145,9 @@ describe('redownload', function() { 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('imagesTooLarge'); + data.toolsResults.redownload.offenders.imagesTooLarge.length.should.equal(0); + data.toolsResults.redownload.offenders.should.have.a.property('gzipCompression'); data.toolsResults.redownload.offenders.gzipCompression.totalGain.should.be.above(0); data.toolsResults.redownload.offenders.gzipCompression.files.length.should.equal(5);