mediaQueriesChecker.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. var debug = require('debug')('ylt:mediaQueriesChecker');
  2. var parseMediaQuery = require('css-mq-parser');
  3. var offendersHelpers = require('../offendersHelpers');
  4. var mediaQueriesChecker = function() {
  5. 'use strict';
  6. var MOBILE_MIN_BREAKPOINT = 200;
  7. var MOBILE_MAX_BREAKPOINT = 300;
  8. this.analyzeMediaQueries = function(data) {
  9. debug('Starting to check all media queries...');
  10. var offenders = data.toolsResults.phantomas.offenders.cssMediaQueries;
  11. var mediaQueries = (offenders) ? this.parseAllMediaQueries(offenders) : [];
  12. var notMobileFirstCount = 0;
  13. var notMobileFirstOffenders = [];
  14. var breakpointsOffenders = {};
  15. for (var i = 0; i < mediaQueries.length; i++) {
  16. var item = mediaQueries[i];
  17. if (!item) {
  18. continue;
  19. }
  20. if (item.isForMobile) {
  21. notMobileFirstCount += item.mediaQuery.rules;
  22. notMobileFirstOffenders.push(item.mediaQuery);
  23. }
  24. for (var j = 0; j < item.breakpoints.length; j++) {
  25. var breakpointString = item.breakpoints[j].string;
  26. if (!breakpointsOffenders[breakpointString]) {
  27. breakpointsOffenders[breakpointString] = {
  28. count: 1,
  29. pixels: item.breakpoints[j].pixels
  30. };
  31. } else {
  32. breakpointsOffenders[breakpointString].count += 1;
  33. }
  34. }
  35. }
  36. data.toolsResults.mediaQueriesChecker = {
  37. metrics: {
  38. cssMobileFirst: notMobileFirstCount,
  39. cssBreakpoints: Object.keys(breakpointsOffenders).length
  40. },
  41. offenders: {
  42. cssMobileFirst: notMobileFirstOffenders,
  43. cssBreakpoints: breakpointsOffenders
  44. }
  45. };
  46. debug('End of media queries check');
  47. return data;
  48. };
  49. this.parseAllMediaQueries = function(offenders) {
  50. return offenders.map(this.parseOneMediaQuery);
  51. };
  52. this.parseOneMediaQuery = function(offender) {
  53. var splittedOffender = offendersHelpers.cssOffenderPattern(offender);
  54. var parts = /^@media (.*) \((\d+ rules)\)$/.exec(splittedOffender.css);
  55. if (!parts) {
  56. debug('Failed to parse media query ' + offender);
  57. return false;
  58. }
  59. var rulesCount = parseInt(parts[2], 10);
  60. var query = parts[1];
  61. var isForMobile = false;
  62. var breakpoints = [];
  63. try {
  64. var ast = parseMediaQuery(query);
  65. var min = 0;
  66. var max = Infinity;
  67. var pixels;
  68. ast.forEach(function(astItem) {
  69. astItem.expressions.forEach(function(expression) {
  70. if (expression.feature === 'width' || expression.feature === 'device-width') {
  71. if (astItem.inverse === false) {
  72. if (expression.modifier === 'max') {
  73. pixels = toPixels(expression.value);
  74. max = Math.min(max, pixels);
  75. breakpoints.push({
  76. string: expression.value,
  77. pixels: pixels
  78. });
  79. } else if (expression.modifier === 'min') {
  80. pixels = toPixels(expression.value);
  81. min = Math.max(min, pixels);
  82. breakpoints.push({
  83. string: expression.value,
  84. pixels: pixels
  85. });
  86. }
  87. } else if (astItem.inverse === true) {
  88. if (expression.modifier === 'max') {
  89. pixels = toPixels(expression.value);
  90. min = Math.max(min, pixels);
  91. breakpoints.push({
  92. string: expression.value,
  93. pixels: pixels
  94. });
  95. } else if (expression.modifier === 'min') {
  96. pixels = toPixels(expression.value);
  97. max = Math.min(max, pixels);
  98. breakpoints.push({
  99. string: expression.value,
  100. pixels: pixels
  101. });
  102. }
  103. }
  104. }
  105. });
  106. });
  107. isForMobile = (min <= MOBILE_MIN_BREAKPOINT && max >= MOBILE_MAX_BREAKPOINT && max !== Infinity);
  108. } catch(error) {
  109. debug('Failed to parse media query ' + offender);
  110. }
  111. return {
  112. mediaQuery: {
  113. query: query,
  114. rules: rulesCount,
  115. file: splittedOffender.file,
  116. line: splittedOffender.line,
  117. column: splittedOffender.column
  118. },
  119. isForMobile: isForMobile,
  120. breakpoints: breakpoints
  121. };
  122. };
  123. // Parses a size in em, pt (or px) and returns it in px
  124. function toPixels(size) {
  125. var splittedSize = /^([\d\.]+)(.*)/.exec(size);
  126. var value = parseFloat(splittedSize[1]);
  127. var unit = splittedSize[2];
  128. if (unit === 'em') {
  129. return value * 16;
  130. }
  131. if (unit === 'pt') {
  132. return value / 12 * 16;
  133. }
  134. return value;
  135. }
  136. };
  137. module.exports = new mediaQueriesChecker();