No Description

tokenizer.js 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. module.exports = function Tokenizer(data, minifyContext) {
  2. var chunker = new Chunker(data, 128);
  3. var chunk = chunker.next();
  4. var flatBlock = /(^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport)|\\@.+?)/;
  5. var whatsNext = function(context) {
  6. var cursor = context.cursor;
  7. var mode = context.mode;
  8. var closest;
  9. if (chunk.length == context.cursor) {
  10. if (chunker.isEmpty())
  11. return null;
  12. chunk = chunker.next();
  13. context.cursor = 0;
  14. }
  15. if (mode == 'body') {
  16. closest = chunk.indexOf('}', cursor);
  17. return closest > -1 ?
  18. [closest, 'bodyEnd'] :
  19. null;
  20. }
  21. var nextSpecial = chunk.indexOf('@', context.cursor);
  22. var nextEscape = mode == 'top' ? chunk.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', context.cursor) : -1;
  23. var nextBodyStart = chunk.indexOf('{', context.cursor);
  24. var nextBodyEnd = chunk.indexOf('}', context.cursor);
  25. closest = nextSpecial;
  26. if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
  27. closest = nextEscape;
  28. if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
  29. closest = nextBodyStart;
  30. if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
  31. closest = nextBodyEnd;
  32. if (closest == -1)
  33. return;
  34. if (nextEscape === closest)
  35. return [closest, 'escape'];
  36. if (nextBodyStart === closest)
  37. return [closest, 'bodyStart'];
  38. if (nextBodyEnd === closest)
  39. return [closest, 'bodyEnd'];
  40. if (nextSpecial === closest)
  41. return [closest, 'special'];
  42. };
  43. var tokenize = function(context) {
  44. var tokenized = [];
  45. context = context || { cursor: 0, mode: 'top' };
  46. while (true) {
  47. var next = whatsNext(context);
  48. if (!next) {
  49. var whatsLeft = chunk.substring(context.cursor);
  50. if (whatsLeft.length > 0) {
  51. tokenized.push(whatsLeft);
  52. context.cursor += whatsLeft.length;
  53. }
  54. break;
  55. }
  56. var nextSpecial = next[0];
  57. var what = next[1];
  58. var nextEnd;
  59. var oldMode;
  60. if (what == 'special') {
  61. var firstOpenBraceAt = chunk.indexOf('{', nextSpecial);
  62. var firstSemicolonAt = chunk.indexOf(';', nextSpecial);
  63. var isSingle = firstSemicolonAt > -1 && (firstOpenBraceAt == -1 || firstSemicolonAt < firstOpenBraceAt);
  64. var isBroken = firstOpenBraceAt == -1 && firstSemicolonAt == -1;
  65. if (isBroken) {
  66. minifyContext.warnings.push('Broken declaration: \'' + chunk.substring(context.cursor) + '\'.');
  67. context.cursor = chunk.length;
  68. } else if (isSingle) {
  69. nextEnd = chunk.indexOf(';', nextSpecial + 1);
  70. tokenized.push(chunk.substring(context.cursor, nextEnd + 1));
  71. context.cursor = nextEnd + 1;
  72. } else {
  73. nextEnd = chunk.indexOf('{', nextSpecial + 1);
  74. var block = chunk.substring(context.cursor, nextEnd).trim();
  75. var isFlat = flatBlock.test(block);
  76. oldMode = context.mode;
  77. context.cursor = nextEnd + 1;
  78. context.mode = isFlat ? 'body' : 'block';
  79. var specialBody = tokenize(context);
  80. context.mode = oldMode;
  81. tokenized.push({ block: block, body: specialBody });
  82. }
  83. } else if (what == 'escape') {
  84. nextEnd = chunk.indexOf('__', nextSpecial + 1);
  85. var escaped = chunk.substring(context.cursor, nextEnd + 2);
  86. tokenized.push(escaped);
  87. context.cursor = nextEnd + 2;
  88. } else if (what == 'bodyStart') {
  89. var selector = chunk.substring(context.cursor, nextSpecial).trim();
  90. oldMode = context.mode;
  91. context.cursor = nextSpecial + 1;
  92. context.mode = 'body';
  93. var body = tokenize(context);
  94. context.mode = oldMode;
  95. tokenized.push({ selector: selector, body: body });
  96. } else if (what == 'bodyEnd') {
  97. // extra closing brace at the top level can be safely ignored
  98. if (context.mode == 'top') {
  99. var at = context.cursor;
  100. var warning = chunk[context.cursor] == '}' ?
  101. 'Unexpected \'}\' in \'' + chunk.substring(at - 20, at + 20) + '\'. Ignoring.' :
  102. 'Unexpected content: \'' + chunk.substring(at, nextSpecial + 1) + '\'. Ignoring.';
  103. minifyContext.warnings.push(warning);
  104. context.cursor = nextSpecial + 1;
  105. continue;
  106. }
  107. if (context.mode != 'block')
  108. tokenized = chunk.substring(context.cursor, nextSpecial);
  109. context.cursor = nextSpecial + 1;
  110. break;
  111. }
  112. }
  113. return tokenized;
  114. };
  115. return {
  116. process: function() {
  117. return tokenize();
  118. }
  119. };
  120. };
  121. // Divides `data` into chunks of `chunkSize` for faster processing
  122. var Chunker = function(data, chunkSize) {
  123. var chunks = [];
  124. for (var cursor = 0, dataSize = data.length; cursor < dataSize;) {
  125. var nextCursor = cursor + chunkSize > dataSize ?
  126. dataSize - 1 :
  127. cursor + chunkSize;
  128. if (data[nextCursor] != '}')
  129. nextCursor = data.indexOf('}', nextCursor);
  130. if (nextCursor == -1)
  131. nextCursor = data.length - 1;
  132. chunks.push(data.substring(cursor, nextCursor + 1));
  133. cursor = nextCursor + 1;
  134. }
  135. return {
  136. isEmpty: function() {
  137. return chunks.length === 0;
  138. },
  139. next: function() {
  140. return chunks.shift() || '';
  141. }
  142. };
  143. };