diff --git a/bower.json b/bower.json index 9d7da0f..8609ca8 100644 --- a/bower.json +++ b/bower.json @@ -1,8 +1,8 @@ { "name": "yellowlabtools", "dependencies": { - "angular": "~1.3.5", - "angular-route": "~1.3.6", - "angular-resource": "~1.3.6" + "angular": "~1.3.8", + "angular-route": "~1.3.8", + "angular-resource": "~1.3.7" } } diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index b9cb910..1f8a6fb 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -58,6 +58,14 @@ var policies = { "isAbnormalThreshold": 500, "takeOffendersFrom": "DOMqueriesDuplicated" }, + "DOMqueriesWithoutResults": { + "tool": "phantomas", + "label": "DOM queries without result", + "message": "

Number of queries that return no result.

It suggests the query is not used on the page, probably because it is some dead code.

Or maybe the code is trying to find an HTML block that is not always here. Look at the JS Timeline to see if the scripts correctly figures out the HTML block is not here and immediatly stops interacting further with the DOM.

", + "isOkThreshold": 0, + "isBadThreshold": 100, + "isAbnormalThreshold": 200 + }, "eventsBound": { "tool": "phantomas", "label": "Events bound", @@ -359,6 +367,30 @@ var policies = { "isBadThreshold": 20, "isAbnormalThreshold": 40 }, + "smallJsFiles": { + "tool": "phantomas", + "label": "Small JS files", + "message": "

Number of JS assets smaller than 2 KB that could probably be inlined or merged.

", + "isOkThreshold": 2, + "isBadThreshold": 10, + "isAbnormalThreshold": 16 + }, + "smallCssFiles": { + "tool": "phantomas", + "label": "Small CSS files", + "message": "

Number of CSS assets smaller than 2 KB that could probably be inlined or merged.

", + "isOkThreshold": 0, + "isBadThreshold": 8, + "isAbnormalThreshold": 12 + }, + "smallImages": { + "tool": "phantomas", + "label": "Small images", + "message": "

Images smaller than 2 KB that could be base64 encoded or merged into a sprite.

", + "isOkThreshold": 2, + "isBadThreshold": 17, + "isAbnormalThreshold": 30 + }, "notFound": { "tool": "phantomas", "label": "404 not found", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 8feb3ff..55c2b9d 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -14,6 +14,7 @@ "policies": { "DOMinserts": 2, "DOMqueries": 1, + "DOMqueriesWithoutResults": 2, "DOMqueriesAvoidable": 2, "eventsBound": 1 } @@ -79,6 +80,14 @@ "otherCount": 0 } }, + "smallRequests": { + "label": "Small requests", + "policies": { + "smallJsFiles": 1, + "smallCssFiles": 1, + "smallImages": 1 + } + }, "network": { "label": "Network", "policies": { @@ -100,6 +109,7 @@ "cssComplexity": 1, "badCSS": 1, "requests": 3, + "smallRequests": 1, "network": 2 } } \ No newline at end of file diff --git a/lib/runner.js b/lib/runner.js index 0cd4dcb..ab6ea6d 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,7 +1,7 @@ var Q = require('q'); var debug = require('debug')('ylt:runner'); -var phantomasWrapper = require('./tools/phantomasWrapper'); +var phantomasWrapper = require('./tools/phantomas/phantomasWrapper'); var jsExecutionTransformer = require('./tools/jsExecutionTransformer'); var rulesChecker = require('./rulesChecker'); var scoreCalculator = require('./scoreCalculator'); diff --git a/phantomas_custom/core/scopeYLT/scopeYLT.js b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js similarity index 99% rename from phantomas_custom/core/scopeYLT/scopeYLT.js rename to lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js index 9ed515a..c7478bb 100644 --- a/phantomas_custom/core/scopeYLT/scopeYLT.js +++ b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js @@ -7,7 +7,7 @@ */ /* global document: true, window: true */ -exports.version = '0.1'; +exports.version = '0.2'; exports.module = function(phantomas) { 'use strict'; @@ -62,7 +62,7 @@ exports.module = function(phantomas) { // After if (enabled && callbackAfter) { - callbackAfter.call(this, result); + callbackAfter.call(this, result, arguments); } } diff --git a/phantomas_custom/modules/domQYLT/domQYLT.js b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js similarity index 82% rename from phantomas_custom/modules/domQYLT/domQYLT.js rename to lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js index 8f0e4c9..81985f4 100644 --- a/phantomas_custom/modules/domQYLT/domQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js @@ -3,12 +3,13 @@ */ /* global Element: true, Document: true, Node: true, window: true */ -exports.version = '0.9.a'; +exports.version = '0.10.a'; exports.module = function(phantomas) { 'use strict'; phantomas.setMetric('DOMqueries'); // @desc number of all DOM queries @offenders + phantomas.setMetric('DOMqueriesWithoutResults'); // @desc number of DOM queries that retutned nothing @offenders phantomas.setMetric('DOMqueriesById'); // @desc number of document.getElementById calls phantomas.setMetric('DOMqueriesByClassName'); // @desc number of document.getElementsByClassName calls phantomas.setMetric('DOMqueriesByTagName'); // @desc number of document.getElementsByTagName calls @@ -21,14 +22,13 @@ exports.module = function(phantomas) { phantomas.once('init', function() { phantomas.evaluate(function() { (function(phantomas) { - function querySpy(type, query, fnName, context) { - phantomas.emit('domQuery', type, query, fnName, context); // @desc DOM query has been made + function querySpy(type, query, fnName, context, hasNoResults) { + phantomas.emit('domQuery', type, query, fnName, context, hasNoResults); // @desc DOM query has been made } phantomas.spy(Document.prototype, 'getElementById', function(id) { phantomas.incrMetric('DOMqueriesById'); phantomas.addOffender('DOMqueriesById', '#%s (in %s)', id, '#document'); - querySpy('id', '#' + id, 'getElementById', '#document'); phantomas.enterContext({ type: 'getElementById', @@ -41,9 +41,13 @@ exports.module = function(phantomas) { backtrace: phantomas.getBacktrace() }); - }, function(result) { + }, function(result, args) { + var id = args[0]; + + querySpy('id', '#' + id, 'getElementById', '#document', (result === null)); + var moreData = { - resultsNumber : result ? 1 : 0 + resultsNumber : (result === null) ? 0 : 1 }; phantomas.leaveContext(moreData); }); @@ -56,7 +60,6 @@ exports.module = function(phantomas) { phantomas.incrMetric('DOMqueriesByClassName'); phantomas.addOffender('DOMqueriesByClassName', '.%s (in %s)', className, context); - querySpy('class', '.' + className, 'getElementsByClassName', context); phantomas.enterContext({ type: 'getElementsByClassName', @@ -70,7 +73,14 @@ exports.module = function(phantomas) { }); } - function selectorClassNameAfter(result) { + function selectorClassNameAfter(result, args) { + /*jshint validthis: true */ + + var className = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('class', '.' + className, 'getElementsByClassName', context, (result.length === 0)); + var moreData = { resultsNumber : (result && result.length > 0) ? result.length : 0 }; @@ -88,7 +98,6 @@ exports.module = function(phantomas) { phantomas.incrMetric('DOMqueriesByTagName'); phantomas.addOffender('DOMqueriesByTagName', '%s (in %s)', tagName, context); - querySpy('tag name', tagName.toLowerCase(), 'getElementsByTagName', context); phantomas.enterContext({ type: 'getElementsByTagName', @@ -102,7 +111,14 @@ exports.module = function(phantomas) { }); } - function selectorTagNameSpyAfter(result) { + function selectorTagNameSpyAfter(result, args) { + /*jshint validthis: true */ + + var tagName = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('tag name', tagName.toLowerCase(), 'getElementsByTagName', context, (result.length === 0)); + var moreData = { resultsNumber : (result && result.length > 0) ? result.length : 0 }; @@ -112,11 +128,11 @@ exports.module = function(phantomas) { phantomas.spy(Document.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter); phantomas.spy(Element.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter); + // selector queries function selectorQuerySpy(selector, context) { phantomas.incrMetric('DOMqueriesByQuerySelectorAll'); phantomas.addOffender('DOMqueriesByQuerySelectorAll', '%s (in %s)', selector, context); - querySpy('selector', selector, 'querySelectorAll', context); } function selectorQuerySpyBefore(selector) { @@ -137,7 +153,14 @@ exports.module = function(phantomas) { }); } - function selectorQuerySpyAfter(result) { + function selectorQuerySpyAfter(result, args) { + /*jshint validthis: true */ + + var selector = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0)); + var moreData = { resultsNumber : result ? 1 : 0 }; @@ -162,7 +185,14 @@ exports.module = function(phantomas) { }); } - function selectorAllQuerySpryAfter(result) { + function selectorAllQuerySpryAfter(result, args) { + /*jshint validthis: true */ + + var selector = args[0]; + var context = phantomas.getDOMPath(this); + + querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0)); + var moreData = { resultsNumber : (result && result.length > 0) ? result.length : 0 }; @@ -244,9 +274,22 @@ exports.module = function(phantomas) { }); }); + // report DOM queries that return no results (issue #420) + phantomas.on('domQuery', function(type, query, fnName, context, hasNoResults) { + // ignore DOM queries within DOM fragments (used internally by jQuery) + if (context.indexOf('body') !== 0 && context.indexOf('#document') !== 0) { + return; + } + + if (hasNoResults === true) { + phantomas.incrMetric('DOMqueriesWithoutResults'); + phantomas.addOffender('DOMqueriesWithoutResults', '%s (in %s) using %s', query, context, fnName); + } + }); + // count DOM queries by either ID, tag name, class name and selector query // @see https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-doctype - var Collection = require('../../../node_modules/phantomas/lib/collection'), + var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), DOMqueries = new Collection(); phantomas.on('domQuery', function(type, query, fnName, context) { diff --git a/phantomas_custom/modules/eventListYLT/eventListYLT.js b/lib/tools/phantomas/custom_modules/modules/eventListYLT/eventListYLT.js similarity index 100% rename from phantomas_custom/modules/eventListYLT/eventListYLT.js rename to lib/tools/phantomas/custom_modules/modules/eventListYLT/eventListYLT.js diff --git a/phantomas_custom/modules/jQYLT/jQYLT.js b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js similarity index 99% rename from phantomas_custom/modules/jQYLT/jQYLT.js rename to lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js index 149fce0..b1014c3 100644 --- a/phantomas_custom/modules/jQYLT/jQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js @@ -298,7 +298,7 @@ exports.module = function(phantomas) { // count Sizzle calls to detect duplicated queries - var Collection = require('../../../node_modules/phantomas/lib/collection'), + var Collection = require('../../../../../../node_modules/phantomas/lib/collection'), sizzleCalls = new Collection(), jQueryLoading = new Collection(); diff --git a/phantomas_custom/modules/jsErrYLT/jsErrYLT.js b/lib/tools/phantomas/custom_modules/modules/jsErrYLT/jsErrYLT.js similarity index 100% rename from phantomas_custom/modules/jsErrYLT/jsErrYLT.js rename to lib/tools/phantomas/custom_modules/modules/jsErrYLT/jsErrYLT.js diff --git a/phantomas_custom/modules/jsFileLoadYLT/jsFileLoadYLT.js b/lib/tools/phantomas/custom_modules/modules/jsFileLoadYLT/jsFileLoadYLT.js similarity index 100% rename from phantomas_custom/modules/jsFileLoadYLT/jsFileLoadYLT.js rename to lib/tools/phantomas/custom_modules/modules/jsFileLoadYLT/jsFileLoadYLT.js diff --git a/phantomas_custom/modules/jsTreeYLT/jsTreeYLT.js b/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js similarity index 100% rename from phantomas_custom/modules/jsTreeYLT/jsTreeYLT.js rename to lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js diff --git a/phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js b/lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js similarity index 100% rename from phantomas_custom/modules/windowPerfYLT/windowPerfYLT.js rename to lib/tools/phantomas/custom_modules/modules/windowPerfYLT/windowPerfYLT.js diff --git a/lib/tools/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js similarity index 95% rename from lib/tools/phantomasWrapper.js rename to lib/tools/phantomas/phantomasWrapper.js index 8dbd614..901b3ab 100644 --- a/lib/tools/phantomasWrapper.js +++ b/lib/tools/phantomas/phantomasWrapper.js @@ -31,7 +31,6 @@ var PhantomasWrapper = function() { 'analyze-css': true, 'skip-modules': [ 'blockDomains', // not needed - 'domComplexity', // overriden 'domMutations', // not compatible with webkit 'domQueries', // overriden 'eventListeners', // overridden @@ -43,8 +42,8 @@ var PhantomasWrapper = function() { 'windowPerformance' // overriden ].join(','), 'include-dirs': [ - 'phantomas_custom/core', - 'phantomas_custom/modules' + 'lib/tools/phantomas/custom_modules/core', + 'lib/tools/phantomas/custom_modules/modules' ].join(',') }; diff --git a/package.json b/package.json index c7cfe8c..f77e320 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "dependencies": { "async": "~0.9.0", "body-parser": "~1.10.0", - "compression": "~1.2.1", - "debug": "^2.1.0", - "express": "~4.10.4", - "phantomas": "1.7.0", - "rimraf": "^2.2.8", - "q": "^1.1.2" + "compression": "~1.2.2", + "debug": "~2.1.0", + "express": "~4.10.6", + "phantomas": "1.8.0", + "rimraf": "~2.2.8", + "q": "~1.1.2" }, "devDependencies": { "chai": "^1.10.0", @@ -31,9 +31,9 @@ "grunt-fontsmith": "^0.9.1", "grunt-mocha-test": "^0.12.4", "matchdep": "^0.3.0", - "mocha": "^2.0.1", - "phantomjs": "^1.9.12", - "request": "^2.49.0", + "mocha": "^2.1.0", + "phantomjs": "^1.9.13", + "request": "^2.51.0", "sinon": "^1.12.1", "sinon-chai": "^2.6.0" }, diff --git a/phantomas_custom/modules/domComplexYLT/domComplexYLT.js b/phantomas_custom/modules/domComplexYLT/domComplexYLT.js deleted file mode 100644 index a64f3c6..0000000 --- a/phantomas_custom/modules/domComplexYLT/domComplexYLT.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Analyzes DOM complexity - */ -/* global document: true, Node: true, window: true */ - -exports.version = '1.0.a'; - -exports.module = function(phantomas) { - 'use strict'; - - // total length of HTML comments (including brackets) - phantomas.setMetric('commentsSize'); // @desc the size of HTML comments on the page @offenders - - // total length of text nodes with whitespaces only (i.e. pretty formatting of HTML) - phantomas.setMetric('whiteSpacesSize'); // @desc the size of text nodes with whitespaces only - - // count all tags - phantomas.setMetric('DOMelementsCount'); // @desc total number of HTML element nodes - phantomas.setMetric('DOMelementMaxDepth'); // @desc maximum level on nesting of HTML element node - - // nodes with inlines CSS (style attribute) - phantomas.setMetric('nodesWithInlineCSS'); // @desc number of nodes with inline CSS styling (with style attribute) @offenders - - // images - phantomas.setMetric('imagesScaledDown'); // @desc number of nodes that have images scaled down in HTML @offenders - phantomas.setMetric('imagesWithoutDimensions'); // @desc number of nodes without both width and height attribute @offenders - - // duplicated ID (issue #392) - phantomas.setMetric('DOMidDuplicated'); // @desc number of duplicated IDs found in DOM - - var Collection = require('../../../node_modules/phantomas/lib/collection'), - DOMids = new Collection(); - - phantomas.on('domId', function(id) { - DOMids.push(id); - }); - - // HTML size - phantomas.on('report', function() { - phantomas.setMetricEvaluate('bodyHTMLSize', function() { // @desc the size of body tag content (document.body.innerHTML.length) - return document.body && document.body.innerHTML.length || 0; - }); - - phantomas.evaluate(function() { - (function(phantomas) { - var runner = new phantomas.nodeRunner(), - whitespacesRegExp = /^\s+$/, - DOMelementMaxDepth = 0, - DOMelementMaxDepthElts = [], - size = 0; - - runner.walk(document.body, function(node, depth) { - switch (node.nodeType) { - case Node.COMMENT_NODE: - size = node.textContent.length + 7; // ''.length - phantomas.incrMetric('commentsSize', size); - - // log HTML comments bigger than 64 characters - if (size > 64) { - phantomas.addOffender('commentsSize', phantomas.getDOMPath(node) + ' (' + size + ' characters)'); - } - break; - - case Node.ELEMENT_NODE: - phantomas.incrMetric('DOMelementsCount'); - - if (depth > DOMelementMaxDepth) { - DOMelementMaxDepth = depth; - DOMelementMaxDepthElts = [phantomas.getDOMPath(node)]; - } else if (depth === DOMelementMaxDepth) { - DOMelementMaxDepthElts.push(phantomas.getDOMPath(node)); - } - - // report duplicated ID (issue #392) - if (node.id) { - phantomas.emit('domId', node.id); - } - - // ignore inline