diff --git a/.gitignore b/.gitignore index a24b9c3..28ef6fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +package-lock.json .tmp .vagrant results/* diff --git a/.travis.yml b/.travis.yml index 3718230..a9a1f81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: node_js sudo: false node_js: - - "8.9" - - "6.2" + - "12.18" + - "14.7" env: - CXX=g++-4.8 addons: diff --git a/Gruntfile.js b/Gruntfile.js index 90eb3d2..d66c14c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -11,21 +11,6 @@ module.exports = function(grunt) { pkg: grunt.file.readJSON('package.json'), settings: grunt.file.readJSON('./server_config/settings.json'), - webfont: { - icons: { - src: 'front/src/fonts/svg-icons/*.svg', - dest: 'tmp', - destCss: 'front/src/less', - options: { - engine: 'node', - types: 'woff', - stylesheet: 'less', - embed: true, - htmlDemo: false, - syntax: 'bootstrap' - } - } - }, less: { all: { files: [ @@ -71,7 +56,10 @@ module.exports = function(grunt) { 'test/core/*.js', 'test/fixtures/*.js', 'front/src/js/**/*.js' - ] + ], + options: { + esversion: 6 + } }, clean: { tmp: { @@ -103,7 +91,7 @@ module.exports = function(grunt) { options: { reporter: 'spec', }, - src: ['test/api/apiTest.js'] + src: ['test/core/mediaQueriesCheckerTest.js'] } }, env: { @@ -222,12 +210,6 @@ module.exports = function(grunt) { process.env.IS_TEST = true; }); - grunt.registerTask('icons', [ - 'webfont:icons', - 'less', - 'clean:tmp' - ]); - grunt.registerTask('build', [ 'jshint', 'clean:build', diff --git a/README.md b/README.md index 902385d..1144f3e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Analyzes a webpage and detects **performance** or **front-end code quality** iss https://yellowlab.tools
+
Useless function call, as the jQuery object is empty.
'; - } else if (node.eventNotDelegated) { - html += 'This binding should use Event Delegation instead of binding each element one by one.
'; - } - - if (node.data.resultsNumber === 0) { - html += 'The query returned 0 results. Could it be unused or dead code?
'; - } else if (node.data.resultsNumber > 0) { - html += 'The query returned ' + node.data.resultsNumber + ' ' + (node.data.resultsNumber > 1 ? 'results' : 'result') + '.
'; - } - - if (node.data.backtrace) { - html += 'This rule reached the abnormality threshold, which means there is a real problem you should care about.
The table below shows the interactions between the JavaScript and the DOM on a scroll event.
-This is the colors palette, sized by total occurrences:
Current weight: {{image.original | bytes}}
-Current weight: {{image.original | bytes}} ({{image.afterCompression | bytes}} gzipped)
-With a lossless optimization:
{{image.afterOptimizationAndCompression | bytes}} gzipped (-{{image.gain | bytes}} gzipped)
With a lossless optimization:
{{image.lossless | bytes}} (-{{image.gain | bytes}})
With a lossy optimization:
{{image.afterOptimizationAndCompression | bytes}} gzipped (-{{image.gain | bytes}} gzipped)
With a lossy optimization:
{{image.lossy | bytes}} (-{{image.gain | bytes}})
Current weight: {{image.originalWeigth | bytes}}
+Current weight: {{image.originalWeigth | bytes}} ({{image.originalCompressedWeight | bytes}} compressed)
+ +With a lossless optimization:
{{image.afterOptimizationAndCompression | bytes}} compressed (-{{image.gain | bytes}} compressed)
With a lossless optimization:
{{image.lossless | bytes}} (-{{image.gain | bytes}})
With a lossy optimization:
{{image.afterOptimizationAndCompression | bytes}} compressed (-{{image.gain | bytes}} compressed)
With a lossy optimization:
{{image.lossy | bytes}} (-{{image.gain | bytes}})
Try to inline these styles in the head of the HTML or to merge them with other files.
-Try to inline these scripts in the HTML or merge them with other files.
-Try to inline these images (with base64 encoding for most image types except SVG that don't need base64 encoding). You can also create sprites.
-This graph gives a quick view of when the JavaScript interactions with the DOM occur during the loading of the page.
- -- The table below shows the interactions between the JavaScript and the DOM. It is useful to understand what happens while the page loads. -
-A deep DOM makes the CSS matching with DOM elements difficult.
It also slows down JavaScript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for JavaScript events, that bubble up to the document root.
", - "isOkThreshold": 12, - "isBadThreshold": 22, - "isAbnormalThreshold": 30, + "isOkThreshold": 14, + "isBadThreshold": 24, + "isAbnormalThreshold": 32, "hasOffenders": true, "offendersTransformFn": function(offenders) { var domArrays = offenders.map(offendersHelpers.domPathToArray); @@ -32,89 +32,38 @@ var policies = { "tool": "phantomas", "label": "Number of iframes", "message": "iFrames are the most complex HTML elements. They are pages, just like the main page, and the browser needs to create a new page context, which has a cost.
", - "isOkThreshold": 3, + "isOkThreshold": 4, "isBadThreshold": 15, "isAbnormalThreshold": 30, - "hasOffenders": false + "hasOffenders": true }, "DOMidDuplicated": { "tool": "phantomas", "label": "IDs duplicated", "message": "IDs of HTML elements must be document-wide unique. This can cause problems with getElementById returning the wrong element.
", "isOkThreshold": 0, - "isBadThreshold": 5, + "isBadThreshold": 10, "isAbnormalThreshold": 50, - "hasOffenders": true, - "offendersTransformFn": function(offenders) { - return { - count: offenders.length, - list: offenders.map(function(offender) { - var parts = /^(.*): ?(\d+) ?occurrences$/.exec(offender); - - if (!parts) { - debug('DOMidDuplicated offenders transform function error with "%s"', offender); - return { - parseError: offender - }; - } - - return { - id: parts[1], - occurrences: parseInt(parts[2], 10) - }; - }) - }; - } + "hasOffenders": true }, - "DOMaccesses": { - "tool": "jsExecutionTransformer", - "label": "DOM access", - "message": "This metric counts the number of calls to DOM related functions (both native DOM functions and jQuery functions) on page load.
The more your JavaScript code accesses the DOM, the slower the page will load.
Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.
Try to reduce the number of queries by refactoring your JavaScript code.
Binding too many events also has a cost. Try to use event delegation as much as possible.
", - "isOkThreshold": 200, + "scriptDuration": { + "tool": "phantomas", + "label": "Total JS execution time", + "message": "This is the number of milliseconds spent by the browser on JavaScript execution during page load.
For more details, try using the performance tab in Chrome DevTools. It is a bit complicated at first sight, but you'll be able to analyze exactly where this execution time is spent.
", + "isOkThreshold": 500, "isBadThreshold": 2000, "isAbnormalThreshold": 4000, - "hasOffenders": false + "hasOffenders": false, + "unit": 'ms' }, - "queriesWithoutResults": { - "tool": "jsExecutionTransformer", - "label": "Queries without result", - "message": "Number of queries that return no result. Both native and jQuery DOM requests are counted.
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": 150, - "isAbnormalThreshold": 250, - "hasOffenders": false - }, - "DOMqueriesAvoidable": { - "tool": "phantomas", - "label": "Duplicated DOM queries", - "message": "This is the number of queries that could be avoided by removing all duplicated queries.
Simply save the result of a query in a variable. Ok it is not always simple, especially with third-party scripts, but at least do it with your own code.
", - "isOkThreshold": 0, - "isBadThreshold": 300, - "isAbnormalThreshold": 600, - "hasOffenders": true, - "takeOffendersFrom": "DOMqueriesDuplicated", - "offendersTransformFn": function(offenders) { - return { - count: offenders.length, - list: offenders.map(function(offender) { - var parts = /^[^"]* ?"(.*)" ?with ?(.*) ?\(in ?context ?(.*)\): ?(.*)\s?queries$/.exec(offender); - - if (!parts) { - debug('DOMqueriesAvoidable offenders transform function error with "%s"', offender); - return { - parseError: offender - }; - } - - return { - query: parts[1], - context: offendersHelpers.domPathToDomElementObj(parts[3]), - fn: parts[2], - count: parseInt(parts[4], 10) - }; - }) - }; - } + "DOMaccesses": { + "tool": "domAccessAgregator", + "label": "DOM access", + "message": "This metric estimates the number of times the JavaScript reads, changes or binds the DOM.
The more your JavaScript code accesses the DOM, the slower the page will load.
Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.
Try to reduce the number of queries by refactoring your JavaScript code.
Binding too many events also has a cost.
", + "isOkThreshold": 500, + "isBadThreshold": 2500, + "isAbnormalThreshold": 5000, + "hasOffenders": true }, "eventsScrollBound": { "tool": "phantomas", @@ -147,25 +96,13 @@ var policies = { }; } }, - "DOMaccessesOnScroll": { - "tool": "jsExecutionTransformer", - "label": "DOM access on scroll", - "message": "This rule counts the number of DOM-accessing functions calls on a scroll event, such as queries, readings, writings, bindings and jQuery functions.
Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.
One of the main reasons of a poor scrolling experience is when too much JS is executed on each scroll event. Note that some devices such as smartphones and MacBooks send more scroll events than others.
Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use throttling or debouncing.
", - "isOkThreshold": 1, - "isBadThreshold": 20, - "isAbnormalThreshold": 35, - "hasOffenders": true, - "offendersTransformFn": function(offenders) { - return offenders; - } - }, "jsErrors": { "tool": "phantomas", "label": "JavaScript errors", - "message": "Just to let you know there are some errors on the page.
Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.
", + "message": "Just to let you know there are some errors on the page.
", "isOkThreshold": 0, "isBadThreshold": 1, - "isAbnormalThreshold": 4, + "isAbnormalThreshold": 5, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -195,8 +132,8 @@ var policies = { "label": "document.write calls", "message": "They slow down the page construction, especially if they are used to insert scripts in the page. Remove them ASAP.
If you cannot remove them because they come from a third-party script (such as ads), have a look at PostScribe.
", "isOkThreshold": 0, - "isBadThreshold": 3, - "isAbnormalThreshold": 8, + "isBadThreshold": 2, + "isAbnormalThreshold": 6, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -250,22 +187,13 @@ var policies = { "isAbnormalThreshold": 1, "hasOffenders": true }, - "consoleMessages": { - "tool": "phantomas", - "label": "Console messages", - "message": "Try to keep your console clean when in production. Debugging is good for development only.
Writing in the console has a cost, especially when dumping large object variables.
There is also a problem with Internet Explorer 8, not knowing the console object.
", - "isOkThreshold": 3, - "isBadThreshold": 20, - "isAbnormalThreshold": 50, - "hasOffenders": false - }, "globalVariables": { "tool": "phantomas", "label": "Global variables", "message": "It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.
Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.
", - "isOkThreshold": 40, - "isBadThreshold": 200, - "isAbnormalThreshold": 700, + "isOkThreshold": 20, + "isBadThreshold": 300, + "isAbnormalThreshold": 800, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -347,76 +275,45 @@ var policies = { "message": "jQuery is a heavy library. You should never load jQuery more than once on the same page.
", "isOkThreshold": 1, "isBadThreshold": 2, - "isAbnormalThreshold": 2, + "isAbnormalThreshold": 3, "hasOffenders": true }, - "jQueryCallsOnEmptyObject": { - "tool": "jsExecutionTransformer", - "label": "Calls on empty objects", - "message": "This metric counts the number of jQuery functions called on an empty jQuery object. The call was useless.
This can be helpful to detect dead or unused code.
", - "isOkThreshold": 1, - "isBadThreshold": 100, - "isAbnormalThreshold": 180, - "hasOffenders": false - }, - "jQueryNotDelegatedEvents": { - "tool": "jsExecutionTransformer", - "label": "Events not delegated", - "message": "This is the number of events that are bound with the .bind() or the .on() function without using event delegation.
This means jQuery binds each element contained in the object one by one. This is bad for performance.
", - "isOkThreshold": 1, - "isBadThreshold": 100, - "isAbnormalThreshold": 180, - "hasOffenders": false - }, "cssParsingErrors": { "tool": "phantomas", "label": "CSS syntax error", "message": "Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.
Maybe a CSS validator can help you.
", "isOkThreshold": 0, - "isBadThreshold": 1, + "isBadThreshold": 2, "isAbnormalThreshold": 20, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { count: offenders.length, list: offenders.map(function(offender) { - if (offender === '[inline CSS] (Empty CSS was provided)') { + if (offender === '[inline CSS]') { return { - error: 'Empty style tag', + error: 'Empty