@@ -261,8 +261,8 @@
|
|||||||
}
|
}
|
||||||
.smallPreview {
|
.smallPreview {
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 4em;
|
max-height: 6em;
|
||||||
max-width: 8em;
|
max-width: 16em;
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
margin: 1em auto 0.2em;
|
margin: 1em auto 0.2em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,8 +290,8 @@
|
|||||||
|
|
||||||
.smallPreview {
|
.smallPreview {
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 4em;
|
max-height: 6em;
|
||||||
max-width: 8em;
|
max-width: 16em;
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
margin: 1em auto 0.2em;
|
margin: 1em auto 0.2em;
|
||||||
}
|
}
|
||||||
@@ -137,6 +137,12 @@
|
|||||||
<url-link url="offender" max-length="100"></url-link>
|
<url-link url="offender" max-length="100"></url-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="policyName === 'imagesTooLarge'">
|
||||||
|
<img ng-src="{{offender.url | https}}" class="smallPreview checker"></img>
|
||||||
|
<div>{{offender.width}}x{{offender.height}}</div>
|
||||||
|
<url-link url="offender.url" max-length="100"></url-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ng-if="policyName === 'notFound' || policyName === 'emptyRequests' || policyName === 'closedConnections' || policyName === 'multipleRequests' || policyName === 'cachingDisabled' || policyName === 'cachingNotSpecified'">
|
<div ng-if="policyName === 'notFound' || policyName === 'emptyRequests' || policyName === 'closedConnections' || policyName === 'multipleRequests' || policyName === 'cachingDisabled' || policyName === 'cachingNotSpecified'">
|
||||||
<url-link url="offender" max-length="100"></url-link>
|
<url-link url="offender" max-length="100"></url-link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -914,6 +914,15 @@ var policies = {
|
|||||||
"hasOffenders": true,
|
"hasOffenders": true,
|
||||||
"unit": 'bytes'
|
"unit": 'bytes'
|
||||||
},
|
},
|
||||||
|
"imagesTooLarge": {
|
||||||
|
"tool": "redownload",
|
||||||
|
"label": "Oversized images",
|
||||||
|
"message": "<p>This is the number of images with a width >800px on mobile or >1500px on desktop. Try </p><p>Please ignore if the file is used as a sprite.</p><p>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.</p>",
|
||||||
|
"isOkThreshold": 0,
|
||||||
|
"isBadThreshold": 5,
|
||||||
|
"isAbnormalThreshold": 10,
|
||||||
|
"hasOffenders": true
|
||||||
|
},
|
||||||
"gzipCompression": {
|
"gzipCompression": {
|
||||||
"tool": "redownload",
|
"tool": "redownload",
|
||||||
"label": "Gzip compression",
|
"label": "Gzip compression",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"policies": {
|
"policies": {
|
||||||
"totalWeight": 5,
|
"totalWeight": 5,
|
||||||
"imageOptimization": 2,
|
"imageOptimization": 2,
|
||||||
|
"imagesTooLarge": 2,
|
||||||
"gzipCompression": 2,
|
"gzipCompression": 2,
|
||||||
"fileMinification": 1
|
"fileMinification": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -19,6 +19,7 @@ var fileMinifier = require('./fileMinifier');
|
|||||||
var gzipCompressor = require('./gzipCompressor');
|
var gzipCompressor = require('./gzipCompressor');
|
||||||
var contentTypeChecker = require('./contentTypeChecker');
|
var contentTypeChecker = require('./contentTypeChecker');
|
||||||
var fontAnalyzer = require('./fontAnalyzer');
|
var fontAnalyzer = require('./fontAnalyzer');
|
||||||
|
var imageDimensions = require('./imageDimensions');
|
||||||
|
|
||||||
|
|
||||||
var Redownload = function() {
|
var Redownload = function() {
|
||||||
@@ -69,6 +70,8 @@ var Redownload = function() {
|
|||||||
|
|
||||||
.then(imageOptimizer.optimizeImage)
|
.then(imageOptimizer.optimizeImage)
|
||||||
|
|
||||||
|
.then(imageDimensions.getDimensions)
|
||||||
|
|
||||||
.then(fileMinifier.minifyFile)
|
.then(fileMinifier.minifyFile)
|
||||||
|
|
||||||
.then(gzipCompressor.compressFile)
|
.then(gzipCompressor.compressFile)
|
||||||
@@ -142,9 +145,14 @@ var Redownload = function() {
|
|||||||
|
|
||||||
|
|
||||||
// Image compression
|
// Image compression
|
||||||
offenders.imageOptimization = listImageNotOptimized(results);
|
offenders.imageOptimization = listImagesNotOptimized(results);
|
||||||
metrics.imageOptimization = offenders.imageOptimization.totalGain;
|
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
|
// File minification
|
||||||
offenders.fileMinification = listFilesNotMinified(results);
|
offenders.fileMinification = listFilesNotMinified(results);
|
||||||
metrics.fileMinification = offenders.fileMinification.totalGain;
|
metrics.fileMinification = offenders.fileMinification.totalGain;
|
||||||
@@ -273,7 +281,7 @@ var Redownload = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function listImageNotOptimized(requests) {
|
function listImagesNotOptimized(requests) {
|
||||||
var results = {
|
var results = {
|
||||||
totalGain: 0,
|
totalGain: 0,
|
||||||
images: []
|
images: []
|
||||||
@@ -305,6 +313,26 @@ var Redownload = function() {
|
|||||||
return results;
|
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) {
|
function listFilesNotMinified(requests) {
|
||||||
var results = {
|
var results = {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"ejs": "2.5.7",
|
"ejs": "2.5.7",
|
||||||
"express": "4.16.2",
|
"express": "4.16.2",
|
||||||
"fontkit": "1.7.7",
|
"fontkit": "1.7.7",
|
||||||
|
"image-size": "0.7.1",
|
||||||
"imagemin": "5.3.1",
|
"imagemin": "5.3.1",
|
||||||
"imagemin-jpegoptim": "5.2.0",
|
"imagemin-jpegoptim": "5.2.0",
|
||||||
"imagemin-jpegtran": "5.0.2",
|
"imagemin-jpegtran": "5.0.2",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -109,6 +109,11 @@ describe('redownload', function() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
|
params: {
|
||||||
|
options: {
|
||||||
|
device: 'phone'
|
||||||
|
}
|
||||||
|
},
|
||||||
toolsResults: {
|
toolsResults: {
|
||||||
phantomas: {
|
phantomas: {
|
||||||
metrics: {
|
metrics: {
|
||||||
@@ -140,6 +145,9 @@ describe('redownload', function() {
|
|||||||
data.toolsResults.redownload.offenders.imageOptimization.totalGain.should.be.above(0);
|
data.toolsResults.redownload.offenders.imageOptimization.totalGain.should.be.above(0);
|
||||||
data.toolsResults.redownload.offenders.imageOptimization.images.length.should.equal(2);
|
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.should.have.a.property('gzipCompression');
|
||||||
data.toolsResults.redownload.offenders.gzipCompression.totalGain.should.be.above(0);
|
data.toolsResults.redownload.offenders.gzipCompression.totalGain.should.be.above(0);
|
||||||
data.toolsResults.redownload.offenders.gzipCompression.files.length.should.equal(5);
|
data.toolsResults.redownload.offenders.gzipCompression.files.length.should.equal(5);
|
||||||
|
|||||||
Reference in New Issue
Block a user