6-restructBlock.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. var resolveProperty = require('../../utils/names.js').property;
  2. var resolveKeyword = require('../../utils/names.js').keyword;
  3. var walkRulesRight = require('../../utils/walk.js').rulesRight;
  4. var translate = require('../../utils/translate.js');
  5. var dontRestructure = {
  6. 'src': 1 // https://github.com/afelix/csso/issues/50
  7. };
  8. var DONT_MIX_VALUE = {
  9. // https://developer.mozilla.org/en-US/docs/Web/CSS/display#Browser_compatibility
  10. 'display': /table|ruby|flex|-(flex)?box$|grid|contents|run-in/i,
  11. // https://developer.mozilla.org/en/docs/Web/CSS/text-align
  12. 'text-align': /^(start|end|match-parent|justify-all)$/i
  13. };
  14. var CURSOR_SAFE_VALUE = [
  15. 'auto', 'crosshair', 'default', 'move', 'text', 'wait', 'help',
  16. 'n-resize', 'e-resize', 's-resize', 'w-resize',
  17. 'ne-resize', 'nw-resize', 'se-resize', 'sw-resize',
  18. 'pointer', 'progress', 'not-allowed', 'no-drop', 'vertical-text', 'all-scroll',
  19. 'col-resize', 'row-resize'
  20. ];
  21. var NEEDLESS_TABLE = {
  22. 'border-width': ['border'],
  23. 'border-style': ['border'],
  24. 'border-color': ['border'],
  25. 'border-top': ['border'],
  26. 'border-right': ['border'],
  27. 'border-bottom': ['border'],
  28. 'border-left': ['border'],
  29. 'border-top-width': ['border-top', 'border-width', 'border'],
  30. 'border-right-width': ['border-right', 'border-width', 'border'],
  31. 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
  32. 'border-left-width': ['border-left', 'border-width', 'border'],
  33. 'border-top-style': ['border-top', 'border-style', 'border'],
  34. 'border-right-style': ['border-right', 'border-style', 'border'],
  35. 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
  36. 'border-left-style': ['border-left', 'border-style', 'border'],
  37. 'border-top-color': ['border-top', 'border-color', 'border'],
  38. 'border-right-color': ['border-right', 'border-color', 'border'],
  39. 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
  40. 'border-left-color': ['border-left', 'border-color', 'border'],
  41. 'margin-top': ['margin'],
  42. 'margin-right': ['margin'],
  43. 'margin-bottom': ['margin'],
  44. 'margin-left': ['margin'],
  45. 'padding-top': ['padding'],
  46. 'padding-right': ['padding'],
  47. 'padding-bottom': ['padding'],
  48. 'padding-left': ['padding'],
  49. 'font-style': ['font'],
  50. 'font-variant': ['font'],
  51. 'font-weight': ['font'],
  52. 'font-size': ['font'],
  53. 'font-family': ['font'],
  54. 'list-style-type': ['list-style'],
  55. 'list-style-position': ['list-style'],
  56. 'list-style-image': ['list-style']
  57. };
  58. function getPropertyFingerprint(propertyName, declaration, fingerprints) {
  59. var realName = resolveProperty(propertyName).name;
  60. if (realName === 'background' ||
  61. (realName === 'filter' && declaration.value.sequence.first().type === 'Progid')) {
  62. return propertyName + ':' + translate(declaration.value);
  63. }
  64. var declarationId = declaration.id;
  65. var fingerprint = fingerprints[declarationId];
  66. if (!fingerprint) {
  67. var vendorId = '';
  68. var iehack = '';
  69. var special = {};
  70. declaration.value.sequence.each(function walk(node) {
  71. switch (node.type) {
  72. case 'Argument':
  73. case 'Value':
  74. case 'Braces':
  75. node.sequence.each(walk);
  76. break;
  77. case 'Identifier':
  78. var name = node.name;
  79. if (!vendorId) {
  80. vendorId = resolveKeyword(name).vendor;
  81. }
  82. if (/\\[09]/.test(name)) {
  83. iehack = RegExp.lastMatch;
  84. }
  85. if (realName === 'cursor') {
  86. if (CURSOR_SAFE_VALUE.indexOf(name) === -1) {
  87. special[name] = true;
  88. }
  89. } else if (DONT_MIX_VALUE.hasOwnProperty(realName)) {
  90. if (DONT_MIX_VALUE[realName].test(name)) {
  91. special[name] = true;
  92. }
  93. }
  94. break;
  95. case 'Function':
  96. var name = node.name;
  97. if (!vendorId) {
  98. vendorId = resolveKeyword(name).vendor;
  99. }
  100. if (name === 'rect') {
  101. // there are 2 forms of rect:
  102. // rect(<top>, <right>, <bottom>, <left>) - standart
  103. // rect(<top> <right> <bottom> <left>) – backwards compatible syntax
  104. // only the same form values can be merged
  105. if (node.arguments.size < 4) {
  106. name = 'rect-backward';
  107. }
  108. }
  109. special[name + '()'] = true;
  110. // check nested tokens too
  111. node.arguments.each(walk);
  112. break;
  113. case 'Dimension':
  114. var unit = node.unit;
  115. switch (unit) {
  116. // is not supported until IE11
  117. case 'rem':
  118. // v* units is too buggy across browsers and better
  119. // don't merge values with those units
  120. case 'vw':
  121. case 'vh':
  122. case 'vmin':
  123. case 'vmax':
  124. case 'vm': // IE9 supporting "vm" instead of "vmin".
  125. special[unit] = true;
  126. break;
  127. }
  128. break;
  129. }
  130. });
  131. fingerprint = '|' + Object.keys(special).sort() + '|' + iehack + vendorId;
  132. fingerprints[declarationId] = fingerprint;
  133. }
  134. return propertyName + fingerprint;
  135. }
  136. function needless(props, declaration, fingerprints) {
  137. var property = resolveProperty(declaration.property.name);
  138. if (NEEDLESS_TABLE.hasOwnProperty(property.name)) {
  139. var table = NEEDLESS_TABLE[property.name];
  140. for (var i = 0; i < table.length; i++) {
  141. var ppre = getPropertyFingerprint(property.prefix + table[i], declaration, fingerprints);
  142. var prev = props[ppre];
  143. if (prev && (!declaration.value.important || prev.item.data.value.important)) {
  144. return prev;
  145. }
  146. }
  147. }
  148. }
  149. function processRuleset(ruleset, item, list, props, fingerprints) {
  150. var declarations = ruleset.block.declarations;
  151. declarations.eachRight(function(declaration, declarationItem) {
  152. var property = declaration.property.name;
  153. var fingerprint = getPropertyFingerprint(property, declaration, fingerprints);
  154. var prev = props[fingerprint];
  155. if (prev && !dontRestructure.hasOwnProperty(property)) {
  156. if (declaration.value.important && !prev.item.data.value.important) {
  157. props[fingerprint] = {
  158. block: declarations,
  159. item: declarationItem
  160. };
  161. prev.block.remove(prev.item);
  162. declaration.info = {
  163. primary: declaration.info,
  164. merged: prev.item.data.info
  165. };
  166. } else {
  167. declarations.remove(declarationItem);
  168. prev.item.data.info = {
  169. primary: prev.item.data.info,
  170. merged: declaration.info
  171. };
  172. }
  173. } else {
  174. var prev = needless(props, declaration, fingerprints);
  175. if (prev) {
  176. declarations.remove(declarationItem);
  177. prev.item.data.info = {
  178. primary: prev.item.data.info,
  179. merged: declaration.info
  180. };
  181. } else {
  182. declaration.fingerprint = fingerprint;
  183. props[fingerprint] = {
  184. block: declarations,
  185. item: declarationItem
  186. };
  187. }
  188. }
  189. });
  190. if (declarations.isEmpty()) {
  191. list.remove(item);
  192. }
  193. };
  194. module.exports = function restructBlock(ast) {
  195. var stylesheetMap = {};
  196. var fingerprints = Object.create(null);
  197. walkRulesRight(ast, function(node, item, list) {
  198. if (node.type !== 'Ruleset') {
  199. return;
  200. }
  201. var stylesheet = this.stylesheet;
  202. var rulesetId = (node.pseudoSignature || '') + '|' + node.selector.selectors.first().id;
  203. var rulesetMap;
  204. var props;
  205. if (!stylesheetMap.hasOwnProperty(stylesheet.id)) {
  206. rulesetMap = {};
  207. stylesheetMap[stylesheet.id] = rulesetMap;
  208. } else {
  209. rulesetMap = stylesheetMap[stylesheet.id];
  210. }
  211. if (rulesetMap.hasOwnProperty(rulesetId)) {
  212. props = rulesetMap[rulesetId];
  213. } else {
  214. props = {};
  215. rulesetMap[rulesetId] = props;
  216. }
  217. processRuleset.call(this, node, item, list, props, fingerprints);
  218. });
  219. };