diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index 82ce5b7..b6f2c35 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -510,7 +510,7 @@ var policies = { return offenders.map(function(offender) { var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var parts = /^([^{]*)(?: { ([^ ]+): (.*) }) \/\/ (.*)$/.exec(splittedOffender.offender); + var parts = /^([^{]*)(?: ?{ ?([^ ]+): (.*) ?}) \/\/ (.*)$/.exec(splittedOffender.offender); if (!parts) { debug('cssOldPropertyPrefixes offenders transform function error with "%s"', offender); @@ -535,7 +535,17 @@ var policies = { "message": "
This is one way to remove complexity from a CSS rule. Generally, when \"body\" is specified in a rule it can be removed, because an element is necessarily inside the body.
", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 200 + "isAbnormalThreshold": 200, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var rule = splittedOffender.offender; + rule = rule.replace(/body/, 'body'); + + return rule + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssRedundantChildNodesSelectors": { "tool": "phantomas", @@ -543,7 +553,27 @@ var policies = { "message": "Some tags included inside other tags are obvious. For example, when \"ul li\" is specified in a rule, \"ul\" can be removed because the \"li\" element is always inside a \"ul\". Same thing for \"tr td\", \"select option\", ...
Lowering compexity in CSS selectors can make the page load a little faster.
", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 200 + "isAbnormalThreshold": 200, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var rule = splittedOffender.offender; + var redundanters = [ + ['ul', 'li'], + ['ol', 'li'], + ['select', 'option'], + ['table', 'tr'], + ['table', 'th'], + ]; + + redundanters.forEach(function(couple) { + rule = rule.replace(new RegExp('(^| |>)' + couple[0] + '([^ >]*)?([ >]| > )' + couple[1] + '([^\\w-]|$)', 'g'), '$1' + couple[0] + '$2$3' + couple[1] + '$4'); + }); + + return rule + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "requests": { "tool": "phantomas", @@ -552,7 +582,17 @@ var policies = { "isOkThreshold": 15, "isBadThreshold": 100, "isAbnormalThreshold": 200, - "takeOffendersFrom": ["htmlCount", "jsCount", "cssCount", "imageCount", "webfontCount", "videoCount", "jsonCount", "jsonCount"] + "takeOffendersFrom": ["htmlCount", "jsCount", "cssCount", "imageCount", "webfontCount", "videoCount", "jsonCount", "jsonCount"], + "offendersTransformFn": function(offenders) { + return offenders + .map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }).sort(function(a, b) { + return b.size - a.size; + }).map(function(fileObj) { + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "htmlCount": { "tool": "phantomas", @@ -560,7 +600,13 @@ var policies = { "message": "The number of HTML pages requests, HTML fragments or iframes.
", "isOkThreshold": 10, "isBadThreshold": 20, - "isAbnormalThreshold": 30 + "isAbnormalThreshold": 30, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "jsCount": { "tool": "phantomas", @@ -568,7 +614,13 @@ var policies = { "message": "Reduce the number of scripts by concatenating them.
", "isOkThreshold": 5, "isBadThreshold": 15, - "isAbnormalThreshold": 30 + "isAbnormalThreshold": 30, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "cssCount": { "tool": "phantomas", @@ -576,7 +628,13 @@ var policies = { "message": "Reduce the number of stylesheets by concatenating them.
", "isOkThreshold": 3, "isBadThreshold": 10, - "isAbnormalThreshold": 22 + "isAbnormalThreshold": 22, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "imageCount": { "tool": "phantomas", @@ -584,7 +642,13 @@ var policies = { "message": "Reduce the number of images by lazyloading them, by spriting them or by creating an icons font.
", "isOkThreshold": 15, "isBadThreshold": 40, - "isAbnormalThreshold": 70 + "isAbnormalThreshold": 70, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "webfontCount": { "tool": "phantomas", @@ -592,15 +656,27 @@ var policies = { "message": "Fonts are loaded on the critical path of the head. Load as many as possible.
", "isOkThreshold": 0, "isBadThreshold": 3, - "isAbnormalThreshold": 5 + "isAbnormalThreshold": 5, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "videoCount": { "tool": "phantomas", - "label": "Videos count", + "label": "Video count", "message": "The number of videos loaded.
", "isOkThreshold": 1, "isBadThreshold": 5, - "isAbnormalThreshold": 15 + "isAbnormalThreshold": 15, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "jsonCount": { "tool": "phantomas", @@ -608,7 +684,13 @@ var policies = { "message": "The number of AJAX requests to JSON files or webservices.
", "isOkThreshold": 2, "isBadThreshold": 10, - "isAbnormalThreshold": 25 + "isAbnormalThreshold": 25, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "otherCount": { "tool": "phantomas", @@ -616,7 +698,13 @@ var policies = { "message": "They can be Flash, XML, music or any unknown format.
", "isOkThreshold": 5, "isBadThreshold": 20, - "isAbnormalThreshold": 40 + "isAbnormalThreshold": 40, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "smallJsFiles": { "tool": "phantomas", @@ -624,7 +712,13 @@ var policies = { "message": "Number of JS assets smaller than 2 KB that could probably be inlined or merged.
", "isOkThreshold": 2, "isBadThreshold": 10, - "isAbnormalThreshold": 16 + "isAbnormalThreshold": 16, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "smallCssFiles": { "tool": "phantomas", @@ -632,7 +726,13 @@ var policies = { "message": "Number of CSS assets smaller than 2 KB that could probably be inlined or merged.
", "isOkThreshold": 0, "isBadThreshold": 8, - "isAbnormalThreshold": 12 + "isAbnormalThreshold": 12, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "smallImages": { "tool": "phantomas", @@ -640,7 +740,13 @@ var policies = { "message": "Images smaller than 2 KB that could be base64 encoded or merged into a sprite.
", "isOkThreshold": 2, "isBadThreshold": 17, - "isAbnormalThreshold": 30 + "isAbnormalThreshold": 30, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var fileObj = offendersHelpers.fileWithSizePattern(offender); + return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); + }); + } }, "notFound": { "tool": "phantomas", @@ -648,7 +754,12 @@ var policies = { "message": "404 errors are never cached, so each time a page ask for it, it hits se server. Even if it is behind a CDN or a reverse-proxy cache.
", "isOkThreshold": 0, "isBadThreshold": 1, - "isAbnormalThreshold": 1 + "isAbnormalThreshold": 1, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + return offendersHelpers.urlToLink(offender); + }); + } }, "closedConnections": { "tool": "phantomas", @@ -656,15 +767,25 @@ var policies = { "message": "This counts the number of requests not keeping the connection alive (specifying \"Connection: close\" in the response headers). It is only counting a request if it is followed by another request on the same domain.
This is slowing down the next request, because the brower needs to open a new connection to the server, which means a additional round-trip.
Correct the problem by setting a Keep-Alive header on the guilty server.
", "isOkThreshold": 0, "isBadThreshold": 8, - "isAbnormalThreshold": 20 + "isAbnormalThreshold": 20, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + return offendersHelpers.urlToLink(offender); + }); + } }, "multipleRequests": { "tool": "phantomas", "label": "Duplicated requests", "message": "This only happens when the asset has no cache and is requested more than once on the same page. Be very careful about it.
", "isOkThreshold": 0, - "isBadThreshold": 5, - "isAbnormalThreshold": 10 + "isBadThreshold": 3, + "isAbnormalThreshold": 10, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + return offendersHelpers.urlToLink(offender); + }); + } }, "cachingDisabled": { "tool": "phantomas", @@ -672,7 +793,12 @@ var policies = { "message": "Counts responses with caching disabled (max-age=0)
Fix immediatly if on static assets.
", "isOkThreshold": 0, "isBadThreshold": 12, - "isAbnormalThreshold": 25 + "isAbnormalThreshold": 25, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + return offendersHelpers.urlToLink(offender); + }); + } }, "cachingNotSpecified": { "tool": "phantomas", @@ -680,7 +806,12 @@ var policies = { "message": "When no caching is specified, each browser will handle it differently. Most of the time, it will automatically add a cache for you, but a poor one. You'd better handle it yourself.
", "isOkThreshold": 5, "isBadThreshold": 20, - "isAbnormalThreshold": 40 + "isAbnormalThreshold": 40, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + return offendersHelpers.urlToLink(offender); + }); + } }, "cachingTooShort": { "tool": "phantomas", @@ -688,7 +819,47 @@ var policies = { "message": "Responses with too short caching time (less than a week).
The longer you cache, the better. Add versionning to your static assets, if it's not already done, and set their cache time to one year.
", "isOkThreshold": 5, "isBadThreshold": 20, - "isAbnormalThreshold": 40 + "isAbnormalThreshold": 40, + "offendersTransformFn": function(offenders) { + return offenders + .map(function(offender) { + var parts = /^([^ ]*) cached for (-?\d+(\.\d+)?) s$/.exec(offender); + + if (!parts) { + debug('cachingTooShort offenders transform function error with "%s"', offender); + return { + file: offender + }; + } + + return { + file: parts[1], + ttl: Math.round(parseFloat(parts[2])) + }; + }).sort(function(a, b) { + return a.ttl - b.ttl; + }).map(function(obj) { + var duration = obj.ttl; + var unit = 'seconds'; + + if (duration >= 120) { + duration = Math.round(duration / 60); + unit = 'minutes'; + } + + if (duration >= 120) { + duration = Math.round(duration / 60); + unit = 'hours'; + } + + if (duration >= 48) { + duration = Math.round(duration / 24); + unit = 'days'; + } + + return offendersHelpers.urlToLink(obj.file) + ' cached for ' + duration + ' ' + unit + ''; + }); + } }, "domains": { "tool": "phantomas", @@ -696,7 +867,19 @@ var policies = { "message": "For each domain met, the browser needs to make a DNS look-up, which is slow. Avoid having to many different domains and the page should render faster.
By the way, domain sharding is not a good practice anymore.
", "isOkThreshold": 10, "isBadThreshold": 25, - "isAbnormalThreshold": 50 + "isAbnormalThreshold": 50, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var parts = /^([^ ]*): (\d+) request\(s\)$/.exec(offender); + + if (!parts) { + debug('domains offenders transform function error with "%s"', offender); + return offender; + } + + return '' + parts[1] + ' (' + parts[2] + ' ' + ((parseInt(parts[2], 10) > 1) ? 'requests' : 'request') + ')'; + }); + } } }; diff --git a/lib/offendersHelpers.js b/lib/offendersHelpers.js index 66b11f3..36151c6 100644 --- a/lib/offendersHelpers.js +++ b/lib/offendersHelpers.js @@ -172,6 +172,21 @@ var OffendersHelpers = function() { } }; + this.fileWithSizePattern = function(fileWithSize) { + var parts = /^([^ ]*) \((\d+\.\d{2}) kB\)$/.exec(fileWithSize); + + if (!parts) { + return { + file: fileWithSize + }; + } else { + return { + file: parts[1], + size: parseFloat(parts[2]) + }; + } + }; + }; module.exports = new OffendersHelpers(); \ No newline at end of file diff --git a/test/core/offendersHelpersTest.js b/test/core/offendersHelpersTest.js index 072d2d1..d6632f0 100644 --- a/test/core/offendersHelpersTest.js +++ b/test/core/offendersHelpersTest.js @@ -267,4 +267,17 @@ describe('offendersHelpers', function() { }); + describe('fileWithSizePattern', function() { + + it('should return an object', function() { + var result = offendersHelpers.fileWithSizePattern('http://img3.pouet.com/2008/portail/js/jq-timer.js (1.72 kB)'); + + result.should.deep.equal({ + file: 'http://img3.pouet.com/2008/portail/js/jq-timer.js', + size: 1.72 + }); + }); + + }); + });