policies.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. var debug = require('debug')('ylt:policies');
  2. var offendersHelpers = require('../offendersHelpers');
  3. var policies = {
  4. "DOMelementsCount": {
  5. "tool": "phantomas",
  6. "label": "DOM elements count",
  7. "message": "<p>A high number of DOM elements means a lot of work for the browser to render the page.</p><p>It also slows down JavaScript DOM queries, as there are more elements to search through.</p>",
  8. "isOkThreshold": 1000,
  9. "isBadThreshold": 2500,
  10. "isAbnormalThreshold": 4000,
  11. "hasOffenders": false
  12. },
  13. "DOMelementMaxDepth": {
  14. "tool": "phantomas",
  15. "label": "DOM max depth",
  16. "message": "<p>A deep DOM makes the CSS matching with DOM elements difficult.</p><p>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.</p>",
  17. "isOkThreshold": 10,
  18. "isBadThreshold": 20,
  19. "isAbnormalThreshold": 28,
  20. "hasOffenders": true,
  21. "offendersTransformFn": function(offenders) {
  22. var domArrays = offenders.map(offendersHelpers.domPathToArray);
  23. return {
  24. count: offenders.length,
  25. tree: offendersHelpers.listOfDomArraysToTree(domArrays)
  26. };
  27. }
  28. },
  29. "iframesCount": {
  30. "tool": "phantomas",
  31. "label": "Number of iframes",
  32. "message": "<p>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.</p>",
  33. "isOkThreshold": 2,
  34. "isBadThreshold": 15,
  35. "isAbnormalThreshold": 30,
  36. "hasOffenders": false
  37. },
  38. "DOMidDuplicated": {
  39. "tool": "phantomas",
  40. "label": "IDs duplicated",
  41. "message": "<p>IDs of HTML elements must be document-wide unique. This can cause problems with getElementById returning the wrong element.</p>",
  42. "isOkThreshold": 0,
  43. "isBadThreshold": 5,
  44. "isAbnormalThreshold": 50,
  45. "hasOffenders": true,
  46. "offendersTransformFn": function(offenders) {
  47. return {
  48. count: offenders.length,
  49. list: offenders.map(function(offender) {
  50. var parts = /^(.*): ?(\d+) ?occurrences$/.exec(offender);
  51. if (!parts) {
  52. debug('DOMidDuplicated offenders transform function error with "%s"', offender);
  53. return {
  54. parseError: offender
  55. };
  56. }
  57. return {
  58. id: parts[1],
  59. occurrences: parseInt(parts[2], 10)
  60. };
  61. })
  62. };
  63. }
  64. },
  65. "DOMaccesses": {
  66. "tool": "jsExecutionTransformer",
  67. "label": "DOM access",
  68. "message": "<p>This metric counts the number of calls to DOM related functions (both native DOM functions and jQuery functions) on page load.</p><p>The more your JavaScript code accesses the DOM, the slower the page will load.</p><p>Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.</p><p>Try to reduce the number of queries by refactoring your JavaScript code.</p><p>Binding too many events also has a cost. Try to use <a href=\"https://learn.jquery.com/events/event-delegation/\" target=\"_blank\">event delegation</a> as much as possible.</p>",
  69. "isOkThreshold": 50,
  70. "isBadThreshold": 1500,
  71. "isAbnormalThreshold": 3000,
  72. "hasOffenders": false
  73. },
  74. "queriesWithoutResults": {
  75. "tool": "jsExecutionTransformer",
  76. "label": "Queries without result",
  77. "message": "<p>Number of queries that return no result. Both native and jQuery DOM requests are counted.</p><p>It suggests the query is not used on the page, probably because it is some dead code.</p><p>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.</p>",
  78. "isOkThreshold": 0,
  79. "isBadThreshold": 100,
  80. "isAbnormalThreshold": 200,
  81. "hasOffenders": false
  82. },
  83. "DOMqueriesAvoidable": {
  84. "tool": "phantomas",
  85. "label": "Duplicated DOM queries",
  86. "message": "<p>This is the number of queries that could be avoided by removing all duplicated queries.</p><p>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.</p>",
  87. "isOkThreshold": 0,
  88. "isBadThreshold": 200,
  89. "isAbnormalThreshold": 500,
  90. "hasOffenders": true,
  91. "takeOffendersFrom": "DOMqueriesDuplicated",
  92. "offendersTransformFn": function(offenders) {
  93. return {
  94. count: offenders.length,
  95. list: offenders.map(function(offender) {
  96. var parts = /^[^"]* ?"(.*)" ?with ?(.*) ?\(in ?context ?(.*)\): ?(.*)\s?queries$/.exec(offender);
  97. if (!parts) {
  98. debug('DOMqueriesAvoidable offenders transform function error with "%s"', offender);
  99. return {
  100. parseError: offender
  101. };
  102. }
  103. return {
  104. query: parts[1],
  105. context: offendersHelpers.domPathToDomElementObj(parts[3]),
  106. fn: parts[2],
  107. count: parseInt(parts[4], 10)
  108. };
  109. })
  110. };
  111. }
  112. },
  113. "eventsScrollBound": {
  114. "tool": "phantomas",
  115. "label": "Scroll events bound",
  116. "message": "<p>Number of 'scroll' event listeners binded to 'window' or 'document'.</p><p>Asking too much work to the browser on scroll hurts the smoothness of the scroll. Merging all your event listeners into an unique listener can help you factorize their code and reduce their footprint on scroll.</p>",
  117. "isOkThreshold": 1,
  118. "isBadThreshold": 7,
  119. "isAbnormalThreshold": 15,
  120. "hasOffenders": true,
  121. "offendersTransformFn": function(offenders) {
  122. return {
  123. count: offenders.length,
  124. list: offenders.map(function(offender) {
  125. var parts = /^bound by (.*) on ([^ ]+)$/.exec(offender);
  126. if (!parts) {
  127. debug('eventsScrollBound offenders transform function error with "%s"', offender);
  128. return {
  129. parseError: offender
  130. };
  131. }
  132. var backtraceArray = offendersHelpers.backtraceToArray(parts[1]);
  133. return {
  134. backtrace: backtraceArray || [],
  135. target: parts[2]
  136. };
  137. })
  138. };
  139. }
  140. },
  141. "DOMaccessesOnScroll": {
  142. "tool": "jsExecutionTransformer",
  143. "label": "DOM access on scroll",
  144. "message": "<p>This rule counts the number of DOM-accessing functions calls on a scroll event, such as queries, readings, writings, bindings and jQuery functions.</p><p>Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.</p><p>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.</p><p>Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use <a href=\"http://blogorama.nerdworks.in/javascriptfunctionthrottlingan/\" target=\"_blank\">throttling or debouncing</a>.</p>",
  145. "isOkThreshold": 1,
  146. "isBadThreshold": 20,
  147. "isAbnormalThreshold": 35,
  148. "hasOffenders": true,
  149. "offendersTransformFn": function(offenders) {
  150. return offenders;
  151. }
  152. },
  153. "jsErrors": {
  154. "tool": "phantomas",
  155. "label": "JavaScript errors",
  156. "message": "<p>Just to let you know there are some errors on the page.</p><p><b>Please note that some errors only occur in the PhantomJS browser, so you might need to double check on other browsers.</b></p>",
  157. "isOkThreshold": 0,
  158. "isBadThreshold": 1,
  159. "isAbnormalThreshold": 4,
  160. "hasOffenders": true,
  161. "offendersTransformFn": function(offenders) {
  162. return {
  163. count: offenders.length,
  164. list: offenders.map(function(offender) {
  165. var parts = /^(.*) - (.*)$/.exec(offender);
  166. if (!parts) {
  167. debug('jsErrors offenders transform function error with "%s"', offender);
  168. return {
  169. parseError: offender
  170. };
  171. }
  172. var backtraceArray = offendersHelpers.backtraceToArray(parts[2]);
  173. return {
  174. error: parts[1],
  175. backtrace: backtraceArray || []
  176. };
  177. })
  178. };
  179. }
  180. },
  181. "documentWriteCalls": {
  182. "tool": "phantomas",
  183. "label": "document.write calls",
  184. "message": "<p>They slow down the page construction, especially if they are used to insert scripts in the page. Remove them ASAP.</p><p>If you cannot remove them because they come from a third-party script (such as ads), have a look at <a href=\"https://github.com/krux/postscribe\" target=\"_blank\">PostScribe</a>.</p>",
  185. "isOkThreshold": 0,
  186. "isBadThreshold": 5,
  187. "isAbnormalThreshold": 10,
  188. "hasOffenders": true,
  189. "offendersTransformFn": function(offenders) {
  190. return {
  191. count: offenders.length,
  192. list: offenders.map(function(offender) {
  193. var parts = /^document.write(ln)?\(\) used from (.*)$/.exec(offender);
  194. if (parts) {
  195. var writeFn = 'document.write' + (parts[1] || '');
  196. var methodParts = /^([^\s]+) \((.+):(\d+)\)$/.exec(parts[2]);
  197. if (methodParts) {
  198. return {
  199. writeFn: writeFn,
  200. from: {
  201. functionName: methodParts[1],
  202. file: methodParts[2],
  203. line: methodParts[3]
  204. }
  205. };
  206. } else {
  207. var noMethodParts = /^(.+):(\d+)$/.exec(parts[2]);
  208. if (noMethodParts) {
  209. return {
  210. writeFn: writeFn,
  211. from: {
  212. file: noMethodParts[1],
  213. line: noMethodParts[2]
  214. }
  215. };
  216. }
  217. }
  218. }
  219. debug('documentWriteCalls offenders transform function error with "%s"', offender);
  220. return {
  221. parseError: offender
  222. };
  223. })
  224. };
  225. }
  226. },
  227. "synchronousXHR": {
  228. "tool": "phantomas",
  229. "label": "Synchronous Ajax requests",
  230. "message": "<p>Making an XMLHttpRequest with the <i>async</i> option set to <i>false</i> is deprecated due to the negative effect to performances. The browser's main thread needs to stop everything until the response is received.</p>",
  231. "isOkThreshold": 0,
  232. "isBadThreshold": 1,
  233. "isAbnormalThreshold": 1,
  234. "hasOffenders": true
  235. },
  236. "consoleMessages": {
  237. "tool": "phantomas",
  238. "label": "Console messages",
  239. "message": "<p>Try to keep your console clean when in production. Debugging is good for development only.</p><p>Writing in the console has a cost, especially when dumping large object variables.</p><p>There is also a problem with Internet Explorer 8, not knowing the console object.</p>",
  240. "isOkThreshold": 0,
  241. "isBadThreshold": 10,
  242. "isAbnormalThreshold": 50,
  243. "hasOffenders": false
  244. },
  245. "globalVariables": {
  246. "tool": "phantomas",
  247. "label": "Global variables",
  248. "message": "<p>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.</p><p>Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.</p>",
  249. "isOkThreshold": 40,
  250. "isBadThreshold": 200,
  251. "isAbnormalThreshold": 700,
  252. "hasOffenders": true,
  253. "offendersTransformFn": function(offenders) {
  254. return {
  255. count: offenders.length,
  256. list: offendersHelpers.sortVarsLikeChromeDevTools(offenders)
  257. };
  258. }
  259. },
  260. "jQueryVersion": {
  261. "label": "jQuery version",
  262. "message": "<p>The current latest version of jQuery is 3.0</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>",
  263. "hasOffenders": false,
  264. "scoreFn": function(data) {
  265. var differentVersions = data.toolsResults.phantomas.metrics.jQueryVersionsLoaded;
  266. if (differentVersions === 0 || differentVersions > 1 || !data.toolsResults.phantomas.metrics.jQueryVersion) {
  267. // Not applicable
  268. return null;
  269. } else {
  270. var value = data.toolsResults.phantomas.metrics.jQueryVersion;
  271. var score;
  272. if (value.indexOf('3.1.') === 0 ||
  273. value.indexOf('3.2.') === 0 ||
  274. value.indexOf('3.3.') === 0) {
  275. score = 100;
  276. } else if (value.indexOf('3.0.') === 0) {
  277. score = 90;
  278. } else if (value.indexOf('1.12.') === 0 ||
  279. value.indexOf('2.2.') === 0) {
  280. score = 70;
  281. } else if (value.indexOf('1.11.') === 0 ||
  282. value.indexOf('2.1.') === 0) {
  283. score = 50;
  284. } else if (value.indexOf('1.10.') === 0 ||
  285. value.indexOf('2.0.') === 0) {
  286. score = 40;
  287. } else if (value.indexOf('1.9.') === 0) {
  288. score = 30;
  289. } else if (value.indexOf('1.8.') === 0) {
  290. score = 20;
  291. } else if (value.indexOf('1.7') === 0) {
  292. score = 10;
  293. } else if (value.indexOf('1.6') === 0 ||
  294. value.indexOf('1.5') === 0 ||
  295. value.indexOf('1.4') === 0 ||
  296. value.indexOf('1.3') === 0 ||
  297. value.indexOf('1.2') === 0) {
  298. score = 0;
  299. } else {
  300. debug('Unknown jQuery version "%s"', value);
  301. return null;
  302. }
  303. // Truncate version number (can be long sometimes, no clue why but it can...)
  304. if (value.length > 30) {
  305. value = value.substr(0, 28) + '...';
  306. }
  307. return {
  308. value: value,
  309. score: score,
  310. bad: value < 100,
  311. abnormal: false,
  312. abnormalityScore: 0
  313. };
  314. }
  315. }
  316. },
  317. "jQueryVersionsLoaded": {
  318. "tool": "phantomas",
  319. "label": "Several jQuery loaded",
  320. "message": "<p>jQuery is a heavy library. You should <b>never</b> load jQuery more than once on the same page.</p>",
  321. "isOkThreshold": 1,
  322. "isBadThreshold": 2,
  323. "isAbnormalThreshold": 2,
  324. "hasOffenders": true
  325. },
  326. "jQueryCallsOnEmptyObject": {
  327. "tool": "jsExecutionTransformer",
  328. "label": "Calls on empty objects",
  329. "message": "<p>This metric counts the number of jQuery functions called on an empty jQuery object. The call was useless.</p><p>This can be helpful to detect dead or unused code.</p>",
  330. "isOkThreshold": 1,
  331. "isBadThreshold": 100,
  332. "isAbnormalThreshold": 180,
  333. "hasOffenders": false
  334. },
  335. "jQueryNotDelegatedEvents": {
  336. "tool": "jsExecutionTransformer",
  337. "label": "Events not delegated",
  338. "message": "<p>This is the number of events that are bound with the .bind() or the .on() function without using <a href=\"https://learn.jquery.com/events/event-delegation/\" target=\"_blank\">event delegation</a>.</p><p>This means jQuery binds each element contained in the object one by one. This is bad for performance.</p>",
  339. "isOkThreshold": 1,
  340. "isBadThreshold": 100,
  341. "isAbnormalThreshold": 180,
  342. "hasOffenders": false
  343. },
  344. "cssParsingErrors": {
  345. "tool": "phantomas",
  346. "label": "CSS syntax error",
  347. "message": "<p>Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.</p><p>Maybe a <a href=\"http://jigsaw.w3.org/css-validator\" target=\"_blank\">CSS validator</a> can help you.</p>",
  348. "isOkThreshold": 0,
  349. "isBadThreshold": 1,
  350. "isAbnormalThreshold": 20,
  351. "hasOffenders": true,
  352. "offendersTransformFn": function(offenders) {
  353. return {
  354. count: offenders.length,
  355. list: offenders.map(function(offender) {
  356. if (offender === '[inline CSS] (Empty CSS was provided)') {
  357. return {
  358. error: 'Empty style tag',
  359. file: null,
  360. line: null,
  361. column: null
  362. };
  363. }
  364. var parts = /^(?:(?:<([^ \(]*)>|\[inline CSS\]) ?)?(?:\((((?! @ ).)*)(?: @ (\d+):(\d+))?\))?$/.exec(offender);
  365. if (parts) {
  366. return {
  367. error: parts[2] || 'Unknown parsing error' + (parts[1] ? '. The entire file was ignored. As a result, the other CSS metrics and scores are miscalculated.' : ''),
  368. file: parts[1] || null,
  369. line: (parts[4] && parts[5]) ? parseInt(parts[4], 10) : null,
  370. column: (parts[4] && parts[5]) ? parseInt(parts[5], 10) : null
  371. };
  372. }
  373. // Try another syntax
  374. parts = /^(.*) <(.*)> @ (\d+):(\d+)$/.exec(offender);
  375. if (parts) {
  376. return {
  377. error: parts[1] || 'Unknown parsing error',
  378. file: parts[2] || null,
  379. line: parseInt(parts[3], 10),
  380. column: parseInt(parts[4], 10)
  381. };
  382. }
  383. debug('cssParsingErrors offenders transform function error with "%s"', offender);
  384. return {
  385. parseError: offender
  386. };
  387. })
  388. };
  389. }
  390. },
  391. "cssRules": {
  392. "tool": "phantomas",
  393. "label": "Rules count",
  394. "message": "<p>Having a huge number of CSS rules hurts performances. If the number of CSS rules is higher than the number of DOM elements, there is clearly a problem.</p><p>Huge stylesheets generally occur when the different pages of a website load all the CSS, concatenated in a single stylesheet, even if a large part of the rules are page-specific. Solution is to create one main CSS file with global rules and one custom file per page.</p>",
  395. "isOkThreshold": 750,
  396. "isBadThreshold": 3000,
  397. "isAbnormalThreshold": 4500,
  398. "hasOffenders": true,
  399. "offendersTransformFn": function(offenders) {
  400. var hasInline = false;
  401. var inlineCount = 0;
  402. var files = [];
  403. offenders.forEach(function(line) {
  404. if (line.indexOf('[inline CSS]: ') === 0) {
  405. hasInline = true;
  406. inlineCount += parseInt(line.substr(14));
  407. } else {
  408. var parts = /^<(.*)>: (\d+)$/.exec(line);
  409. if (parts) {
  410. files.push({
  411. file: parts[1],
  412. rules: parseInt(parts[2], 10)
  413. });
  414. }
  415. }
  416. });
  417. if (hasInline) {
  418. files.push({
  419. file: 'inline CSS',
  420. rules: inlineCount
  421. });
  422. }
  423. return {
  424. count: files.length,
  425. list: files
  426. };
  427. }
  428. },
  429. "cssComplexSelectors": {
  430. "tool": "phantomas",
  431. "label": "Complex selectors",
  432. "message": "<p>Complex selectors are CSS selectors with 4 or more expressions, like \"#header ul li .foo\".</p><p>They are adding more work for the browser, and this could be avoided by simplifying selectors. The <a href=\"http://getbem.com\" target=\"_blank\">B.E.M. methodology</a> is an useful way to simplify your CSS.</p>",
  433. "isOkThreshold": 0,
  434. "isBadThreshold": 600,
  435. "isAbnormalThreshold": 2000,
  436. "hasOffenders": true,
  437. "offendersTransformFn": function(offenders) {
  438. var parsedOffenders = offenders.map(function(offender) {
  439. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  440. return splittedOffender;
  441. });
  442. return offendersHelpers.orderByFile(parsedOffenders);
  443. }
  444. },
  445. "cssColors": {
  446. "tool": "phantomas",
  447. "label": "Colors count",
  448. "message": "<p>This is the number of different colors defined in CSS.</p><p>Your CSS will be easier to maintain if you keep a small color set.</p>",
  449. "isOkThreshold": 30,
  450. "isBadThreshold": 150,
  451. "isAbnormalThreshold": 400,
  452. "hasOffenders": true,
  453. "offendersTransformFn": function(offenders, ruleObject) {
  454. var deduplicatedObj = {};
  455. offenders.map(function(offender) {
  456. var parts = /^([^ ]*) \((\d+) times\)$/.exec(offender);
  457. if (!parts) {
  458. debug('cssColors offenders transform function error with "%s"', offender);
  459. return;
  460. }
  461. var color = parts[1];
  462. var count = parseInt(parts[2], 10);
  463. deduplicatedObj[color] = (deduplicatedObj[color] || 0) + count;
  464. });
  465. var deduplicatedTable = [];
  466. for (var color in deduplicatedObj) {
  467. deduplicatedTable.push({
  468. color: color,
  469. occurrences: deduplicatedObj[color]
  470. });
  471. }
  472. deduplicatedTable.sort(function(a, b) {
  473. return b.occurrences - a.occurrences;
  474. });
  475. // Override rules.value
  476. ruleObject.value = deduplicatedTable.length;
  477. return {
  478. count: deduplicatedTable.length,
  479. palette: deduplicatedTable
  480. };
  481. }
  482. },
  483. "similarColors": {
  484. "tool": "colorDiff",
  485. "label": "Similar colors",
  486. "message": "<p>This is the list of colors found in the stylesheets, that are very close to each other. The eye can barely see the difference.</p><p>Use this list to reduce the number of colors in your palette, it will be easier to maintain.</p>",
  487. "isOkThreshold": 0,
  488. "isBadThreshold": 40,
  489. "isAbnormalThreshold": 80,
  490. "hasOffenders": true,
  491. "offendersTransformFn": function(offenders) {
  492. return {
  493. count: offenders.length,
  494. list: offenders
  495. };
  496. }
  497. },
  498. "cssBreakpoints": {
  499. "tool": "mediaQueriesChecker",
  500. "label": "Breakpoints count",
  501. "message": "<p>This is the number of different breakpoints found in the stylesheets' media queries.</p><p>Please note this rule is based on <i>min-width</i>, <i>max-width</i>, <i>min-device-width</i> and <i>max-device-width</i> media queries only.</p><p>Your CSS will be easier to maintain if you keep a reasonable number of breakpoints. Try to make a fluid design - using percents - to avoid the creation of numerous breakpoints.</p>",
  502. "isOkThreshold": 6,
  503. "isBadThreshold": 40,
  504. "isAbnormalThreshold": 60,
  505. "hasOffenders": true,
  506. "offendersTransformFn": function(offenders) {
  507. var offendersTable = [];
  508. for (var offender in offenders) {
  509. offendersTable.push({
  510. breakpoint: offender,
  511. count: offenders[offender].count,
  512. pixels: offenders[offender].pixels
  513. });
  514. }
  515. return offendersTable;
  516. }
  517. },
  518. "cssMobileFirst": {
  519. "tool": "mediaQueriesChecker",
  520. "label": "Not mobile-first media queries",
  521. "message": "<p>This is the number of CSS rules inside media queries that address small screens.</p><p>The common good practice, when creating a responsive website, is to write it \"mobile-first\". More explanation in <a href=\"http://www.sitepoint.com/introduction-mobile-first-media-queries\" target=\"_blank\">this great article</a>.</p>",
  522. "isOkThreshold": 25,
  523. "isBadThreshold": 200,
  524. "isAbnormalThreshold": 1000,
  525. "hasOffenders": true,
  526. "offendersTransformFn": function(offenders) {
  527. return offendersHelpers.orderByFile(offenders);
  528. }
  529. },
  530. "cssImports": {
  531. "tool": "phantomas",
  532. "label": "Uses of @import",
  533. "message": "<p>It’s bad for performance to use @import because CSS files don't get downloaded in parallel.</p><p>You should use &lt;link rel='stylesheet' href='a.css'&gt; instead.</p>",
  534. "isOkThreshold": 0,
  535. "isBadThreshold": 1,
  536. "isAbnormalThreshold": 1,
  537. "hasOffenders": true,
  538. "offendersTransformFn": function(offenders) {
  539. return {
  540. count: offenders.length,
  541. list: offenders.map(function(offender) {
  542. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  543. return splittedOffender;
  544. })
  545. };
  546. }
  547. },
  548. "cssDuplicatedSelectors": {
  549. "tool": "phantomas",
  550. "label": "Duplicated selectors",
  551. "message": "<p>This is when two or more selectors are strictly identical and should be merged.</p>",
  552. "isOkThreshold": 0,
  553. "isBadThreshold": 50,
  554. "isAbnormalThreshold": 100,
  555. "hasOffenders": true,
  556. "offendersTransformFn": function(offenders) {
  557. return {
  558. count: offenders.length,
  559. list: offenders.map(function(offender) {
  560. var parts = /^(.*) \((\d+) times\)$/.exec(offender);
  561. if (!parts) {
  562. debug('cssDuplicatedSelectors offenders transform function error with "%s"', offender);
  563. return {
  564. parseError: offender
  565. };
  566. }
  567. return {
  568. rule: parts[1],
  569. occurrences: parseInt(parts[2], 10)
  570. };
  571. })
  572. };
  573. }
  574. },
  575. "cssDuplicatedProperties": {
  576. "tool": "phantomas",
  577. "label": "Duplicated properties",
  578. "message": "<p>This is the number of property definitions duplicated within a selector.</p>",
  579. "isOkThreshold": 0,
  580. "isBadThreshold": 60,
  581. "isAbnormalThreshold": 120,
  582. "hasOffenders": true,
  583. "offendersTransformFn": function(offenders) {
  584. var parsedOffenders = offenders.map(function(offender) {
  585. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  586. var parts = /^([^{]+) {([^ ]+): (.+)}$/.exec(splittedOffender.css);
  587. if (!parts) {
  588. debug('cssDuplicatedProperties offenders transform function error with "%s"', offender);
  589. return {
  590. parseError: offender
  591. };
  592. }
  593. return {
  594. property: parts[2],
  595. rule: parts[1],
  596. file: splittedOffender.file,
  597. line: splittedOffender.line,
  598. column: splittedOffender.column
  599. };
  600. });
  601. return offendersHelpers.orderByFile(parsedOffenders);
  602. }
  603. },
  604. "cssEmptyRules": {
  605. "tool": "phantomas",
  606. "label": "Empty rules",
  607. "message": "<p>Very easy to fix: remove all empty rules.</p>",
  608. "isOkThreshold": 0,
  609. "isBadThreshold": 50,
  610. "isAbnormalThreshold": 100,
  611. "hasOffenders": true,
  612. "offendersTransformFn": function(offenders) {
  613. var parsedOffenders = offenders.map(offendersHelpers.cssOffenderPattern);
  614. return offendersHelpers.orderByFile(parsedOffenders);
  615. }
  616. },
  617. "cssExpressions": {
  618. "tool": "phantomas",
  619. "label": "CSS expressions",
  620. "message": "<p>Such as: expression( document.body.clientWidth > 600 ? \"600px\" : \"auto\" )</p><p>This is a bad practice as it slows down browsers. There are some simpler CSS3 methods for doing this.</p>",
  621. "isOkThreshold": 0,
  622. "isBadThreshold": 1,
  623. "isAbnormalThreshold": 20,
  624. "hasOffenders": true,
  625. "offendersTransformFn": function(offenders) {
  626. var parsedOffenders = offenders.map(function(offender) {
  627. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  628. var parts = /^(.*) {([^ ]+): expression\((.*)\)}$/.exec(splittedOffender.css);
  629. if (!parts) {
  630. debug('cssExpressions offenders transform function error with "%s"', offender);
  631. return {
  632. parseError: offender
  633. };
  634. }
  635. return {
  636. rule: parts[1],
  637. property: parts[2],
  638. expression: parts[3],
  639. file: splittedOffender.file,
  640. line: splittedOffender.line,
  641. column: splittedOffender.column
  642. };
  643. });
  644. return offendersHelpers.orderByFile(parsedOffenders);
  645. }
  646. },
  647. "cssImportants": {
  648. "tool": "phantomas",
  649. "label": "Uses of !important",
  650. "message": "<p>It can be useful, but only as a last resort. It is a bad practice because it overrides the normal cascading logic. The more you use !important, the more you need it again to over-override. This conducts to a poor maintainability.</p>",
  651. "isOkThreshold": 0,
  652. "isBadThreshold": 75,
  653. "isAbnormalThreshold": 200,
  654. "hasOffenders": true,
  655. "offendersTransformFn": function(offenders) {
  656. var parsedOffenders = offenders.map(function(offender) {
  657. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  658. var parts = /^(.*) {([^ ]+): (.*) ?\!important}$/.exec(splittedOffender.css);
  659. if (!parts) {
  660. debug('cssImportants offenders transform function error with "%s"', offender);
  661. return {
  662. parseError: offender
  663. };
  664. }
  665. return {
  666. rule: parts[1],
  667. property: parts[2],
  668. value: parts[3],
  669. file: splittedOffender.file,
  670. line: splittedOffender.line,
  671. column: splittedOffender.column
  672. };
  673. });
  674. return offendersHelpers.orderByFile(parsedOffenders);
  675. }
  676. },
  677. "cssOldIEFixes": {
  678. "tool": "phantomas",
  679. "label": "Old IE fixes",
  680. "message": "<p>What browser do you need to support? Once you've got the answer, take a look at these old rules that pollute your CSS code and remove them.</p><p>IE6:<ul><li>* html</li><li>html > body (everything but IE6)</li></ul><p><p>IE7:<ul><li><b>*</b>height: 123px;</li><li>height: 123px <b>!ie</b>;</li></ul><p><p>IE9:<ul><li>-ms-filter</li><li>progid:DXImageTransform.Microsoft</li></ul></p>",
  681. "isOkThreshold": 0,
  682. "isBadThreshold": 75,
  683. "isAbnormalThreshold": 300,
  684. "hasOffenders": true,
  685. "offendersTransformFn": function(offenders) {
  686. var parsedOffenders = offenders.map(function(offender) {
  687. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  688. var parts = /^([^{]*)( {([^ ]+): (.*)})?$/.exec(splittedOffender.css);
  689. if (!parts) {
  690. debug('cssOldIEFixes offenders transform function error with "%s"', offender);
  691. return {
  692. parseError: offender
  693. };
  694. }
  695. var rule = parts[1];
  696. var property = parts[3];
  697. var value = parts[4];
  698. var browser = null;
  699. if (rule.indexOf('* html') === 0) {
  700. rule = rule.replace(/^\* html/, '<b>* html</b>');
  701. browser = 'IE6';
  702. } else if (rule.indexOf('html>body') === 0) {
  703. rule = rule.replace(/^html>body/, '<b>html>body</b>');
  704. browser = 'IE6';
  705. } else if (property.indexOf('*') === 0) {
  706. property = '<b>' + property + '</b>';
  707. browser = 'IE7';
  708. } else if (value.match(/\!ie$/)) {
  709. value = value.replace(/\!ie$/, '<b>!ie</b>');
  710. browser = 'IE7';
  711. } else if (property === '-ms-filter') {
  712. property = '<b>-ms-filter</b>';
  713. browser = 'IE9';
  714. } else if (value.indexOf('progid:DXImageTransform.Microsoft') >= 0) {
  715. value = value.replace(/progid:DXImageTransform\.Microsoft/, '<b>progid:DXImageTransform.Microsoft</b>');
  716. browser = 'IE9';
  717. }
  718. var propertyAndValue = (property && value) ? ' {' + property + ': ' + value + '}' : '';
  719. splittedOffender.bolded = rule + propertyAndValue;
  720. splittedOffender.browser = browser;
  721. return splittedOffender;
  722. });
  723. return offendersHelpers.orderByFile(parsedOffenders);
  724. }
  725. },
  726. "cssOldPropertyPrefixes": {
  727. "tool": "phantomas",
  728. "label": "Old prefixes",
  729. "message": "<p>Many property prefixes such as -moz- or -webkit- are not needed anymore, or by very few people. Sometimes, they have never even existed. You can remove them or replace them with the non-prefixed version. This will help reducing your stylesheets weight.</p><p>The prefixes database comes from <a href=\"http://caniuse.com/\" target=\"_blank\">Can I Use</a>.</p>",
  730. "isOkThreshold": 0,
  731. "isBadThreshold": 75,
  732. "isAbnormalThreshold": 300,
  733. "hasOffenders": true,
  734. "offendersTransformFn": function(offenders) {
  735. var properties = {};
  736. offenders.forEach(function(offender) {
  737. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  738. var parts = /^([^{]*)(?: ?{ ?([^ ]+): (.*) ?}) \/\/ (.*)$/.exec(splittedOffender.css);
  739. if (!parts) {
  740. debug('cssOldPropertyPrefixes offenders transform function error with "%s"', offender);
  741. return {
  742. parseError: offender
  743. };
  744. }
  745. var propertyName = parts[2];
  746. if (!properties[propertyName]) {
  747. properties[propertyName] = {
  748. property: propertyName,
  749. message: parts[4],
  750. rules: []
  751. };
  752. }
  753. properties[propertyName].rules.push({
  754. rule: parts[1],
  755. value: parts[3],
  756. file: splittedOffender.file,
  757. line: splittedOffender.line,
  758. column: splittedOffender.column
  759. });
  760. });
  761. // Object to array
  762. var list = [];
  763. for (var propertyName in properties) {
  764. list.push(properties[propertyName]);
  765. }
  766. return {
  767. count: offenders.length,
  768. list: list
  769. };
  770. }
  771. },
  772. "cssRedundantBodySelectors": {
  773. "tool": "phantomas",
  774. "label": "Redundant body selectors",
  775. "message": "<p>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.</p>",
  776. "isOkThreshold": 0,
  777. "isBadThreshold": 60,
  778. "isAbnormalThreshold": 200,
  779. "hasOffenders": true,
  780. "offendersTransformFn": function(offenders) {
  781. var parsedOffenders = offenders.map(function(offender) {
  782. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  783. splittedOffender.bolded = splittedOffender.css.replace(/body/, '<b>body</b>');
  784. return splittedOffender;
  785. });
  786. return offendersHelpers.orderByFile(parsedOffenders);
  787. }
  788. },
  789. "cssRedundantChildNodesSelectors": {
  790. "tool": "phantomas",
  791. "label": "Redundant tags selectors",
  792. "message": "<p>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\" tag is nearly always inside an \"ul\" container (the \"ol\" container is quite rare). Same thing for \"tr td\", \"select option\", ...</p><p>Lowering compexity in CSS selectors can make the page load a little faster.</p>",
  793. "isOkThreshold": 0,
  794. "isBadThreshold": 60,
  795. "isAbnormalThreshold": 200,
  796. "hasOffenders": true,
  797. "offendersTransformFn": function(offenders) {
  798. var parsedOffenders = offenders.map(function(offender) {
  799. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  800. var rule = splittedOffender.css || '';
  801. var redundanters = [
  802. ['ul', 'li'],
  803. ['ol', 'li'],
  804. ['select', 'option'],
  805. ['table', 'tr'],
  806. ['table', 'th'],
  807. ];
  808. redundanters.forEach(function(couple) {
  809. rule = rule.replace(new RegExp('(^| |>)' + couple[0] + '([^ >]*)?([ >]| > )' + couple[1] + '([^\\w-]|$)', 'g'), '$1<b>' + couple[0] + '</b>$2$3<b>' + couple[1] + '</b>$4');
  810. });
  811. splittedOffender.bolded = rule;
  812. return splittedOffender;
  813. });
  814. return offendersHelpers.orderByFile(parsedOffenders);
  815. }
  816. },
  817. "totalWeight": {
  818. "tool": "redownload",
  819. "label": "Total weight",
  820. "message": "<p>The weight is of course very important if you want the page to load fast. Try to stay under 1MB, which is alreay very long to download over a slow connection.</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 page weight.</p>",
  821. "isOkThreshold": 716800,
  822. "isBadThreshold": 2097152,
  823. "isAbnormalThreshold": 3145728,
  824. "hasOffenders": true,
  825. "unit": 'bytes'
  826. },
  827. "imageOptimization": {
  828. "tool": "redownload",
  829. "label": "Image optimization",
  830. "message": "<p>This metric measures the number of bytes that could be saved by optimizing images.</p><p>Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they're not very good for optimization. Use specialized tools such as <a href=\"https://kraken.io/\" target=\"_blank\">Kraken.io</a> or the excellent <a href=\"https://imageoptim.com/\" target=\"_blank\">ImageOptim</a> on Mac. For SVG images, you can use <a href=\"https://jakearchibald.github.io/svgomg/\" target=\"_blank\">SVGOMG</a></p><p>The tools in use in YellowLabTools are not set to their maximum optimization power (JPEG quality 85), so you might be able to compress even more!</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 page weight.</p>",
  831. "isOkThreshold": 10240,
  832. "isBadThreshold": 122880,
  833. "isAbnormalThreshold": 307200,
  834. "hasOffenders": true,
  835. "unit": 'bytes'
  836. },
  837. "gzipCompression": {
  838. "tool": "redownload",
  839. "label": "Gzip compression",
  840. "message": "<p>Measures the number of bytes that could be saved by compressing file transfers.</p><p>Gzip is a powerfull weight reducer and should be enabled on text-based assets in your server's configuration. Note that gzipping small files (< 1 KB) is arguable, and that some assets such as images should not be gzipped as they are already compressed. <a href=\"https://gist.github.com/gmetais/971ce13a1fbeebd88445\" target=\"_blank\">Here</a> is a list of Content-Types that should be gzipped.</p>",
  841. "isOkThreshold": 5125,
  842. "isBadThreshold": 81920,
  843. "isAbnormalThreshold": 153600,
  844. "hasOffenders": true,
  845. "unit": 'bytes'
  846. },
  847. "fileMinification": {
  848. "tool": "redownload",
  849. "label": "File minification",
  850. "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>",
  851. "isOkThreshold": 5125,
  852. "isBadThreshold": 61440,
  853. "isAbnormalThreshold": 122880,
  854. "hasOffenders": true,
  855. "unit": 'bytes'
  856. },
  857. "totalRequests": {
  858. "tool": "redownload",
  859. "label": "Requests number",
  860. "message": "<p>This is one of the most important performance rule. Every request is slowing down the page loading.</p><p>There are several technics to reduce their number:<ul><li>Concatenate JS files</li><li>Concatenate CSS files</li><li>Embed or inline small JS or CSS files in the HTML</li><li>Create sprites</li><li>Base64 encode small images in HTML or stylesheets</li><li>Use lazyloading for images</li></ul></p>",
  861. "isOkThreshold": 15,
  862. "isBadThreshold": 100,
  863. "isAbnormalThreshold": 180,
  864. "hasOffenders": true
  865. },
  866. "domains": {
  867. "tool": "phantomas",
  868. "label": "Different domains",
  869. "message": "<p>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.</p><p>By the way, domain sharding is not a good practice anymore.</p>",
  870. "isOkThreshold": 10,
  871. "isBadThreshold": 25,
  872. "isAbnormalThreshold": 50,
  873. "hasOffenders": true,
  874. "offendersTransformFn": function(offenders) {
  875. return {
  876. count: offenders.length,
  877. list: offenders.map(function(offender) {
  878. var parts = /^([^ ]*): (\d+) request\(s\)$/.exec(offender);
  879. if (!parts) {
  880. debug('domains offenders transform function error with "%s"', offender);
  881. return {
  882. file: offender
  883. };
  884. }
  885. return {
  886. domain: parts[1],
  887. requests: parseInt(parts[2])
  888. };
  889. })
  890. };
  891. }
  892. },
  893. "notFound": {
  894. "tool": "phantomas",
  895. "label": "404 not found",
  896. "message": "<p>404 errors are never cached, so each time a page ask for it, it hits the server. Even if it is behind a CDN or a reverse-proxy cache.</p>",
  897. "isOkThreshold": 0,
  898. "isBadThreshold": 1,
  899. "isAbnormalThreshold": 1,
  900. "hasOffenders": true
  901. },
  902. "closedConnections": {
  903. "tool": "phantomas",
  904. "label": "Connections closed",
  905. "message": "<p>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.</p><p>This is slowing down the next request, because the brower needs to open a new connection to the server, which means an additional round-trip.</p><p>Correct the problem by setting a Keep-Alive header on the guilty server.</p>",
  906. "isOkThreshold": 0,
  907. "isBadThreshold": 7,
  908. "isAbnormalThreshold": 20,
  909. "hasOffenders": true
  910. },
  911. "identicalFiles": {
  912. "tool": "redownload",
  913. "label": "Identical content",
  914. "message": "<p>This is the number of requests that could be avoided, because of downloaded files that have the same content but are loaded from different URLs.</p><p>Try to load them from the same URL.</p>",
  915. "isOkThreshold": 0,
  916. "isBadThreshold": 5,
  917. "isAbnormalThreshold": 15,
  918. "hasOffenders": true,
  919. "offendersTransformFn": function(offenders) {
  920. return offenders;
  921. }
  922. },
  923. "emptyRequests": {
  924. "tool": "redownload",
  925. "label": "Empty requests",
  926. "message": "<p>List of GET requests that respond with an empty body. These are probably the easiest requests to remove.</p>",
  927. "isOkThreshold": 0,
  928. "isBadThreshold": 1,
  929. "isAbnormalThreshold": 5,
  930. "hasOffenders": true
  931. },
  932. "smallRequests": {
  933. "tool": "redownload",
  934. "label": "Small requests",
  935. "message": "<p>List of all requests that are less than 2 KB. Try to merge them with other files.</p>",
  936. "isOkThreshold": 4,
  937. "isBadThreshold": 30,
  938. "isAbnormalThreshold": 50,
  939. "hasOffenders": true
  940. },
  941. "lazyLoadableImagesBelowTheFold": {
  942. "tool": "phantomas",
  943. "label": "Below the fold images",
  944. "message": "<p>This is the number of images displayed below the fold that could be lazy-loaded. This is an excellent way to accelerate the loading time of an heavy page.</p><p>I recommend using <a href=\"https://github.com/vvo/lazyload\" target=\"_blank\">this lazyloader</a>.</p>",
  945. "isOkThreshold": 1,
  946. "isBadThreshold": 12,
  947. "isAbnormalThreshold": 30,
  948. "hasOffenders": true
  949. },
  950. "hiddenImages": {
  951. "tool": "phantomas",
  952. "label": "Hidden images",
  953. "message": "<p>List of all images that have a display:none property, or one of their parents. These images are loaded by the browser even if they're not visible. You might be able to find a way to lazy-load them, only when they get visible.</p><p>As images displayed in 1x1 pixels tend to be trackers, they are excluded from this rule.</p>",
  954. "isOkThreshold": 1,
  955. "isBadThreshold": 12,
  956. "isAbnormalThreshold": 30,
  957. "hasOffenders": true
  958. },
  959. "fontsCount": {
  960. "tool": "redownload",
  961. "label": "Webfonts number",
  962. "message": "<p>This is the number of custom web fonts loaded on the page.</p><p>Webfonts are beautiful, but heavy. You should keep their number as low as possible.</p>",
  963. "isOkThreshold": 1,
  964. "isBadThreshold": 5,
  965. "isAbnormalThreshold": 7,
  966. "hasOffenders": true,
  967. "offendersTransformFn": function(offenders) {
  968. return offenders;
  969. }
  970. },
  971. "heavyFonts": {
  972. "tool": "redownload",
  973. "label": "Overweighted webfonts",
  974. "message": "<p>This metric is the sum of all bytes above 40KB in loaded fonts. Over this size, the font is probably not optimized for the web.</p><p>It can be a compresson issue, a font that contains too many glyphs or a font with complex shapes.</p><p>Sorry, Yellow Lab Tools is not yet compatible with the WOFF2 font format that generates 20-30% smaller fonts. You can proceed to a manual verification on a modern browser.</p>",
  975. "isOkThreshold": 0,
  976. "isBadThreshold": 102400,
  977. "isAbnormalThreshold": 204800,
  978. "unit": 'bytes',
  979. "hasOffenders": true,
  980. "offendersTransformFn": function(offenders) {
  981. return offenders;
  982. }
  983. },
  984. "unusedUnicodeRanges": {
  985. "tool": "redownload",
  986. "label": "Unused Unicode ranges",
  987. "message": "<p>This metric counts the number of unused Unicode ranges inside each font. For example, one font could include Cyrillic glyphs but none of them are used on the page.</p><p>It also reveals the number of ligatures (letters that are represented differently when close to each other) and hidden chars (glyphs not linked to the unicode system that can't be displayed on the web).</p><p>Because of technical limitations, Yellow Lab Tools checks each font against the glyphs of the entire page. As a result, estimated use is >= to reality. For example, if you read that 10 glyphs are \"possibly used\", it means that these 10 glyphs are used on the page but nothing guaranties that they are displayed using this font.</p><p>Tools such as <a href=\"https://www.fontsquirrel.com/tools/webfont-generator\" target=\"_blank\">Font Squirrel</a> can remove some unicode ranges from a font.</p><p>In the case of an icon font, make sure you only keep the icons that are used on the website and to remove the others. Several tools are able to extract SVG images from a font, then some other tools can generate a font from the SVGs you want to keep.</p>",
  988. "isOkThreshold": 0,
  989. "isBadThreshold": 8,
  990. "isAbnormalThreshold": 12,
  991. "hasOffenders": true,
  992. "offendersTransformFn": function(offenders) {
  993. return offenders;
  994. }
  995. },
  996. "http2": {
  997. "label": "HTTP/2 or SPDY",
  998. "message": "<p>HTTP/2 is the latest version of the HTTP protocol and is designed to optimize load speed. SPDY is deprecated but still very well supported.</p><p>The latest versions of all major browsers are now compatible. The difficulty is on the server side, where technologies are not quite ready yet.</p>",
  999. "hasOffenders": true,
  1000. "scoreFn": function(data) {
  1001. if (!data.toolsResults.http2) {
  1002. return null;
  1003. }
  1004. var isHttp2 = data.toolsResults.http2.metrics.http2;
  1005. var result = {
  1006. value: isHttp2 ? 'Yes' : 'No',
  1007. score: isHttp2 ? 100 : 0,
  1008. bad: !isHttp2,
  1009. abnormal: false,
  1010. abnormalityScore: 0
  1011. };
  1012. if (data.toolsResults.http2.offenders) {
  1013. result.offendersObj = {
  1014. count: data.toolsResults.http2.offenders.http2.length,
  1015. list: data.toolsResults.http2.offenders.http2
  1016. };
  1017. }
  1018. return result;
  1019. }
  1020. },
  1021. "cachingDisabled": {
  1022. "tool": "phantomas",
  1023. "label": "Caching disabled",
  1024. "message": "<p>Counts responses with caching disabled (max-age=0)</p><p>Fix immediatly if on static assets.</p>",
  1025. "isOkThreshold": 0,
  1026. "isBadThreshold": 12,
  1027. "isAbnormalThreshold": 25,
  1028. "hasOffenders": true
  1029. },
  1030. "cachingNotSpecified": {
  1031. "tool": "phantomas",
  1032. "label": "Caching not specified",
  1033. "message": "<p>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.</p>",
  1034. "isOkThreshold": 5,
  1035. "isBadThreshold": 20,
  1036. "isAbnormalThreshold": 40,
  1037. "hasOffenders": true
  1038. },
  1039. "cachingTooShort": {
  1040. "tool": "phantomas",
  1041. "label": "Caching too short",
  1042. "message": "<p>Responses with too short caching time (less than a week).</p><p>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.</p>",
  1043. "isOkThreshold": 5,
  1044. "isBadThreshold": 20,
  1045. "isAbnormalThreshold": 40,
  1046. "hasOffenders": true,
  1047. "offendersTransformFn": function(offenders) {
  1048. return {
  1049. count: offenders.length,
  1050. list: offenders
  1051. .map(function(offender) {
  1052. var parts = /^([^ ]*) cached for (-?\d+(\.\d+)?) s$/.exec(offender);
  1053. if (!parts) {
  1054. debug('cachingTooShort offenders transform function error with "%s"', offender);
  1055. return {
  1056. file: offender
  1057. };
  1058. }
  1059. return {
  1060. file: parts[1],
  1061. ttl: Math.round(parseFloat(parts[2]))
  1062. };
  1063. }).sort(function(a, b) {
  1064. return a.ttl - b.ttl;
  1065. }).map(function(obj) {
  1066. var duration = obj.ttl;
  1067. var unit = 'seconds';
  1068. if (duration >= 120) {
  1069. duration = Math.round(duration / 60);
  1070. unit = 'minutes';
  1071. }
  1072. if (duration >= 120) {
  1073. duration = Math.round(duration / 60);
  1074. unit = 'hours';
  1075. }
  1076. if (duration >= 48) {
  1077. duration = Math.round(duration / 24);
  1078. unit = 'days';
  1079. }
  1080. obj.ttlWithUnit = duration;
  1081. obj.unit = unit;
  1082. return obj;
  1083. })
  1084. };
  1085. }
  1086. }
  1087. };
  1088. module.exports = policies;