Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f5f9d6238 | ||
|
|
52ac5e5424 | ||
|
|
c59e9fc3f5 | ||
|
|
febe9871b9 | ||
|
|
4d5d881af6 | ||
|
|
2735deff73 | ||
|
|
ee93add6fc | ||
|
|
8ffab2ae70 | ||
|
|
fa216dddd5 | ||
|
|
34ec39a858 | ||
|
|
7dd611b61a | ||
|
|
5d017258b7 | ||
|
|
e58c00ecdf | ||
|
|
b88848ba8a | ||
|
|
6b6e7c647a | ||
|
|
b972422cc6 | ||
|
|
52ded23315 | ||
|
|
b3399f419e | ||
|
|
e2d60dde43 | ||
|
|
214b3c75d0 | ||
|
|
95659839b2 | ||
|
|
cab3f57737 | ||
|
|
dbbd01a83d | ||
|
|
90e66841df | ||
|
|
5ba41c6ea0 | ||
|
|
63c0de42db | ||
|
|
5646a22245 | ||
|
|
0e13801a7a |
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
+1
-1
@@ -4,5 +4,5 @@ node_modules
|
||||
results/*
|
||||
coverage
|
||||
front/build
|
||||
|
||||
package-lock.json
|
||||
har.json
|
||||
@@ -3,7 +3,6 @@ sudo: false
|
||||
node_js:
|
||||
- "8.9"
|
||||
- "6.2"
|
||||
- "4.7"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM node:10
|
||||
WORKDIR /app
|
||||
ENV VERSION=master
|
||||
EXPOSE 8383
|
||||
RUN git clone --branch ${VERSION} https://github.com/LumberjackOtters/YellowLabTools ylt && cd ylt && yarn install && yarn build
|
||||
ENV NODE_ENV=production
|
||||
CMD ["node", "/app/ylt/bin/server.js"]
|
||||
@@ -11,7 +11,7 @@ Analyzes a webpage and detects **performance** or **front-end code quality** iss
|
||||
<tr>
|
||||
<td width="70%">
|
||||
The <b>online tool</b> (recommended):
|
||||
<a href="http://yellowlab.tools" target="_blank">http://yellowlab.tools</a>
|
||||
<a href="https://yellowlab.tools" target="_blank">https://yellowlab.tools</a>
|
||||
</td>
|
||||
<td width="30%">
|
||||
<img src="./doc/img/YLT-animated.gif"></img>
|
||||
@@ -19,7 +19,7 @@ Analyzes a webpage and detects **performance** or **front-end code quality** iss
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="70%">
|
||||
The <b>CLI</b> (Command Line Interface) - <a href="https://github.com/gmetais/YellowLabTools/wiki/Command-Line-Interface" target="_blank">Doc here</a>
|
||||
The <b>CLI</b> (Command Line Interface) - <a href="https://github.com/YellowLabTools/YellowLabTools/wiki/Command-Line-Interface" target="_blank">Doc here</a>
|
||||
</td>
|
||||
<td width="30%">
|
||||
<img src="./doc/img/YLT-cli-animated.gif"></img>
|
||||
@@ -36,7 +36,7 @@ Analyzes a webpage and detects **performance** or **front-end code quality** iss
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="70%">
|
||||
The <b>NodeJS</b> package - <a href="https://github.com/gmetais/YellowLabTools/wiki/NodeJS-module" target="_blank">Doc here</a>
|
||||
The <b>NodeJS</b> package - <a href="https://github.com/YellowLabTools/YellowLabTools/wiki/NodeJS-module" target="_blank">Doc here</a>
|
||||
</td>
|
||||
<td width="30%">
|
||||
<img src="./doc/img/npm-logo.png"></img>
|
||||
@@ -44,7 +44,7 @@ Analyzes a webpage and detects **performance** or **front-end code quality** iss
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="70%">
|
||||
The <b>public API</b> - <a href="https://github.com/gmetais/YellowLabTools/wiki/Public-API" target="_blank">Doc here</a>
|
||||
The <b>public API</b> - <a href="https://github.com/YellowLabTools/YellowLabTools/wiki/Public-API" target="_blank">Doc here</a>
|
||||
<br>
|
||||
Allows you to launch runs from any language
|
||||
</td>
|
||||
@@ -72,7 +72,7 @@ You can use [ngrok](https://ngrok.com/), a tool that creates a secure tunnel bet
|
||||
|
||||
## Install your own private instance
|
||||
|
||||
If your project is not accessible from outside, or if you want to fork and improve the tool, you can build your own instance. The documentation is [here](https://github.com/gmetais/YellowLabTools/wiki/Install-your-private-server).
|
||||
If your project is not accessible from outside, or if you want to fork and improve the tool, you can build your own instance. The documentation is [here](https://github.com/YellowLabTools/YellowLabTools/wiki/Install-your-private-server).
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
@@ -158,14 +158,6 @@
|
||||
.summary .fromShare {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
.summary .apiTip {
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 4em;
|
||||
color: #413;
|
||||
}
|
||||
.summary .apiTip a {
|
||||
color: inherit;
|
||||
}
|
||||
.summary .tweet .tweetText {
|
||||
color: #413;
|
||||
background: #F2F2F2;
|
||||
@@ -189,3 +181,11 @@
|
||||
.summary .tweet input {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.summary .sponsor {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 4em;
|
||||
color: #413;
|
||||
}
|
||||
.summary .sponsor a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -177,3 +177,6 @@ input[type=submit].clicked {
|
||||
top: 0.2em;
|
||||
box-shadow: none;
|
||||
}
|
||||
.homeSponsor {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,9 @@ a.linkButton {
|
||||
.footer .github {
|
||||
margin: 1em 0 0 0.5em;
|
||||
}
|
||||
.footer .sponsor {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
[class^="icon-"]:before,
|
||||
[class*=" icon-"]:before {
|
||||
vertical-align: baseline;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -35,14 +35,14 @@ yltApp.run(['$rootScope', '$location', function($rootScope, $location) {
|
||||
|
||||
// Google Analytics
|
||||
$rootScope.$on('$routeChangeSuccess', function(){
|
||||
if (ga) {
|
||||
if (typeof ga !== "undefined") {
|
||||
ga('send', 'pageview', {'page': $location.path()});
|
||||
}
|
||||
});
|
||||
|
||||
// GitHub star button (asynchronously loaded iframe)
|
||||
window.addEventListener('load', function() {
|
||||
window.document.getElementById('ghbtn').src = 'http://ghbtns.com/github-btn.html?user=gmetais&repo=YellowLabTools&type=star&count=true&size=large';
|
||||
window.document.getElementById('ghbtn').src = 'https://ghbtns.com/github-btn.html?user=YellowLabTools&repo=YellowLabTools&type=star&count=true&size=large';
|
||||
});
|
||||
}]);
|
||||
|
||||
|
||||
@@ -41,11 +41,11 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams
|
||||
};
|
||||
|
||||
$scope.shareOnTwitter = function(message) {
|
||||
openSocialPopup('https://twitter.com/intent/tweet?text=' + encodeURIComponent(message + 'http://yellowlab.tools'));
|
||||
openSocialPopup('https://twitter.com/intent/tweet?text=' + encodeURIComponent(message + 'https://yellowlab.tools'));
|
||||
};
|
||||
|
||||
$scope.shareOnLinkedin = function(message) {
|
||||
openSocialPopup('https://www.linkedin.com/shareArticle?mini=true&url=http://yellowlab.tools&title=' + encodeURIComponent(message) + '&summary=' + encodeURIComponent('YellowLabTools is a free online tool that analyzes performance and front-end quality of a webpage.'));
|
||||
openSocialPopup('https://www.linkedin.com/shareArticle?mini=true&url=https://yellowlab.tools&title=' + encodeURIComponent(message) + '&summary=' + encodeURIComponent('YellowLabTools is a free online tool that analyzes performance and front-end quality of a webpage.'));
|
||||
};
|
||||
|
||||
function openSocialPopup(url) {
|
||||
|
||||
@@ -924,4 +924,15 @@
|
||||
};
|
||||
});
|
||||
|
||||
// Proxify an HTTP image to HTTPS if hosted on HTTPS
|
||||
// Uses a great free open-source external service: https://images.weserv.nl
|
||||
offendersDirectives.filter('https', function() {
|
||||
return function(url) {
|
||||
if (url && url.indexOf('http://') === 0 && window.location.protocol === 'https:') {
|
||||
return 'https://images.weserv.nl/?url=' + encodeURIComponent(url.substr(7));
|
||||
}
|
||||
return url;
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
@@ -158,15 +158,6 @@
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.summary .apiTip {
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 4em;
|
||||
color: #413;
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.summary .tweet {
|
||||
.tweetText {
|
||||
color: #413;
|
||||
@@ -189,4 +180,13 @@
|
||||
input {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
.summary .sponsor {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 4em;
|
||||
color: #413;
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
@@ -199,4 +199,8 @@ input[type=submit].clicked {
|
||||
left: 0.1em;
|
||||
top: 0.2em;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.homeSponsor {
|
||||
margin-top: 3em;
|
||||
}
|
||||
@@ -250,6 +250,9 @@ a.linkButton {
|
||||
.github {
|
||||
margin: 1em 0 0 0.5em;
|
||||
}
|
||||
.sponsor {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+4
-4
@@ -18,10 +18,10 @@
|
||||
<link rel="stylesheet" type="text/css" href="css/about.css">
|
||||
<!-- endbuild -->
|
||||
|
||||
<link rel="preconnect" href="//www.google-analytics.com">
|
||||
<link rel="dns-prefetch" href="//www.google-analytics.com">
|
||||
<link rel="preconnect" href="//ghbtns.com">
|
||||
<link rel="dns-prefetch" href="//ghbtns.com">
|
||||
<link rel="preconnect" href="https://www.google-analytics.com">
|
||||
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
||||
<link rel="preconnect" href="https://ghbtns.com">
|
||||
<link rel="dns-prefetch" href="https://ghbtns.com">
|
||||
<link rel="preconnect" href="https://api.github.com">
|
||||
<link rel="dns-prefetch" href="https://api.github.com">
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<div class="about">
|
||||
<p><b>Yellow Lab Tools</b> is an open source project by <a href="http://www.gaelmetais.com" target="_blank">Gaël Métais</a>. It lets you test a webpage (via an URL) and detects <b>performance</b> and <b>front-end code quality</b> issues.</p>
|
||||
<p><b>Yellow Lab Tools</b> is an open source project by <a href="http://www.gaelmetais.com" target="_blank">Gaël Métais</a>. It allows you to test a webpage (via an URL) and detects <b>performance</b> and <b>front-end code quality</b> issues.</p>
|
||||
|
||||
<p>This is done by loading the webpage via PhantomJS and collecting various metrics and statistics with the help of <a href="https://github.com/macbre/phantomas" target="_blank">Phantomas</a>. These metrics are categorized and transformed into scores. It also gives in-depth details so developpers can correct the detected issues.</p>
|
||||
<p>This is done by loading the webpage via PhantomJS and collecting various metrics and statistics with the help of <a href="https://github.com/macbre/phantomas" target="_blank">Phantomas</a>. These metrics are categorized and transformed into scores. It also gives in-depth details so that developers can correct the detected issues.</p>
|
||||
|
||||
<p>By the way, <b>it's free</b> because we are geeks, not businessmen. All we want is <a href="https://github.com/gmetais/YellowLabTools" target="_blank" class="star">a <span>★</span> on GitHub</a>. It will boost our motivation to add more awesome features!!!</p>
|
||||
<p>By the way, <b>it's free</b> because we are geeks, not businessmen. All we want is <a href="https://github.com/YellowLabTools/YellowLabTools" target="_blank" class="star">a <span>★</span> on GitHub</a>. It will boost our motivation to add more awesome features!!!</p>
|
||||
|
||||
<%if (sponsoring.about) { %>
|
||||
<div class="sponsor"><%- sponsoring.about %></div>
|
||||
<% } %>
|
||||
|
||||
<p><br><a href="<%= baseUrl %>">Back to index</a></p>
|
||||
</div>
|
||||
@@ -55,9 +55,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="!error && !fromSocialShare" class="apiTip">
|
||||
<b>Did you know?</b> You can <b>bookmark</b> this result page URL or <b>share it</b>, Yellow Lab Tools keeps test results for years!
|
||||
</div>
|
||||
<%if (sponsoring.dashboard) { %>
|
||||
<div ng-if="!error && !fromSocialShare" class="sponsor"><%- sponsoring.dashboard %></div>
|
||||
<% } %>
|
||||
|
||||
|
||||
<div class="tweet" ng-if="!error && !fromSocialShare">
|
||||
<form ng-submit="sendTweet()">
|
||||
|
||||
@@ -110,3 +110,7 @@
|
||||
<p>Untangles the JavaScript spaghetti code</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%if (sponsoring.home) { %>
|
||||
<div class="homeSponsor"><%- sponsoring.home %></div>
|
||||
<% } %>
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="status">Test failed</div>
|
||||
<p class="statusSubMessage">{{status.error}}</p>
|
||||
|
||||
<a class="linkButton" href="https://github.com/gmetais/YellowLabTools/issues" target="_blank">Report the issue on GitHub</a>
|
||||
<a class="linkButton" href="https://github.com/YellowLabTools/YellowLabTools/issues" target="_blank">Report the issue on GitHub</a>
|
||||
<a class="linkButton" href="<%= baseUrl %>">New test</a>
|
||||
</div>
|
||||
<div ng-if="status.statusCode == 'awaiting'">
|
||||
|
||||
@@ -133,10 +133,16 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="policyName === 'lazyLoadableImagesBelowTheFold' || policyName === 'hiddenImages'">
|
||||
<img ng-src="{{offender}}" class="smallPreview checker"></img>
|
||||
<img ng-src="{{offender | https}}" class="smallPreview checker"></img>
|
||||
<url-link url="offender" max-length="100"></url-link>
|
||||
</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'">
|
||||
<url-link url="offender" max-length="100"></url-link>
|
||||
</div>
|
||||
@@ -267,7 +273,7 @@
|
||||
<div ng-repeat="image in rule.offendersObj.list.images | orderBy:'-gain'">
|
||||
<div>
|
||||
Current file: <url-link url="image.url" max-length="50"></url-link>
|
||||
<div><a href="{{image.url}}" target="_blank"><img ng-src="{{image.url}}" class="checker" /></a></div>
|
||||
<div><a href="{{image.url}}" target="_blank"><img ng-src="{{image.url | https}}" class="checker" /></a></div>
|
||||
</div>
|
||||
<div>
|
||||
<p ng-if="!image.afterCompression">Current weight: {{image.original | bytes}}</p>
|
||||
@@ -364,7 +370,7 @@
|
||||
<div>Weight (bytes)</div>
|
||||
</div>
|
||||
<div ng-repeat="request in requests track by $index">
|
||||
<div ng-if="type == 'image'"><img ng-src="{{request.url}}" class="smallPreview checker" /></div>
|
||||
<div ng-if="type == 'image'"><img ng-src="{{request.url | https}}" class="smallPreview checker" /></div>
|
||||
<div><url-link url="request.url" max-length="100"></url-link></div>
|
||||
<div>{{request.size}}</div>
|
||||
</div>
|
||||
|
||||
@@ -276,7 +276,7 @@ var policies = {
|
||||
},
|
||||
"jQueryVersion": {
|
||||
"label": "jQuery version",
|
||||
"message": "<p>The current latest version of jQuery is 3.3</p><p>Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.</p>",
|
||||
"message": "<p>The current latest version of jQuery is 3.4</p><p>Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.</p>",
|
||||
"hasOffenders": false,
|
||||
"scoreFn": function(data) {
|
||||
var differentVersions = data.toolsResults.phantomas.metrics.jQueryVersionsLoaded;
|
||||
@@ -291,6 +291,8 @@ var policies = {
|
||||
if (value.indexOf('3.3.') === 0 ||
|
||||
value.indexOf('3.4.') === 0 ||
|
||||
value.indexOf('3.5.') === 0 ||
|
||||
value.indexOf('3.6.') === 0 ||
|
||||
value.indexOf('3.7.') === 0 ||
|
||||
value.indexOf('4.0.') === 0) {
|
||||
score = 100;
|
||||
} else if (value.indexOf('3.2.') === 0) {
|
||||
@@ -914,6 +916,15 @@ var policies = {
|
||||
"hasOffenders": true,
|
||||
"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 reducing their size.</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": {
|
||||
"tool": "redownload",
|
||||
"label": "Gzip compression",
|
||||
@@ -927,7 +938,7 @@ var policies = {
|
||||
"fileMinification": {
|
||||
"tool": "redownload",
|
||||
"label": "File minification",
|
||||
"message": "<p>This is the weight that could be saved if all text resources were correctly minified.</p><p>The tools in use here are <b>UglifyJS</b>, <b>clean-css</b> and <b>HTMLMinifier</b>. These tools are so good that some of your minified files can be marked as unminified. Change your tool it this happens :)</p><p>The gains of minification are generally small, but the impact can be high when these text files are loaded on the critical path.</p>",
|
||||
"message": "<p>This is the weight that could be saved if all text resources were correctly minified.</p><p>The tools in use here are <b>UglifyJS</b>, <b>clean-css</b> and <b>HTMLMinifier</b>. These tools are so good that some of your minified files can be marked as unminified. Change your tool if this happens :)</p><p>The gains of minification are generally small, but the impact can be high when these text files are loaded on the critical path.</p>",
|
||||
"isOkThreshold": 5125,
|
||||
"isBadThreshold": 61440,
|
||||
"isAbnormalThreshold": 122880,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"policies": {
|
||||
"totalWeight": 5,
|
||||
"imageOptimization": 2,
|
||||
"imagesTooLarge": 2,
|
||||
"gzipCompression": 2,
|
||||
"fileMinification": 1
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
var path = require('path');
|
||||
var express = require('express');
|
||||
|
||||
var serverSettings = (process.env.IS_TEST) ? require('../../../test/fixtures/settings.json') : require('../../../server_config/settings.json');
|
||||
|
||||
var FrontController = function(app) {
|
||||
'use strict';
|
||||
|
||||
@@ -14,7 +16,8 @@ var FrontController = function(app) {
|
||||
app.get(route, function(req, res) {
|
||||
res.setHeader('Cache-Control', 'public, max-age=20');
|
||||
res.render(path.join(__dirname, assetsPath, 'main.html'), {
|
||||
baseUrl: app.locals.baseUrl || '/'
|
||||
baseUrl: app.locals.baseUrl || '/',
|
||||
sponsoring: serverSettings.sponsoring || {}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,7 +26,8 @@ var FrontController = function(app) {
|
||||
app.get('/views/:viewName', function(req, res) {
|
||||
res.setHeader('Cache-Control', 'public, max-age=' + cacheDuration);
|
||||
res.render(path.join(__dirname, assetsPath, 'views/' + req.params.viewName), {
|
||||
baseUrl: app.locals.baseUrl || '/'
|
||||
baseUrl: app.locals.baseUrl || '/',
|
||||
sponsoring: serverSettings.sponsoring || {}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,25 +6,27 @@ var debug = require('debug')('apiLimitsMiddleware');
|
||||
var apiLimitsMiddleware = function(req, res, next) {
|
||||
'use strict';
|
||||
|
||||
debug('Entering API Limits Middleware with IP address %s', req.connection.remoteAddress);
|
||||
var ipAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
|
||||
debug('Entering API Limits Middleware with IP address %s', ipAddress);
|
||||
|
||||
if (req.path.indexOf('/api/') === 0 && !res.locals.hasApiKey) {
|
||||
|
||||
|
||||
if (req.path === '/api/runs') {
|
||||
|
||||
if (!runsTable.accepts(req.connection.remoteAddress)) {
|
||||
if (!runsTable.accepts(ipAddress)) {
|
||||
// Sorry :/
|
||||
debug('Too many tests launched from IP address %s', req.connection.remoteAddress);
|
||||
debug('Too many tests launched from IP address %s', ipAddress);
|
||||
res.status(429).send('Too many requests');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!callsTable.accepts(req.connection.remoteAddress)) {
|
||||
if (!callsTable.accepts(ipAddress)) {
|
||||
// Sorry :/
|
||||
debug('Too many API requests from IP address %s', req.connection.remoteAddress);
|
||||
debug('Too many API requests from IP address %s', ipAddress);
|
||||
res.status(429).send('Too many requests');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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 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 = {
|
||||
|
||||
+6
-4
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "yellowlabtools",
|
||||
"version": "1.13.0",
|
||||
"version": "1.13.4",
|
||||
"description": "Online tool to audit a webpage for performance and front-end quality issues",
|
||||
"license": "GPL-2.0",
|
||||
"author": {
|
||||
"name": "Gaël Métais",
|
||||
"email": "gael@gaelmetais.com",
|
||||
"url": "http://www.gaelmetais.com"
|
||||
"url": "https://www.gaelmetais.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gmetais/YellowLabTools.git"
|
||||
"url": "git://github.com/YellowLabTools/YellowLabTools.git"
|
||||
},
|
||||
"bin": {
|
||||
"yellowlabtools": "./bin/cli.js"
|
||||
@@ -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",
|
||||
@@ -96,7 +97,8 @@
|
||||
"sinon-chai": "~2.14.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
"test": "grunt test",
|
||||
"build": "grunt build"
|
||||
},
|
||||
"keywords": [
|
||||
"performance",
|
||||
|
||||
@@ -9,5 +9,11 @@
|
||||
},
|
||||
"maxAnonymousRunsPerDay": 99999999,
|
||||
"maxAnonymousCallsPerDay": 99999999,
|
||||
"blockedUrls": []
|
||||
"blockedUrls": [],
|
||||
|
||||
"sponsoring" : {
|
||||
"home": "(this is a private instance)",
|
||||
"dashboard": null,
|
||||
"about": "(this is a private instance)"
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,11 @@
|
||||
},
|
||||
"maxAnonymousRunsPerDay": 99999999,
|
||||
"maxAnonymousCallsPerDay": 99999999,
|
||||
"blockedUrls": []
|
||||
"blockedUrls": [],
|
||||
|
||||
"sponsoring" : {
|
||||
"home": "(this is a private instance)",
|
||||
"dashboard": null,
|
||||
"about": "(this is a private instance)"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -161,6 +161,8 @@ describe('imageOptimizer', function() {
|
||||
});
|
||||
|
||||
it('should optimize an SVG image losslessly', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
var fileContent = fs.readFileSync(path.resolve(__dirname, '../www/svg-image.svg'));
|
||||
|
||||
var fileSize = fileContent.length;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user