index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. var List = require('../utils/list');
  2. var clone = require('../utils/clone');
  3. var usageUtils = require('./usage');
  4. var clean = require('./clean');
  5. var compress = require('./compress');
  6. var restructureBlock = require('./restructure');
  7. var walkRules = require('../utils/walk').rules;
  8. function readRulesChunk(rules, specialComments) {
  9. var buffer = new List();
  10. var nonSpaceTokenInBuffer = false;
  11. var protectedComment;
  12. rules.nextUntil(rules.head, function(node, item, list) {
  13. if (node.type === 'Comment') {
  14. if (!specialComments || node.value.charAt(0) !== '!') {
  15. list.remove(item);
  16. return;
  17. }
  18. if (nonSpaceTokenInBuffer || protectedComment) {
  19. return true;
  20. }
  21. list.remove(item);
  22. protectedComment = node;
  23. return;
  24. }
  25. if (node.type !== 'Space') {
  26. nonSpaceTokenInBuffer = true;
  27. }
  28. buffer.insert(list.remove(item));
  29. });
  30. return {
  31. comment: protectedComment,
  32. stylesheet: {
  33. type: 'StyleSheet',
  34. info: null,
  35. rules: buffer
  36. }
  37. };
  38. }
  39. function compressChunk(ast, firstAtrulesAllowed, usageData, num, logger) {
  40. logger('Compress block #' + num, null, true);
  41. var seed = 1;
  42. walkRules(ast, function markStylesheets() {
  43. if ('id' in this.stylesheet === false) {
  44. this.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed;
  45. this.stylesheet.id = seed++;
  46. }
  47. });
  48. logger('init', ast);
  49. // remove redundant
  50. clean(ast, usageData);
  51. logger('clean', ast);
  52. // compress nodes
  53. compress(ast, usageData);
  54. logger('compress', ast);
  55. return ast;
  56. }
  57. function getCommentsOption(options) {
  58. var comments = 'comments' in options ? options.comments : 'exclamation';
  59. if (typeof comments === 'boolean') {
  60. comments = comments ? 'exclamation' : false;
  61. } else if (comments !== 'exclamation' && comments !== 'first-exclamation') {
  62. comments = false;
  63. }
  64. return comments;
  65. }
  66. function getRestructureOption(options) {
  67. return 'restructure' in options ? options.restructure :
  68. 'restructuring' in options ? options.restructuring :
  69. true;
  70. }
  71. function wrapBlock(block) {
  72. return new List([{
  73. type: 'Ruleset',
  74. selector: {
  75. type: 'Selector',
  76. selectors: new List([{
  77. type: 'SimpleSelector',
  78. sequence: new List([{
  79. type: 'Identifier',
  80. name: 'x'
  81. }])
  82. }])
  83. },
  84. block: block
  85. }]);
  86. }
  87. module.exports = function compress(ast, options) {
  88. ast = ast || { type: 'StyleSheet', info: null, rules: new List() };
  89. options = options || {};
  90. var logger = typeof options.logger === 'function' ? options.logger : Function();
  91. var specialComments = getCommentsOption(options);
  92. var restructuring = getRestructureOption(options);
  93. var firstAtrulesAllowed = true;
  94. var usageData = false;
  95. var inputRules;
  96. var outputRules = new List();
  97. var chunk;
  98. var chunkNum = 1;
  99. var chunkRules;
  100. if (options.clone) {
  101. ast = clone(ast);
  102. }
  103. if (ast.type === 'StyleSheet') {
  104. inputRules = ast.rules;
  105. ast.rules = outputRules;
  106. } else {
  107. inputRules = wrapBlock(ast);
  108. }
  109. if (options.usage) {
  110. usageData = usageUtils.buildIndex(options.usage);
  111. }
  112. do {
  113. chunk = readRulesChunk(inputRules, Boolean(specialComments));
  114. compressChunk(chunk.stylesheet, firstAtrulesAllowed, usageData, chunkNum++, logger);
  115. // structure optimisations
  116. if (restructuring) {
  117. restructureBlock(chunk.stylesheet, usageData, logger);
  118. }
  119. chunkRules = chunk.stylesheet.rules;
  120. if (chunk.comment) {
  121. // add \n before comment if there is another content in outputRules
  122. if (!outputRules.isEmpty()) {
  123. outputRules.insert(List.createItem({
  124. type: 'Raw',
  125. value: '\n'
  126. }));
  127. }
  128. outputRules.insert(List.createItem(chunk.comment));
  129. // add \n after comment if chunk is not empty
  130. if (!chunkRules.isEmpty()) {
  131. outputRules.insert(List.createItem({
  132. type: 'Raw',
  133. value: '\n'
  134. }));
  135. }
  136. }
  137. if (firstAtrulesAllowed && !chunkRules.isEmpty()) {
  138. var lastRule = chunkRules.last();
  139. if (lastRule.type !== 'Atrule' ||
  140. (lastRule.name !== 'import' && lastRule.name !== 'charset')) {
  141. firstAtrulesAllowed = false;
  142. }
  143. }
  144. if (specialComments !== 'exclamation') {
  145. specialComments = false;
  146. }
  147. outputRules.appendList(chunkRules);
  148. } while (!inputRules.isEmpty());
  149. return {
  150. ast: ast
  151. };
  152. };