diff options
Diffstat (limited to 'node_modules/csso/lib/compressor')
35 files changed, 2692 insertions, 0 deletions
diff --git a/node_modules/csso/lib/compressor/clean/Atrule.js b/node_modules/csso/lib/compressor/clean/Atrule.js new file mode 100644 index 00000000..d27db04b --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/Atrule.js @@ -0,0 +1,54 @@ +module.exports = function cleanAtrule(node, item, list) { + if (node.block) { + // otherwise removed at-rule don't prevent @import for removal + this.root.firstAtrulesAllowed = false; + + if (node.block.type === 'Block' && node.block.declarations.isEmpty()) { + list.remove(item); + return; + } + + if (node.block.type === 'StyleSheet' && node.block.rules.isEmpty()) { + list.remove(item); + return; + } + } + + switch (node.name) { + case 'charset': + if (node.expression.sequence.isEmpty()) { + list.remove(item); + return; + } + + // if there is any rule before @charset -> remove it + if (item.prev) { + list.remove(item); + return; + } + + break; + + case 'import': + if (!this.root.firstAtrulesAllowed) { + list.remove(item); + return; + } + + // if there are some rules that not an @import or @charset before @import + // remove it + list.prevUntil(item.prev, function(rule) { + if (rule.type === 'Atrule') { + if (rule.name === 'import' || rule.name === 'charset') { + return; + } + } + + this.root.firstAtrulesAllowed = false; + list.remove(item); + return true; + }, this); + + break; + } +}; diff --git a/node_modules/csso/lib/compressor/clean/Comment.js b/node_modules/csso/lib/compressor/clean/Comment.js new file mode 100644 index 00000000..aa80108d --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/Comment.js @@ -0,0 +1,3 @@ +module.exports = function cleanComment(data, item, list) { + list.remove(item); +}; diff --git a/node_modules/csso/lib/compressor/clean/Declaration.js b/node_modules/csso/lib/compressor/clean/Declaration.js new file mode 100644 index 00000000..05738d16 --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/Declaration.js @@ -0,0 +1,5 @@ +module.exports = function cleanDeclartion(node, item, list) { + if (node.value.sequence.isEmpty()) { + list.remove(item); + } +}; diff --git a/node_modules/csso/lib/compressor/clean/Identifier.js b/node_modules/csso/lib/compressor/clean/Identifier.js new file mode 100644 index 00000000..20aec93e --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/Identifier.js @@ -0,0 +1,9 @@ +module.exports = function cleanIdentifier(node, item, list) { + // remove useless universal selector + if (this.selector !== null && node.name === '*') { + // remove when universal selector isn't last + if (item.next && item.next.data.type !== 'Combinator') { + list.remove(item); + } + } +}; diff --git a/node_modules/csso/lib/compressor/clean/Ruleset.js b/node_modules/csso/lib/compressor/clean/Ruleset.js new file mode 100644 index 00000000..4622b215 --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/Ruleset.js @@ -0,0 +1,39 @@ +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function cleanUnused(node, usageData) { + return node.selector.selectors.each(function(selector, item, list) { + var hasUnused = selector.sequence.some(function(node) { + switch (node.type) { + case 'Class': + return usageData.classes && !hasOwnProperty.call(usageData.classes, node.name); + + case 'Id': + return usageData.ids && !hasOwnProperty.call(usageData.ids, node.name); + + case 'Identifier': + // ignore universal selector + if (node.name !== '*') { + // TODO: remove toLowerCase when type selectors will be normalized + return usageData.tags && !hasOwnProperty.call(usageData.tags, node.name.toLowerCase()); + } + + break; + } + }); + + if (hasUnused) { + list.remove(item); + } + }); +} + +module.exports = function cleanRuleset(node, item, list, usageData) { + if (usageData) { + cleanUnused(node, usageData); + } + + if (node.selector.selectors.isEmpty() || + node.block.declarations.isEmpty()) { + list.remove(item); + } +}; diff --git a/node_modules/csso/lib/compressor/clean/Space.js b/node_modules/csso/lib/compressor/clean/Space.js new file mode 100644 index 00000000..3342c4ec --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/Space.js @@ -0,0 +1,16 @@ +function canCleanWhitespace(node) { + if (node.type !== 'Operator') { + return false; + } + + return node.value !== '+' && node.value !== '-'; +} + +module.exports = function cleanWhitespace(node, item, list) { + var prev = item.prev && item.prev.data; + var next = item.next && item.next.data; + + if (canCleanWhitespace(prev) || canCleanWhitespace(next)) { + list.remove(item); + } +}; diff --git a/node_modules/csso/lib/compressor/clean/index.js b/node_modules/csso/lib/compressor/clean/index.js new file mode 100644 index 00000000..750ee44e --- /dev/null +++ b/node_modules/csso/lib/compressor/clean/index.js @@ -0,0 +1,17 @@ +var walk = require('../../utils/walk.js').all; +var handlers = { + Space: require('./Space.js'), + Atrule: require('./Atrule.js'), + Ruleset: require('./Ruleset.js'), + Declaration: require('./Declaration.js'), + Identifier: require('./Identifier.js'), + Comment: require('./Comment.js') +}; + +module.exports = function(ast, usageData) { + walk(ast, function(node, item, list) { + if (handlers.hasOwnProperty(node.type)) { + handlers[node.type].call(this, node, item, list, usageData); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/compress/Atrule.js b/node_modules/csso/lib/compressor/compress/Atrule.js new file mode 100644 index 00000000..c5410ab9 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Atrule.js @@ -0,0 +1,9 @@ +var resolveKeyword = require('../../utils/names.js').keyword; +var compressKeyframes = require('./atrule/keyframes.js'); + +module.exports = function(node) { + // compress @keyframe selectors + if (resolveKeyword(node.name).name === 'keyframes') { + compressKeyframes(node); + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Attribute.js b/node_modules/csso/lib/compressor/compress/Attribute.js new file mode 100644 index 00000000..99cafdce --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Attribute.js @@ -0,0 +1,33 @@ +// Can unquote attribute detection +// Adopted implementation of Mathias Bynens +// https://github.com/mathiasbynens/mothereff.in/blob/master/unquoted-attributes/eff.js +var escapesRx = /\\([0-9A-Fa-f]{1,6})[ \t\n\f\r]?|\\./g; +var blockUnquoteRx = /^(-?\d|--)|[\u0000-\u002c\u002e\u002f\u003A-\u0040\u005B-\u005E\u0060\u007B-\u009f]/; + +function canUnquote(value) { + if (value === '' || value === '-') { + return; + } + + // Escapes are valid, so replace them with a valid non-empty string + value = value.replace(escapesRx, 'a'); + + return !blockUnquoteRx.test(value); +} + +module.exports = function(node) { + var attrValue = node.value; + + if (!attrValue || attrValue.type !== 'String') { + return; + } + + var unquotedValue = attrValue.value.replace(/^(.)(.*)\1$/, '$2'); + if (canUnquote(unquotedValue)) { + node.value = { + type: 'Identifier', + info: attrValue.info, + name: unquotedValue + }; + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Dimension.js b/node_modules/csso/lib/compressor/compress/Dimension.js new file mode 100644 index 00000000..e614ad08 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Dimension.js @@ -0,0 +1,54 @@ +var packNumber = require('./Number.js').pack; +var LENGTH_UNIT = { + // absolute length units + 'px': true, + 'mm': true, + 'cm': true, + 'in': true, + 'pt': true, + 'pc': true, + + // relative length units + 'em': true, + 'ex': true, + 'ch': true, + 'rem': true, + + // viewport-percentage lengths + 'vh': true, + 'vw': true, + 'vmin': true, + 'vmax': true, + 'vm': true +}; + +module.exports = function compressDimension(node, item) { + var value = packNumber(node.value); + + node.value = value; + + if (value === '0' && this.declaration) { + var unit = node.unit.toLowerCase(); + + // only length values can be compressed + if (!LENGTH_UNIT.hasOwnProperty(unit)) { + return; + } + + // issue #200: don't remove units in flex property as it could change value meaning + if (this.declaration.property.name === 'flex') { + return; + } + + // issue #222: don't remove units inside calc + if (this['function'] && this['function'].name === 'calc') { + return; + } + + item.data = { + type: 'Number', + info: node.info, + value: value + }; + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Number.js b/node_modules/csso/lib/compressor/compress/Number.js new file mode 100644 index 00000000..8e17e118 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Number.js @@ -0,0 +1,22 @@ +function packNumber(value) { + // 100 -> '100' + // 00100 -> '100' + // +100 -> '100' + // -100 -> '-100' + // 0.123 -> '.123' + // 0.12300 -> '.123' + // 0.0 -> '' + // 0 -> '' + value = String(value).replace(/^(?:\+|(-))?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/, '$1$2$3'); + + if (value.length === 0 || value === '-') { + value = '0'; + } + + return value; +}; + +module.exports = function(node) { + node.value = packNumber(node.value); +}; +module.exports.pack = packNumber; diff --git a/node_modules/csso/lib/compressor/compress/String.js b/node_modules/csso/lib/compressor/compress/String.js new file mode 100644 index 00000000..01a20a2c --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/String.js @@ -0,0 +1,12 @@ +module.exports = function(node) { + var value = node.value; + + // remove escaped \n, i.e. + // .a { content: "foo\ + // bar"} + // -> + // .a { content: "foobar" } + value = value.replace(/\\\n/g, ''); + + node.value = value; +}; diff --git a/node_modules/csso/lib/compressor/compress/Url.js b/node_modules/csso/lib/compressor/compress/Url.js new file mode 100644 index 00000000..040a3590 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Url.js @@ -0,0 +1,33 @@ +var UNICODE = '\\\\[0-9a-f]{1,6}(\\r\\n|[ \\n\\r\\t\\f])?'; +var ESCAPE = '(' + UNICODE + '|\\\\[^\\n\\r\\f0-9a-fA-F])'; +var NONPRINTABLE = '\u0000\u0008\u000b\u000e-\u001f\u007f'; +var SAFE_URL = new RegExp('^(' + ESCAPE + '|[^\"\'\\(\\)\\\\\\s' + NONPRINTABLE + '])*$', 'i'); + +module.exports = function(node) { + var value = node.value; + + if (value.type !== 'String') { + return; + } + + var quote = value.value[0]; + var url = value.value.substr(1, value.value.length - 2); + + // convert `\\` to `/` + url = url.replace(/\\\\/g, '/'); + + // remove quotes when safe + // https://www.w3.org/TR/css-syntax-3/#url-unquoted-diagram + if (SAFE_URL.test(url)) { + node.value = { + type: 'Raw', + info: node.value.info, + value: url + }; + } else { + // use double quotes if string has no double quotes + // otherwise use original quotes + // TODO: make better quote type selection + node.value.value = url.indexOf('"') === -1 ? '"' + url + '"' : quote + url + quote; + } +}; diff --git a/node_modules/csso/lib/compressor/compress/Value.js b/node_modules/csso/lib/compressor/compress/Value.js new file mode 100644 index 00000000..8196ba82 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/Value.js @@ -0,0 +1,18 @@ +var resolveName = require('../../utils/names.js').property; +var handlers = { + 'font': require('./property/font.js'), + 'font-weight': require('./property/font-weight.js'), + 'background': require('./property/background.js') +}; + +module.exports = function compressValue(node) { + if (!this.declaration) { + return; + } + + var property = resolveName(this.declaration.property.name); + + if (handlers.hasOwnProperty(property.name)) { + handlers[property.name](node); + } +}; diff --git a/node_modules/csso/lib/compressor/compress/atrule/keyframes.js b/node_modules/csso/lib/compressor/compress/atrule/keyframes.js new file mode 100644 index 00000000..74eff2c6 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/atrule/keyframes.js @@ -0,0 +1,21 @@ +module.exports = function(node) { + node.block.rules.each(function(ruleset) { + ruleset.selector.selectors.each(function(simpleselector) { + simpleselector.sequence.each(function(data, item) { + if (data.type === 'Percentage' && data.value === '100') { + item.data = { + type: 'Identifier', + info: data.info, + name: 'to' + }; + } else if (data.type === 'Identifier' && data.name === 'from') { + item.data = { + type: 'Percentage', + info: data.info, + value: '0' + }; + } + }); + }); + }); +}; diff --git a/node_modules/csso/lib/compressor/compress/color.js b/node_modules/csso/lib/compressor/compress/color.js new file mode 100644 index 00000000..cf2a3236 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/color.js @@ -0,0 +1,489 @@ +var List = require('../../utils/list.js'); +var packNumber = require('./Number.js').pack; + +// http://www.w3.org/TR/css3-color/#svg-color +var NAME_TO_HEX = { + 'aliceblue': 'f0f8ff', + 'antiquewhite': 'faebd7', + 'aqua': '0ff', + 'aquamarine': '7fffd4', + 'azure': 'f0ffff', + 'beige': 'f5f5dc', + 'bisque': 'ffe4c4', + 'black': '000', + 'blanchedalmond': 'ffebcd', + 'blue': '00f', + 'blueviolet': '8a2be2', + 'brown': 'a52a2a', + 'burlywood': 'deb887', + 'cadetblue': '5f9ea0', + 'chartreuse': '7fff00', + 'chocolate': 'd2691e', + 'coral': 'ff7f50', + 'cornflowerblue': '6495ed', + 'cornsilk': 'fff8dc', + 'crimson': 'dc143c', + 'cyan': '0ff', + 'darkblue': '00008b', + 'darkcyan': '008b8b', + 'darkgoldenrod': 'b8860b', + 'darkgray': 'a9a9a9', + 'darkgrey': 'a9a9a9', + 'darkgreen': '006400', + 'darkkhaki': 'bdb76b', + 'darkmagenta': '8b008b', + 'darkolivegreen': '556b2f', + 'darkorange': 'ff8c00', + 'darkorchid': '9932cc', + 'darkred': '8b0000', + 'darksalmon': 'e9967a', + 'darkseagreen': '8fbc8f', + 'darkslateblue': '483d8b', + 'darkslategray': '2f4f4f', + 'darkslategrey': '2f4f4f', + 'darkturquoise': '00ced1', + 'darkviolet': '9400d3', + 'deeppink': 'ff1493', + 'deepskyblue': '00bfff', + 'dimgray': '696969', + 'dimgrey': '696969', + 'dodgerblue': '1e90ff', + 'firebrick': 'b22222', + 'floralwhite': 'fffaf0', + 'forestgreen': '228b22', + 'fuchsia': 'f0f', + 'gainsboro': 'dcdcdc', + 'ghostwhite': 'f8f8ff', + 'gold': 'ffd700', + 'goldenrod': 'daa520', + 'gray': '808080', + 'grey': '808080', + 'green': '008000', + 'greenyellow': 'adff2f', + 'honeydew': 'f0fff0', + 'hotpink': 'ff69b4', + 'indianred': 'cd5c5c', + 'indigo': '4b0082', + 'ivory': 'fffff0', + 'khaki': 'f0e68c', + 'lavender': 'e6e6fa', + 'lavenderblush': 'fff0f5', + 'lawngreen': '7cfc00', + 'lemonchiffon': 'fffacd', + 'lightblue': 'add8e6', + 'lightcoral': 'f08080', + 'lightcyan': 'e0ffff', + 'lightgoldenrodyellow': 'fafad2', + 'lightgray': 'd3d3d3', + 'lightgrey': 'd3d3d3', + 'lightgreen': '90ee90', + 'lightpink': 'ffb6c1', + 'lightsalmon': 'ffa07a', + 'lightseagreen': '20b2aa', + 'lightskyblue': '87cefa', + 'lightslategray': '789', + 'lightslategrey': '789', + 'lightsteelblue': 'b0c4de', + 'lightyellow': 'ffffe0', + 'lime': '0f0', + 'limegreen': '32cd32', + 'linen': 'faf0e6', + 'magenta': 'f0f', + 'maroon': '800000', + 'mediumaquamarine': '66cdaa', + 'mediumblue': '0000cd', + 'mediumorchid': 'ba55d3', + 'mediumpurple': '9370db', + 'mediumseagreen': '3cb371', + 'mediumslateblue': '7b68ee', + 'mediumspringgreen': '00fa9a', + 'mediumturquoise': '48d1cc', + 'mediumvioletred': 'c71585', + 'midnightblue': '191970', + 'mintcream': 'f5fffa', + 'mistyrose': 'ffe4e1', + 'moccasin': 'ffe4b5', + 'navajowhite': 'ffdead', + 'navy': '000080', + 'oldlace': 'fdf5e6', + 'olive': '808000', + 'olivedrab': '6b8e23', + 'orange': 'ffa500', + 'orangered': 'ff4500', + 'orchid': 'da70d6', + 'palegoldenrod': 'eee8aa', + 'palegreen': '98fb98', + 'paleturquoise': 'afeeee', + 'palevioletred': 'db7093', + 'papayawhip': 'ffefd5', + 'peachpuff': 'ffdab9', + 'peru': 'cd853f', + 'pink': 'ffc0cb', + 'plum': 'dda0dd', + 'powderblue': 'b0e0e6', + 'purple': '800080', + 'rebeccapurple': '639', + 'red': 'f00', + 'rosybrown': 'bc8f8f', + 'royalblue': '4169e1', + 'saddlebrown': '8b4513', + 'salmon': 'fa8072', + 'sandybrown': 'f4a460', + 'seagreen': '2e8b57', + 'seashell': 'fff5ee', + 'sienna': 'a0522d', + 'silver': 'c0c0c0', + 'skyblue': '87ceeb', + 'slateblue': '6a5acd', + 'slategray': '708090', + 'slategrey': '708090', + 'snow': 'fffafa', + 'springgreen': '00ff7f', + 'steelblue': '4682b4', + 'tan': 'd2b48c', + 'teal': '008080', + 'thistle': 'd8bfd8', + 'tomato': 'ff6347', + 'turquoise': '40e0d0', + 'violet': 'ee82ee', + 'wheat': 'f5deb3', + 'white': 'fff', + 'whitesmoke': 'f5f5f5', + 'yellow': 'ff0', + 'yellowgreen': '9acd32' +}; + +var HEX_TO_NAME = { + '800000': 'maroon', + '800080': 'purple', + '808000': 'olive', + '808080': 'gray', + '00ffff': 'cyan', + 'f0ffff': 'azure', + 'f5f5dc': 'beige', + 'ffe4c4': 'bisque', + '000000': 'black', + '0000ff': 'blue', + 'a52a2a': 'brown', + 'ff7f50': 'coral', + 'ffd700': 'gold', + '008000': 'green', + '4b0082': 'indigo', + 'fffff0': 'ivory', + 'f0e68c': 'khaki', + '00ff00': 'lime', + 'faf0e6': 'linen', + '000080': 'navy', + 'ffa500': 'orange', + 'da70d6': 'orchid', + 'cd853f': 'peru', + 'ffc0cb': 'pink', + 'dda0dd': 'plum', + 'f00': 'red', + 'ff0000': 'red', + 'fa8072': 'salmon', + 'a0522d': 'sienna', + 'c0c0c0': 'silver', + 'fffafa': 'snow', + 'd2b48c': 'tan', + '008080': 'teal', + 'ff6347': 'tomato', + 'ee82ee': 'violet', + 'f5deb3': 'wheat', + 'ffffff': 'white', + 'ffff00': 'yellow' +}; + +function hueToRgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; +} + +function hslToRgb(h, s, l, a) { + var r; + var g; + var b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + + r = hueToRgb(p, q, h + 1 / 3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1 / 3); + } + + return [ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + a + ]; +} + +function toHex(value) { + value = value.toString(16); + return value.length === 1 ? '0' + value : value; +} + +function parseFunctionArgs(functionArgs, count, rgb) { + var argument = functionArgs.head; + var args = []; + + while (argument !== null) { + var argumentPart = argument.data.sequence.head; + var wasValue = false; + + while (argumentPart !== null) { + var value = argumentPart.data; + var type = value.type; + + switch (type) { + case 'Number': + case 'Percentage': + if (wasValue) { + return; + } + + wasValue = true; + args.push({ + type: type, + value: Number(value.value) + }); + break; + + case 'Operator': + if (wasValue || value.value !== '+') { + return; + } + break; + + default: + // something we couldn't understand + return; + } + + argumentPart = argumentPart.next; + } + + argument = argument.next; + } + + if (args.length !== count) { + // invalid arguments count + // TODO: remove those tokens + return; + } + + if (args.length === 4) { + if (args[3].type !== 'Number') { + // 4th argument should be a number + // TODO: remove those tokens + return; + } + + args[3].type = 'Alpha'; + } + + if (rgb) { + if (args[0].type !== args[1].type || args[0].type !== args[2].type) { + // invalid color, numbers and percentage shouldn't be mixed + // TODO: remove those tokens + return; + } + } else { + if (args[0].type !== 'Number' || + args[1].type !== 'Percentage' || + args[2].type !== 'Percentage') { + // invalid color, for hsl values should be: number, percentage, percentage + // TODO: remove those tokens + return; + } + + args[0].type = 'Angle'; + } + + return args.map(function(arg) { + var value = Math.max(0, arg.value); + + switch (arg.type) { + case 'Number': + // fit value to [0..255] range + value = Math.min(value, 255); + break; + + case 'Percentage': + // convert 0..100% to value in [0..255] range + value = Math.min(value, 100) / 100; + + if (!rgb) { + return value; + } + + value = 255 * value; + break; + + case 'Angle': + // fit value to (-360..360) range + return (((value % 360) + 360) % 360) / 360; + + case 'Alpha': + // fit value to [0..1] range + return Math.min(value, 1); + } + + return Math.round(value); + }); +} + +function compressFunction(node, item, list) { + var functionName = node.name; + var args; + + if (functionName === 'rgba' || functionName === 'hsla') { + args = parseFunctionArgs(node.arguments, 4, functionName === 'rgba'); + + if (!args) { + // something went wrong + return; + } + + if (functionName === 'hsla') { + args = hslToRgb.apply(null, args); + node.name = 'rgba'; + } + + if (args[3] !== 1) { + // replace argument values for normalized/interpolated + node.arguments.each(function(argument) { + var item = argument.sequence.head; + + if (item.data.type === 'Operator') { + item = item.next; + } + + argument.sequence = new List([{ + type: 'Number', + info: item.data.info, + value: packNumber(args.shift()) + }]); + }); + + return; + } + + // otherwise convert to rgb, i.e. rgba(255, 0, 0, 1) -> rgb(255, 0, 0) + functionName = 'rgb'; + } + + if (functionName === 'hsl') { + args = args || parseFunctionArgs(node.arguments, 3, false); + + if (!args) { + // something went wrong + return; + } + + // convert to rgb + args = hslToRgb.apply(null, args); + functionName = 'rgb'; + } + + if (functionName === 'rgb') { + args = args || parseFunctionArgs(node.arguments, 3, true); + + if (!args) { + // something went wrong + return; + } + + // check if color is not at the end and not followed by space + var next = item.next; + if (next && next.data.type !== 'Space') { + list.insert(list.createItem({ + type: 'Space' + }), next); + } + + item.data = { + type: 'Hash', + info: node.info, + value: toHex(args[0]) + toHex(args[1]) + toHex(args[2]) + }; + + compressHex(item.data, item); + } +} + +function compressIdent(node, item) { + if (this.declaration === null) { + return; + } + + var color = node.name.toLowerCase(); + + if (NAME_TO_HEX.hasOwnProperty(color)) { + var hex = NAME_TO_HEX[color]; + + if (hex.length + 1 <= color.length) { + // replace for shorter hex value + item.data = { + type: 'Hash', + info: node.info, + value: hex + }; + } else { + // special case for consistent colors + if (color === 'grey') { + color = 'gray'; + } + + // just replace value for lower cased name + node.name = color; + } + } +} + +function compressHex(node, item) { + var color = node.value.toLowerCase(); + + // #112233 -> #123 + if (color.length === 6 && + color[0] === color[1] && + color[2] === color[3] && + color[4] === color[5]) { + color = color[0] + color[2] + color[4]; + } + + if (HEX_TO_NAME[color]) { + item.data = { + type: 'Identifier', + info: node.info, + name: HEX_TO_NAME[color] + }; + } else { + node.value = color; + } +} + +module.exports = { + compressFunction: compressFunction, + compressIdent: compressIdent, + compressHex: compressHex +}; diff --git a/node_modules/csso/lib/compressor/compress/index.js b/node_modules/csso/lib/compressor/compress/index.js new file mode 100644 index 00000000..ff6b0296 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/index.js @@ -0,0 +1,22 @@ +var walk = require('../../utils/walk.js').all; +var handlers = { + Atrule: require('./Atrule.js'), + Attribute: require('./Attribute.js'), + Value: require('./Value.js'), + Dimension: require('./Dimension.js'), + Percentage: require('./Number.js'), + Number: require('./Number.js'), + String: require('./String.js'), + Url: require('./Url.js'), + Hash: require('./color.js').compressHex, + Identifier: require('./color.js').compressIdent, + Function: require('./color.js').compressFunction +}; + +module.exports = function(ast) { + walk(ast, function(node, item, list) { + if (handlers.hasOwnProperty(node.type)) { + handlers[node.type].call(this, node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/compress/property/background.js b/node_modules/csso/lib/compressor/compress/property/background.js new file mode 100644 index 00000000..210127a9 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/property/background.js @@ -0,0 +1,66 @@ +var List = require('../../../utils/list.js'); + +module.exports = function compressBackground(node) { + function lastType() { + if (buffer.length) { + return buffer[buffer.length - 1].type; + } + } + + function flush() { + if (lastType() === 'Space') { + buffer.pop(); + } + + if (!buffer.length) { + buffer.unshift( + { + type: 'Number', + value: '0' + }, + { + type: 'Space' + }, + { + type: 'Number', + value: '0' + } + ); + } + + newValue.push.apply(newValue, buffer); + + buffer = []; + } + + var newValue = []; + var buffer = []; + + node.sequence.each(function(node) { + if (node.type === 'Operator' && node.value === ',') { + flush(); + newValue.push(node); + return; + } + + // remove defaults + if (node.type === 'Identifier') { + if (node.name === 'transparent' || + node.name === 'none' || + node.name === 'repeat' || + node.name === 'scroll') { + return; + } + } + + // don't add redundant spaces + if (node.type === 'Space' && (!buffer.length || lastType() === 'Space')) { + return; + } + + buffer.push(node); + }); + + flush(); + node.sequence = new List(newValue); +}; diff --git a/node_modules/csso/lib/compressor/compress/property/font-weight.js b/node_modules/csso/lib/compressor/compress/property/font-weight.js new file mode 100644 index 00000000..29122eb6 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/property/font-weight.js @@ -0,0 +1,22 @@ +module.exports = function compressFontWeight(node) { + var value = node.sequence.head.data; + + if (value.type === 'Identifier') { + switch (value.name) { + case 'normal': + node.sequence.head.data = { + type: 'Number', + info: value.info, + value: '400' + }; + break; + case 'bold': + node.sequence.head.data = { + type: 'Number', + info: value.info, + value: '700' + }; + break; + } + } +}; diff --git a/node_modules/csso/lib/compressor/compress/property/font.js b/node_modules/csso/lib/compressor/compress/property/font.js new file mode 100644 index 00000000..783685f4 --- /dev/null +++ b/node_modules/csso/lib/compressor/compress/property/font.js @@ -0,0 +1,45 @@ +module.exports = function compressFont(node) { + var list = node.sequence; + + list.eachRight(function(node, item) { + if (node.type === 'Identifier') { + if (node.name === 'bold') { + item.data = { + type: 'Number', + info: node.info, + value: '700' + }; + } else if (node.name === 'normal') { + var prev = item.prev; + + if (prev && prev.data.type === 'Operator' && prev.data.value === '/') { + this.remove(prev); + } + + this.remove(item); + } else if (node.name === 'medium') { + var next = item.next; + + if (!next || next.data.type !== 'Operator') { + this.remove(item); + } + } + } + }); + + // remove redundant spaces + list.each(function(node, item) { + if (node.type === 'Space') { + if (!item.prev || !item.next || item.next.data.type === 'Space') { + this.remove(item); + } + } + }); + + if (list.isEmpty()) { + list.insert(list.createItem({ + type: 'Identifier', + name: 'normal' + })); + } +}; diff --git a/node_modules/csso/lib/compressor/index.js b/node_modules/csso/lib/compressor/index.js new file mode 100644 index 00000000..6d3fdeba --- /dev/null +++ b/node_modules/csso/lib/compressor/index.js @@ -0,0 +1,186 @@ +var List = require('../utils/list'); +var clone = require('../utils/clone'); +var usageUtils = require('./usage'); +var clean = require('./clean'); +var compress = require('./compress'); +var restructureBlock = require('./restructure'); +var walkRules = require('../utils/walk').rules; + +function readRulesChunk(rules, specialComments) { + var buffer = new List(); + var nonSpaceTokenInBuffer = false; + var protectedComment; + + rules.nextUntil(rules.head, function(node, item, list) { + if (node.type === 'Comment') { + if (!specialComments || node.value.charAt(0) !== '!') { + list.remove(item); + return; + } + + if (nonSpaceTokenInBuffer || protectedComment) { + return true; + } + + list.remove(item); + protectedComment = node; + return; + } + + if (node.type !== 'Space') { + nonSpaceTokenInBuffer = true; + } + + buffer.insert(list.remove(item)); + }); + + return { + comment: protectedComment, + stylesheet: { + type: 'StyleSheet', + info: null, + rules: buffer + } + }; +} + +function compressChunk(ast, firstAtrulesAllowed, usageData, num, logger) { + logger('Compress block #' + num, null, true); + + var seed = 1; + walkRules(ast, function markStylesheets() { + if ('id' in this.stylesheet === false) { + this.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed; + this.stylesheet.id = seed++; + } + }); + logger('init', ast); + + // remove redundant + clean(ast, usageData); + logger('clean', ast); + + // compress nodes + compress(ast, usageData); + logger('compress', ast); + + return ast; +} + +function getCommentsOption(options) { + var comments = 'comments' in options ? options.comments : 'exclamation'; + + if (typeof comments === 'boolean') { + comments = comments ? 'exclamation' : false; + } else if (comments !== 'exclamation' && comments !== 'first-exclamation') { + comments = false; + } + + return comments; +} + +function getRestructureOption(options) { + return 'restructure' in options ? options.restructure : + 'restructuring' in options ? options.restructuring : + true; +} + +function wrapBlock(block) { + return new List([{ + type: 'Ruleset', + selector: { + type: 'Selector', + selectors: new List([{ + type: 'SimpleSelector', + sequence: new List([{ + type: 'Identifier', + name: 'x' + }]) + }]) + }, + block: block + }]); +} + +module.exports = function compress(ast, options) { + ast = ast || { type: 'StyleSheet', info: null, rules: new List() }; + options = options || {}; + + var logger = typeof options.logger === 'function' ? options.logger : Function(); + var specialComments = getCommentsOption(options); + var restructuring = getRestructureOption(options); + var firstAtrulesAllowed = true; + var usageData = false; + var inputRules; + var outputRules = new List(); + var chunk; + var chunkNum = 1; + var chunkRules; + + if (options.clone) { + ast = clone(ast); + } + + if (ast.type === 'StyleSheet') { + inputRules = ast.rules; + ast.rules = outputRules; + } else { + inputRules = wrapBlock(ast); + } + + if (options.usage) { + usageData = usageUtils.buildIndex(options.usage); + } + + do { + chunk = readRulesChunk(inputRules, Boolean(specialComments)); + + compressChunk(chunk.stylesheet, firstAtrulesAllowed, usageData, chunkNum++, logger); + + // structure optimisations + if (restructuring) { + restructureBlock(chunk.stylesheet, usageData, logger); + } + + chunkRules = chunk.stylesheet.rules; + + if (chunk.comment) { + // add \n before comment if there is another content in outputRules + if (!outputRules.isEmpty()) { + outputRules.insert(List.createItem({ + type: 'Raw', + value: '\n' + })); + } + + outputRules.insert(List.createItem(chunk.comment)); + + // add \n after comment if chunk is not empty + if (!chunkRules.isEmpty()) { + outputRules.insert(List.createItem({ + type: 'Raw', + value: '\n' + })); + } + } + + if (firstAtrulesAllowed && !chunkRules.isEmpty()) { + var lastRule = chunkRules.last(); + + if (lastRule.type !== 'Atrule' || + (lastRule.name !== 'import' && lastRule.name !== 'charset')) { + firstAtrulesAllowed = false; + } + } + + if (specialComments !== 'exclamation') { + specialComments = false; + } + + outputRules.appendList(chunkRules); + } while (!inputRules.isEmpty()); + + return { + ast: ast + }; +}; diff --git a/node_modules/csso/lib/compressor/restructure/1-initialMergeRuleset.js b/node_modules/csso/lib/compressor/restructure/1-initialMergeRuleset.js new file mode 100644 index 00000000..036a04b4 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/1-initialMergeRuleset.js @@ -0,0 +1,48 @@ +var utils = require('./utils.js'); +var walkRules = require('../../utils/walk.js').rules; + +function processRuleset(node, item, list) { + var selectors = node.selector.selectors; + var declarations = node.block.declarations; + + list.prevUntil(item.prev, function(prev) { + // skip non-ruleset node if safe + if (prev.type !== 'Ruleset') { + return utils.unsafeToSkipNode.call(selectors, prev); + } + + var prevSelectors = prev.selector.selectors; + var prevDeclarations = prev.block.declarations; + + // try to join rulesets with equal pseudo signature + if (node.pseudoSignature === prev.pseudoSignature) { + // try to join by selectors + if (utils.isEqualLists(prevSelectors, selectors)) { + prevDeclarations.appendList(declarations); + list.remove(item); + return true; + } + + // try to join by declarations + if (utils.isEqualDeclarations(declarations, prevDeclarations)) { + utils.addSelectors(prevSelectors, selectors); + list.remove(item); + return true; + } + } + + // go to prev ruleset if has no selector similarities + return utils.hasSimilarSelectors(selectors, prevSelectors); + }); +}; + +// NOTE: direction should be left to right, since rulesets merge to left +// ruleset. When direction right to left unmerged rulesets may prevent lookup +// TODO: remove initial merge +module.exports = function initialMergeRuleset(ast) { + walkRules(ast, function(node, item, list) { + if (node.type === 'Ruleset') { + processRuleset(node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/restructure/2-mergeAtrule.js b/node_modules/csso/lib/compressor/restructure/2-mergeAtrule.js new file mode 100644 index 00000000..d07318f7 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/2-mergeAtrule.js @@ -0,0 +1,35 @@ +var walkRulesRight = require('../../utils/walk.js').rulesRight; + +function isMediaRule(node) { + return node.type === 'Atrule' && node.name === 'media'; +} + +function processAtrule(node, item, list) { + if (!isMediaRule(node)) { + return; + } + + var prev = item.prev && item.prev.data; + + if (!prev || !isMediaRule(prev)) { + return; + } + + // merge @media with same query + if (node.expression.id === prev.expression.id) { + prev.block.rules.appendList(node.block.rules); + prev.info = { + primary: prev.info, + merged: node.info + }; + list.remove(item); + } +}; + +module.exports = function rejoinAtrule(ast) { + walkRulesRight(ast, function(node, item, list) { + if (node.type === 'Atrule') { + processAtrule(node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/restructure/3-disjoinRuleset.js b/node_modules/csso/lib/compressor/restructure/3-disjoinRuleset.js new file mode 100644 index 00000000..6df4f807 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/3-disjoinRuleset.js @@ -0,0 +1,42 @@ +var List = require('../../utils/list.js'); +var walkRulesRight = require('../../utils/walk.js').rulesRight; + +function processRuleset(node, item, list) { + var selectors = node.selector.selectors; + + // generate new rule sets: + // .a, .b { color: red; } + // -> + // .a { color: red; } + // .b { color: red; } + + // while there are more than 1 simple selector split for rulesets + while (selectors.head !== selectors.tail) { + var newSelectors = new List(); + newSelectors.insert(selectors.remove(selectors.head)); + + list.insert(list.createItem({ + type: 'Ruleset', + info: node.info, + pseudoSignature: node.pseudoSignature, + selector: { + type: 'Selector', + info: node.selector.info, + selectors: newSelectors + }, + block: { + type: 'Block', + info: node.block.info, + declarations: node.block.declarations.copy() + } + }), item); + } +}; + +module.exports = function disjoinRuleset(ast) { + walkRulesRight(ast, function(node, item, list) { + if (node.type === 'Ruleset') { + processRuleset(node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/restructure/4-restructShorthand.js b/node_modules/csso/lib/compressor/restructure/4-restructShorthand.js new file mode 100644 index 00000000..aa95e3cc --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/4-restructShorthand.js @@ -0,0 +1,430 @@ +var List = require('../../utils/list.js'); +var translate = require('../../utils/translate.js'); +var walkRulesRight = require('../../utils/walk.js').rulesRight; + +var REPLACE = 1; +var REMOVE = 2; +var TOP = 0; +var RIGHT = 1; +var BOTTOM = 2; +var LEFT = 3; +var SIDES = ['top', 'right', 'bottom', 'left']; +var SIDE = { + 'margin-top': 'top', + 'margin-right': 'right', + 'margin-bottom': 'bottom', + 'margin-left': 'left', + + 'padding-top': 'top', + 'padding-right': 'right', + 'padding-bottom': 'bottom', + 'padding-left': 'left', + + 'border-top-color': 'top', + 'border-right-color': 'right', + 'border-bottom-color': 'bottom', + 'border-left-color': 'left', + 'border-top-width': 'top', + 'border-right-width': 'right', + 'border-bottom-width': 'bottom', + 'border-left-width': 'left', + 'border-top-style': 'top', + 'border-right-style': 'right', + 'border-bottom-style': 'bottom', + 'border-left-style': 'left' +}; +var MAIN_PROPERTY = { + 'margin': 'margin', + 'margin-top': 'margin', + 'margin-right': 'margin', + 'margin-bottom': 'margin', + 'margin-left': 'margin', + + 'padding': 'padding', + 'padding-top': 'padding', + 'padding-right': 'padding', + 'padding-bottom': 'padding', + 'padding-left': 'padding', + + 'border-color': 'border-color', + 'border-top-color': 'border-color', + 'border-right-color': 'border-color', + 'border-bottom-color': 'border-color', + 'border-left-color': 'border-color', + 'border-width': 'border-width', + 'border-top-width': 'border-width', + 'border-right-width': 'border-width', + 'border-bottom-width': 'border-width', + 'border-left-width': 'border-width', + 'border-style': 'border-style', + 'border-top-style': 'border-style', + 'border-right-style': 'border-style', + 'border-bottom-style': 'border-style', + 'border-left-style': 'border-style' +}; + +function TRBL(name) { + this.name = name; + this.info = null; + this.iehack = undefined; + this.sides = { + 'top': null, + 'right': null, + 'bottom': null, + 'left': null + }; +} + +TRBL.prototype.getValueSequence = function(value, count) { + var values = []; + var iehack = ''; + var hasBadValues = value.sequence.some(function(child) { + var special = false; + + switch (child.type) { + case 'Identifier': + switch (child.name) { + case '\\0': + case '\\9': + iehack = child.name; + return; + + case 'inherit': + case 'initial': + case 'unset': + case 'revert': + special = child.name; + break; + } + break; + + case 'Dimension': + switch (child.unit) { + // is not supported until IE11 + case 'rem': + + // v* units is too buggy across browsers and better + // don't merge values with those units + case 'vw': + case 'vh': + case 'vmin': + case 'vmax': + case 'vm': // IE9 supporting "vm" instead of "vmin". + special = child.unit; + break; + } + break; + + case 'Hash': // color + case 'Number': + case 'Percentage': + break; + + case 'Function': + special = child.name; + break; + + case 'Space': + return false; // ignore space + + default: + return true; // bad value + } + + values.push({ + node: child, + special: special, + important: value.important + }); + }); + + if (hasBadValues || values.length > count) { + return false; + } + + if (typeof this.iehack === 'string' && this.iehack !== iehack) { + return false; + } + + this.iehack = iehack; // move outside + + return values; +}; + +TRBL.prototype.canOverride = function(side, value) { + var currentValue = this.sides[side]; + + return !currentValue || (value.important && !currentValue.important); +}; + +TRBL.prototype.add = function(name, value, info) { + function attemptToAdd() { + var sides = this.sides; + var side = SIDE[name]; + + if (side) { + if (side in sides === false) { + return false; + } + + var values = this.getValueSequence(value, 1); + + if (!values || !values.length) { + return false; + } + + // can mix only if specials are equal + for (var key in sides) { + if (sides[key] !== null && sides[key].special !== values[0].special) { + return false; + } + } + + if (!this.canOverride(side, values[0])) { + return true; + } + + sides[side] = values[0]; + return true; + } else if (name === this.name) { + var values = this.getValueSequence(value, 4); + + if (!values || !values.length) { + return false; + } + + switch (values.length) { + case 1: + values[RIGHT] = values[TOP]; + values[BOTTOM] = values[TOP]; + values[LEFT] = values[TOP]; + break; + + case 2: + values[BOTTOM] = values[TOP]; + values[LEFT] = values[RIGHT]; + break; + + case 3: + values[LEFT] = values[RIGHT]; + break; + } + + // can mix only if specials are equal + for (var i = 0; i < 4; i++) { + for (var key in sides) { + if (sides[key] !== null && sides[key].special !== values[i].special) { + return false; + } + } + } + + for (var i = 0; i < 4; i++) { + if (this.canOverride(SIDES[i], values[i])) { + sides[SIDES[i]] = values[i]; + } + } + + return true; + } + } + + if (!attemptToAdd.call(this)) { + return false; + } + + if (this.info) { + this.info = { + primary: this.info, + merged: info + }; + } else { + this.info = info; + } + + return true; +}; + +TRBL.prototype.isOkToMinimize = function() { + var top = this.sides.top; + var right = this.sides.right; + var bottom = this.sides.bottom; + var left = this.sides.left; + + if (top && right && bottom && left) { + var important = + top.important + + right.important + + bottom.important + + left.important; + + return important === 0 || important === 4; + } + + return false; +}; + +TRBL.prototype.getValue = function() { + var result = []; + var sides = this.sides; + var values = [ + sides.top, + sides.right, + sides.bottom, + sides.left + ]; + var stringValues = [ + translate(sides.top.node), + translate(sides.right.node), + translate(sides.bottom.node), + translate(sides.left.node) + ]; + + if (stringValues[LEFT] === stringValues[RIGHT]) { + values.pop(); + if (stringValues[BOTTOM] === stringValues[TOP]) { + values.pop(); + if (stringValues[RIGHT] === stringValues[TOP]) { + values.pop(); + } + } + } + + for (var i = 0; i < values.length; i++) { + if (i) { + result.push({ type: 'Space' }); + } + + result.push(values[i].node); + } + + if (this.iehack) { + result.push({ type: 'Space' }, { + type: 'Identifier', + info: {}, + name: this.iehack + }); + } + + return { + type: 'Value', + info: {}, + important: sides.top.important, + sequence: new List(result) + }; +}; + +TRBL.prototype.getProperty = function() { + return { + type: 'Property', + info: {}, + name: this.name + }; +}; + +function processRuleset(ruleset, shorts, shortDeclarations, lastShortSelector) { + var declarations = ruleset.block.declarations; + var selector = ruleset.selector.selectors.first().id; + + ruleset.block.declarations.eachRight(function(declaration, item) { + var property = declaration.property.name; + + if (!MAIN_PROPERTY.hasOwnProperty(property)) { + return; + } + + var key = MAIN_PROPERTY[property]; + var shorthand; + var operation; + + if (!lastShortSelector || selector === lastShortSelector) { + if (key in shorts) { + operation = REMOVE; + shorthand = shorts[key]; + } + } + + if (!shorthand || !shorthand.add(property, declaration.value, declaration.info)) { + operation = REPLACE; + shorthand = new TRBL(key); + + // if can't parse value ignore it and break shorthand sequence + if (!shorthand.add(property, declaration.value, declaration.info)) { + lastShortSelector = null; + return; + } + } + + shorts[key] = shorthand; + shortDeclarations.push({ + operation: operation, + block: declarations, + item: item, + shorthand: shorthand + }); + + lastShortSelector = selector; + }); + + return lastShortSelector; +}; + +function processShorthands(shortDeclarations, markDeclaration) { + shortDeclarations.forEach(function(item) { + var shorthand = item.shorthand; + + if (!shorthand.isOkToMinimize()) { + return; + } + + if (item.operation === REPLACE) { + item.item.data = markDeclaration({ + type: 'Declaration', + info: shorthand.info, + property: shorthand.getProperty(), + value: shorthand.getValue(), + id: 0, + length: 0, + fingerprint: null + }); + } else { + item.block.remove(item.item); + } + }); +}; + +module.exports = function restructBlock(ast, indexer) { + var stylesheetMap = {}; + var shortDeclarations = []; + + walkRulesRight(ast, function(node) { + if (node.type !== 'Ruleset') { + return; + } + + var stylesheet = this.stylesheet; + var rulesetId = (node.pseudoSignature || '') + '|' + node.selector.selectors.first().id; + var rulesetMap; + var shorts; + + if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { + rulesetMap = { + lastShortSelector: null + }; + stylesheetMap[stylesheet.id] = rulesetMap; + } else { + rulesetMap = stylesheetMap[stylesheet.id]; + } + + if (rulesetMap.hasOwnProperty(rulesetId)) { + shorts = rulesetMap[rulesetId]; + } else { + shorts = {}; + rulesetMap[rulesetId] = shorts; + } + + rulesetMap.lastShortSelector = processRuleset.call(this, node, shorts, shortDeclarations, rulesetMap.lastShortSelector); + }); + + processShorthands(shortDeclarations, indexer.declaration); +}; diff --git a/node_modules/csso/lib/compressor/restructure/6-restructBlock.js b/node_modules/csso/lib/compressor/restructure/6-restructBlock.js new file mode 100644 index 00000000..4933eed4 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/6-restructBlock.js @@ -0,0 +1,261 @@ +var resolveProperty = require('../../utils/names.js').property; +var resolveKeyword = require('../../utils/names.js').keyword; +var walkRulesRight = require('../../utils/walk.js').rulesRight; +var translate = require('../../utils/translate.js'); +var dontRestructure = { + 'src': 1 // https://github.com/afelix/csso/issues/50 +}; + +var DONT_MIX_VALUE = { + // https://developer.mozilla.org/en-US/docs/Web/CSS/display#Browser_compatibility + 'display': /table|ruby|flex|-(flex)?box$|grid|contents|run-in/i, + // https://developer.mozilla.org/en/docs/Web/CSS/text-align + 'text-align': /^(start|end|match-parent|justify-all)$/i +}; + +var CURSOR_SAFE_VALUE = [ + 'auto', 'crosshair', 'default', 'move', 'text', 'wait', 'help', + 'n-resize', 'e-resize', 's-resize', 'w-resize', + 'ne-resize', 'nw-resize', 'se-resize', 'sw-resize', + 'pointer', 'progress', 'not-allowed', 'no-drop', 'vertical-text', 'all-scroll', + 'col-resize', 'row-resize' +]; + +var NEEDLESS_TABLE = { + 'border-width': ['border'], + 'border-style': ['border'], + 'border-color': ['border'], + 'border-top': ['border'], + 'border-right': ['border'], + 'border-bottom': ['border'], + 'border-left': ['border'], + 'border-top-width': ['border-top', 'border-width', 'border'], + 'border-right-width': ['border-right', 'border-width', 'border'], + 'border-bottom-width': ['border-bottom', 'border-width', 'border'], + 'border-left-width': ['border-left', 'border-width', 'border'], + 'border-top-style': ['border-top', 'border-style', 'border'], + 'border-right-style': ['border-right', 'border-style', 'border'], + 'border-bottom-style': ['border-bottom', 'border-style', 'border'], + 'border-left-style': ['border-left', 'border-style', 'border'], + 'border-top-color': ['border-top', 'border-color', 'border'], + 'border-right-color': ['border-right', 'border-color', 'border'], + 'border-bottom-color': ['border-bottom', 'border-color', 'border'], + 'border-left-color': ['border-left', 'border-color', 'border'], + 'margin-top': ['margin'], + 'margin-right': ['margin'], + 'margin-bottom': ['margin'], + 'margin-left': ['margin'], + 'padding-top': ['padding'], + 'padding-right': ['padding'], + 'padding-bottom': ['padding'], + 'padding-left': ['padding'], + 'font-style': ['font'], + 'font-variant': ['font'], + 'font-weight': ['font'], + 'font-size': ['font'], + 'font-family': ['font'], + 'list-style-type': ['list-style'], + 'list-style-position': ['list-style'], + 'list-style-image': ['list-style'] +}; + +function getPropertyFingerprint(propertyName, declaration, fingerprints) { + var realName = resolveProperty(propertyName).name; + + if (realName === 'background' || + (realName === 'filter' && declaration.value.sequence.first().type === 'Progid')) { + return propertyName + ':' + translate(declaration.value); + } + + var declarationId = declaration.id; + var fingerprint = fingerprints[declarationId]; + + if (!fingerprint) { + var vendorId = ''; + var iehack = ''; + var special = {}; + + declaration.value.sequence.each(function walk(node) { + switch (node.type) { + case 'Argument': + case 'Value': + case 'Braces': + node.sequence.each(walk); + break; + + case 'Identifier': + var name = node.name; + + if (!vendorId) { + vendorId = resolveKeyword(name).vendor; + } + + if (/\\[09]/.test(name)) { + iehack = RegExp.lastMatch; + } + + if (realName === 'cursor') { + if (CURSOR_SAFE_VALUE.indexOf(name) === -1) { + special[name] = true; + } + } else if (DONT_MIX_VALUE.hasOwnProperty(realName)) { + if (DONT_MIX_VALUE[realName].test(name)) { + special[name] = true; + } + } + + break; + + case 'Function': + var name = node.name; + + if (!vendorId) { + vendorId = resolveKeyword(name).vendor; + } + + if (name === 'rect') { + // there are 2 forms of rect: + // rect(<top>, <right>, <bottom>, <left>) - standart + // rect(<top> <right> <bottom> <left>) – backwards compatible syntax + // only the same form values can be merged + if (node.arguments.size < 4) { + name = 'rect-backward'; + } + } + + special[name + '()'] = true; + + // check nested tokens too + node.arguments.each(walk); + + break; + + case 'Dimension': + var unit = node.unit; + + switch (unit) { + // is not supported until IE11 + case 'rem': + + // v* units is too buggy across browsers and better + // don't merge values with those units + case 'vw': + case 'vh': + case 'vmin': + case 'vmax': + case 'vm': // IE9 supporting "vm" instead of "vmin". + special[unit] = true; + break; + } + break; + } + }); + + fingerprint = '|' + Object.keys(special).sort() + '|' + iehack + vendorId; + + fingerprints[declarationId] = fingerprint; + } + + return propertyName + fingerprint; +} + +function needless(props, declaration, fingerprints) { + var property = resolveProperty(declaration.property.name); + + if (NEEDLESS_TABLE.hasOwnProperty(property.name)) { + var table = NEEDLESS_TABLE[property.name]; + + for (var i = 0; i < table.length; i++) { + var ppre = getPropertyFingerprint(property.prefix + table[i], declaration, fingerprints); + var prev = props[ppre]; + + if (prev && (!declaration.value.important || prev.item.data.value.important)) { + return prev; + } + } + } +} + +function processRuleset(ruleset, item, list, props, fingerprints) { + var declarations = ruleset.block.declarations; + + declarations.eachRight(function(declaration, declarationItem) { + var property = declaration.property.name; + var fingerprint = getPropertyFingerprint(property, declaration, fingerprints); + var prev = props[fingerprint]; + + if (prev && !dontRestructure.hasOwnProperty(property)) { + if (declaration.value.important && !prev.item.data.value.important) { + props[fingerprint] = { + block: declarations, + item: declarationItem + }; + + prev.block.remove(prev.item); + declaration.info = { + primary: declaration.info, + merged: prev.item.data.info + }; + } else { + declarations.remove(declarationItem); + prev.item.data.info = { + primary: prev.item.data.info, + merged: declaration.info + }; + } + } else { + var prev = needless(props, declaration, fingerprints); + + if (prev) { + declarations.remove(declarationItem); + prev.item.data.info = { + primary: prev.item.data.info, + merged: declaration.info + }; + } else { + declaration.fingerprint = fingerprint; + + props[fingerprint] = { + block: declarations, + item: declarationItem + }; + } + } + }); + + if (declarations.isEmpty()) { + list.remove(item); + } +}; + +module.exports = function restructBlock(ast) { + var stylesheetMap = {}; + var fingerprints = Object.create(null); + + walkRulesRight(ast, function(node, item, list) { + if (node.type !== 'Ruleset') { + return; + } + + var stylesheet = this.stylesheet; + var rulesetId = (node.pseudoSignature || '') + '|' + node.selector.selectors.first().id; + var rulesetMap; + var props; + + if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { + rulesetMap = {}; + stylesheetMap[stylesheet.id] = rulesetMap; + } else { + rulesetMap = stylesheetMap[stylesheet.id]; + } + + if (rulesetMap.hasOwnProperty(rulesetId)) { + props = rulesetMap[rulesetId]; + } else { + props = {}; + rulesetMap[rulesetId] = props; + } + + processRuleset.call(this, node, item, list, props, fingerprints); + }); +}; diff --git a/node_modules/csso/lib/compressor/restructure/7-mergeRuleset.js b/node_modules/csso/lib/compressor/restructure/7-mergeRuleset.js new file mode 100644 index 00000000..0ae7edb6 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/7-mergeRuleset.js @@ -0,0 +1,87 @@ +var utils = require('./utils.js'); +var walkRules = require('../../utils/walk.js').rules; + +/* + At this step all rules has single simple selector. We try to join by equal + declaration blocks to first rule, e.g. + + .a { color: red } + b { ... } + .b { color: red } + -> + .a, .b { color: red } + b { ... } +*/ + +function processRuleset(node, item, list) { + var selectors = node.selector.selectors; + var declarations = node.block.declarations; + var nodeCompareMarker = selectors.first().compareMarker; + var skippedCompareMarkers = {}; + + list.nextUntil(item.next, function(next, nextItem) { + // skip non-ruleset node if safe + if (next.type !== 'Ruleset') { + return utils.unsafeToSkipNode.call(selectors, next); + } + + if (node.pseudoSignature !== next.pseudoSignature) { + return true; + } + + var nextFirstSelector = next.selector.selectors.head; + var nextDeclarations = next.block.declarations; + var nextCompareMarker = nextFirstSelector.data.compareMarker; + + // if next ruleset has same marked as one of skipped then stop joining + if (nextCompareMarker in skippedCompareMarkers) { + return true; + } + + // try to join by selectors + if (selectors.head === selectors.tail) { + if (selectors.first().id === nextFirstSelector.data.id) { + declarations.appendList(nextDeclarations); + list.remove(nextItem); + return; + } + } + + // try to join by properties + if (utils.isEqualDeclarations(declarations, nextDeclarations)) { + var nextStr = nextFirstSelector.data.id; + + selectors.some(function(data, item) { + var curStr = data.id; + + if (nextStr < curStr) { + selectors.insert(nextFirstSelector, item); + return true; + } + + if (!item.next) { + selectors.insert(nextFirstSelector); + return true; + } + }); + + list.remove(nextItem); + return; + } + + // go to next ruleset if current one can be skipped (has no equal specificity nor element selector) + if (nextCompareMarker === nodeCompareMarker) { + return true; + } + + skippedCompareMarkers[nextCompareMarker] = true; + }); +}; + +module.exports = function mergeRuleset(ast) { + walkRules(ast, function(node, item, list) { + if (node.type === 'Ruleset') { + processRuleset(node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/restructure/8-restructRuleset.js b/node_modules/csso/lib/compressor/restructure/8-restructRuleset.js new file mode 100644 index 00000000..9a9e545f --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/8-restructRuleset.js @@ -0,0 +1,157 @@ +var List = require('../../utils/list.js'); +var utils = require('./utils.js'); +var walkRulesRight = require('../../utils/walk.js').rulesRight; + +function calcSelectorLength(list) { + var length = 0; + + list.each(function(data) { + length += data.id.length + 1; + }); + + return length - 1; +} + +function calcDeclarationsLength(tokens) { + var length = 0; + + for (var i = 0; i < tokens.length; i++) { + length += tokens[i].length; + } + + return ( + length + // declarations + tokens.length - 1 // delimeters + ); +} + +function processRuleset(node, item, list) { + var avoidRulesMerge = this.stylesheet.avoidRulesMerge; + var selectors = node.selector.selectors; + var block = node.block; + var disallowDownMarkers = Object.create(null); + var allowMergeUp = true; + var allowMergeDown = true; + + list.prevUntil(item.prev, function(prev, prevItem) { + // skip non-ruleset node if safe + if (prev.type !== 'Ruleset') { + return utils.unsafeToSkipNode.call(selectors, prev); + } + + var prevSelectors = prev.selector.selectors; + var prevBlock = prev.block; + + if (node.pseudoSignature !== prev.pseudoSignature) { + return true; + } + + allowMergeDown = !prevSelectors.some(function(selector) { + return selector.compareMarker in disallowDownMarkers; + }); + + // try prev ruleset if simpleselectors has no equal specifity and element selector + if (!allowMergeDown && !allowMergeUp) { + return true; + } + + // try to join by selectors + if (allowMergeUp && utils.isEqualLists(prevSelectors, selectors)) { + prevBlock.declarations.appendList(block.declarations); + list.remove(item); + return true; + } + + // try to join by properties + var diff = utils.compareDeclarations(block.declarations, prevBlock.declarations); + + // console.log(diff.eq, diff.ne1, diff.ne2); + + if (diff.eq.length) { + if (!diff.ne1.length && !diff.ne2.length) { + // equal blocks + if (allowMergeDown) { + utils.addSelectors(selectors, prevSelectors); + list.remove(prevItem); + } + + return true; + } else if (!avoidRulesMerge) { /* probably we don't need to prevent those merges for @keyframes + TODO: need to be checked */ + + if (diff.ne1.length && !diff.ne2.length) { + // prevBlock is subset block + var selectorLength = calcSelectorLength(selectors); + var blockLength = calcDeclarationsLength(diff.eq); // declarations length + + if (allowMergeUp && selectorLength < blockLength) { + utils.addSelectors(prevSelectors, selectors); + block.declarations = new List(diff.ne1); + } + } else if (!diff.ne1.length && diff.ne2.length) { + // node is subset of prevBlock + var selectorLength = calcSelectorLength(prevSelectors); + var blockLength = calcDeclarationsLength(diff.eq); // declarations length + + if (allowMergeDown && selectorLength < blockLength) { + utils.addSelectors(selectors, prevSelectors); + prevBlock.declarations = new List(diff.ne2); + } + } else { + // diff.ne1.length && diff.ne2.length + // extract equal block + var newSelector = { + type: 'Selector', + info: {}, + selectors: utils.addSelectors(prevSelectors.copy(), selectors) + }; + var newBlockLength = calcSelectorLength(newSelector.selectors) + 2; // selectors length + curly braces length + var blockLength = calcDeclarationsLength(diff.eq); // declarations length + + // create new ruleset if declarations length greater than + // ruleset description overhead + if (allowMergeDown && blockLength >= newBlockLength) { + var newRuleset = { + type: 'Ruleset', + info: {}, + pseudoSignature: node.pseudoSignature, + selector: newSelector, + block: { + type: 'Block', + info: {}, + declarations: new List(diff.eq) + } + }; + + block.declarations = new List(diff.ne1); + prevBlock.declarations = new List(diff.ne2.concat(diff.ne2overrided)); + list.insert(list.createItem(newRuleset), prevItem); + return true; + } + } + } + } + + if (allowMergeUp) { + // TODO: disallow up merge only if any property interception only (i.e. diff.ne2overrided.length > 0); + // await property families to find property interception correctly + allowMergeUp = !prevSelectors.some(function(prevSelector) { + return selectors.some(function(selector) { + return selector.compareMarker === prevSelector.compareMarker; + }); + }); + } + + prevSelectors.each(function(data) { + disallowDownMarkers[data.compareMarker] = true; + }); + }); +}; + +module.exports = function restructRuleset(ast) { + walkRulesRight(ast, function(node, item, list) { + if (node.type === 'Ruleset') { + processRuleset.call(this, node, item, list); + } + }); +}; diff --git a/node_modules/csso/lib/compressor/restructure/index.js b/node_modules/csso/lib/compressor/restructure/index.js new file mode 100644 index 00000000..6a05974e --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/index.js @@ -0,0 +1,35 @@ +var prepare = require('./prepare/index.js'); +var initialMergeRuleset = require('./1-initialMergeRuleset.js'); +var mergeAtrule = require('./2-mergeAtrule.js'); +var disjoinRuleset = require('./3-disjoinRuleset.js'); +var restructShorthand = require('./4-restructShorthand.js'); +var restructBlock = require('./6-restructBlock.js'); +var mergeRuleset = require('./7-mergeRuleset.js'); +var restructRuleset = require('./8-restructRuleset.js'); + +module.exports = function(ast, usageData, debug) { + // prepare ast for restructing + var indexer = prepare(ast, usageData); + debug('prepare', ast); + + initialMergeRuleset(ast); + debug('initialMergeRuleset', ast); + + mergeAtrule(ast); + debug('mergeAtrule', ast); + + disjoinRuleset(ast); + debug('disjoinRuleset', ast); + + restructShorthand(ast, indexer); + debug('restructShorthand', ast); + + restructBlock(ast); + debug('restructBlock', ast); + + mergeRuleset(ast); + debug('mergeRuleset', ast); + + restructRuleset(ast); + debug('restructRuleset', ast); +}; diff --git a/node_modules/csso/lib/compressor/restructure/prepare/createDeclarationIndexer.js b/node_modules/csso/lib/compressor/restructure/prepare/createDeclarationIndexer.js new file mode 100644 index 00000000..c5235309 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/prepare/createDeclarationIndexer.js @@ -0,0 +1,32 @@ +var translate = require('../../../utils/translate.js'); + +function Index() { + this.seed = 0; + this.map = Object.create(null); +} + +Index.prototype.resolve = function(str) { + var index = this.map[str]; + + if (!index) { + index = ++this.seed; + this.map[str] = index; + } + + return index; +}; + +module.exports = function createDeclarationIndexer() { + var names = new Index(); + var values = new Index(); + + return function markDeclaration(node) { + var property = node.property.name; + var value = translate(node.value); + + node.id = names.resolve(property) + (values.resolve(value) << 12); + node.length = property.length + 1 + value.length; + + return node; + }; +}; diff --git a/node_modules/csso/lib/compressor/restructure/prepare/index.js b/node_modules/csso/lib/compressor/restructure/prepare/index.js new file mode 100644 index 00000000..075dc5f1 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/prepare/index.js @@ -0,0 +1,44 @@ +var resolveKeyword = require('../../../utils/names.js').keyword; +var walkRules = require('../../../utils/walk.js').rules; +var translate = require('../../../utils/translate.js'); +var createDeclarationIndexer = require('./createDeclarationIndexer.js'); +var processSelector = require('./processSelector.js'); + +function walk(node, markDeclaration, usageData) { + switch (node.type) { + case 'Ruleset': + node.block.declarations.each(markDeclaration); + processSelector(node, usageData); + break; + + case 'Atrule': + if (node.expression) { + node.expression.id = translate(node.expression); + } + + // compare keyframe selectors by its values + // NOTE: still no clarification about problems with keyframes selector grouping (issue #197) + if (resolveKeyword(node.name).name === 'keyframes') { + node.block.avoidRulesMerge = true; /* probably we don't need to prevent those merges for @keyframes + TODO: need to be checked */ + node.block.rules.each(function(ruleset) { + ruleset.selector.selectors.each(function(simpleselector) { + simpleselector.compareMarker = simpleselector.id; + }); + }); + } + break; + } +}; + +module.exports = function prepare(ast, usageData) { + var markDeclaration = createDeclarationIndexer(); + + walkRules(ast, function(node) { + walk(node, markDeclaration, usageData); + }); + + return { + declaration: markDeclaration + }; +}; diff --git a/node_modules/csso/lib/compressor/restructure/prepare/processSelector.js b/node_modules/csso/lib/compressor/restructure/prepare/processSelector.js new file mode 100644 index 00000000..56c46b56 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/prepare/processSelector.js @@ -0,0 +1,99 @@ +var translate = require('../../../utils/translate.js'); +var specificity = require('./specificity.js'); + +var nonFreezePseudoElements = { + 'first-letter': true, + 'first-line': true, + 'after': true, + 'before': true +}; +var nonFreezePseudoClasses = { + 'link': true, + 'visited': true, + 'hover': true, + 'active': true, + 'first-letter': true, + 'first-line': true, + 'after': true, + 'before': true +}; + +module.exports = function freeze(node, usageData) { + var pseudos = Object.create(null); + var hasPseudo = false; + + node.selector.selectors.each(function(simpleSelector) { + var tagName = '*'; + var scope = 0; + + simpleSelector.sequence.some(function(node) { + switch (node.type) { + case 'Class': + if (usageData && usageData.scopes) { + var classScope = usageData.scopes[node.name] || 0; + + if (scope !== 0 && classScope !== scope) { + throw new Error('Selector can\'t has classes from different scopes: ' + translate(simpleSelector)); + } + + scope = classScope; + } + break; + + case 'PseudoClass': + if (!nonFreezePseudoClasses.hasOwnProperty(node.name)) { + pseudos[node.name] = true; + hasPseudo = true; + } + break; + + case 'PseudoElement': + if (!nonFreezePseudoElements.hasOwnProperty(node.name)) { + pseudos[node.name] = true; + hasPseudo = true; + } + break; + + case 'FunctionalPseudo': + pseudos[node.name] = true; + hasPseudo = true; + break; + + case 'Negation': + pseudos.not = true; + hasPseudo = true; + break; + + case 'Identifier': + tagName = node.name; + break; + + case 'Attribute': + if (node.flags) { + pseudos['[' + node.flags + ']'] = true; + hasPseudo = true; + } + break; + + case 'Combinator': + tagName = '*'; + break; + } + }); + + simpleSelector.id = translate(simpleSelector); + simpleSelector.compareMarker = specificity(simpleSelector).toString(); + + if (scope) { + simpleSelector.compareMarker += ':' + scope; + } + + if (tagName !== '*') { + simpleSelector.compareMarker += ',' + tagName; + } + }); + + if (hasPseudo) { + node.pseudoSignature = Object.keys(pseudos).sort().join(','); + } +}; diff --git a/node_modules/csso/lib/compressor/restructure/prepare/specificity.js b/node_modules/csso/lib/compressor/restructure/prepare/specificity.js new file mode 100644 index 00000000..506c3373 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/prepare/specificity.js @@ -0,0 +1,48 @@ +module.exports = function specificity(simpleSelector) { + var A = 0; + var B = 0; + var C = 0; + + simpleSelector.sequence.each(function walk(data) { + switch (data.type) { + case 'SimpleSelector': + case 'Negation': + data.sequence.each(walk); + break; + + case 'Id': + A++; + break; + + case 'Class': + case 'Attribute': + case 'FunctionalPseudo': + B++; + break; + + case 'Identifier': + if (data.name !== '*') { + C++; + } + break; + + case 'PseudoElement': + C++; + break; + + case 'PseudoClass': + var name = data.name.toLowerCase(); + if (name === 'before' || + name === 'after' || + name === 'first-line' || + name === 'first-letter') { + C++; + } else { + B++; + } + break; + } + }); + + return [A, B, C]; +}; diff --git a/node_modules/csso/lib/compressor/restructure/utils.js b/node_modules/csso/lib/compressor/restructure/utils.js new file mode 100644 index 00000000..70c92c51 --- /dev/null +++ b/node_modules/csso/lib/compressor/restructure/utils.js @@ -0,0 +1,141 @@ +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function isEqualLists(a, b) { + var cursor1 = a.head; + var cursor2 = b.head; + + while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { + cursor1 = cursor1.next; + cursor2 = cursor2.next; + } + + return cursor1 === null && cursor2 === null; +} + +function isEqualDeclarations(a, b) { + var cursor1 = a.head; + var cursor2 = b.head; + + while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { + cursor1 = cursor1.next; + cursor2 = cursor2.next; + } + + return cursor1 === null && cursor2 === null; +} + +function compareDeclarations(declarations1, declarations2) { + var result = { + eq: [], + ne1: [], + ne2: [], + ne2overrided: [] + }; + + var fingerprints = Object.create(null); + var declarations2hash = Object.create(null); + + for (var cursor = declarations2.head; cursor; cursor = cursor.next) { + declarations2hash[cursor.data.id] = true; + } + + for (var cursor = declarations1.head; cursor; cursor = cursor.next) { + var data = cursor.data; + + if (data.fingerprint) { + fingerprints[data.fingerprint] = data.value.important; + } + + if (declarations2hash[data.id]) { + declarations2hash[data.id] = false; + result.eq.push(data); + } else { + result.ne1.push(data); + } + } + + for (var cursor = declarations2.head; cursor; cursor = cursor.next) { + var data = cursor.data; + + if (declarations2hash[data.id]) { + // if declarations1 has overriding declaration, this is not a difference + // but take in account !important - prev should be equal or greater than follow + if (hasOwnProperty.call(fingerprints, data.fingerprint) && + Number(fingerprints[data.fingerprint]) >= Number(data.value.important)) { + result.ne2overrided.push(data); + } else { + result.ne2.push(data); + } + } + } + + return result; +} + +function addSelectors(dest, source) { + source.each(function(sourceData) { + var newStr = sourceData.id; + var cursor = dest.head; + + while (cursor) { + var nextStr = cursor.data.id; + + if (nextStr === newStr) { + return; + } + + if (nextStr > newStr) { + break; + } + + cursor = cursor.next; + } + + dest.insert(dest.createItem(sourceData), cursor); + }); + + return dest; +} + +// check if simpleselectors has no equal specificity and element selector +function hasSimilarSelectors(selectors1, selectors2) { + return selectors1.some(function(a) { + return selectors2.some(function(b) { + return a.compareMarker === b.compareMarker; + }); + }); +} + +// test node can't to be skipped +function unsafeToSkipNode(node) { + switch (node.type) { + case 'Ruleset': + // unsafe skip ruleset with selector similarities + return hasSimilarSelectors(node.selector.selectors, this); + + case 'Atrule': + // can skip at-rules with blocks + if (node.block) { + // non-stylesheet blocks are safe to skip since have no selectors + if (node.block.type !== 'StyleSheet') { + return false; + } + + // unsafe skip at-rule if block contains something unsafe to skip + return node.block.rules.some(unsafeToSkipNode, this); + } + break; + } + + // unsafe by default + return true; +} + +module.exports = { + isEqualLists: isEqualLists, + isEqualDeclarations: isEqualDeclarations, + compareDeclarations: compareDeclarations, + addSelectors: addSelectors, + hasSimilarSelectors: hasSimilarSelectors, + unsafeToSkipNode: unsafeToSkipNode +}; diff --git a/node_modules/csso/lib/compressor/usage.js b/node_modules/csso/lib/compressor/usage.js new file mode 100644 index 00000000..72d8b15b --- /dev/null +++ b/node_modules/csso/lib/compressor/usage.js @@ -0,0 +1,58 @@ +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function buildMap(list, caseInsensitive) { + var map = Object.create(null); + + if (!Array.isArray(list)) { + return false; + } + + for (var i = 0; i < list.length; i++) { + var name = list[i]; + + if (caseInsensitive) { + name = name.toLowerCase(); + } + + map[name] = true; + } + + return map; +} + +function buildIndex(data) { + var scopes = false; + + if (data.scopes && Array.isArray(data.scopes)) { + scopes = Object.create(null); + + for (var i = 0; i < data.scopes.length; i++) { + var list = data.scopes[i]; + + if (!list || !Array.isArray(list)) { + throw new Error('Wrong usage format'); + } + + for (var j = 0; j < list.length; j++) { + var name = list[j]; + + if (hasOwnProperty.call(scopes, name)) { + throw new Error('Class can\'t be used for several scopes: ' + name); + } + + scopes[name] = i + 1; + } + } + } + + return { + tags: buildMap(data.tags, true), + ids: buildMap(data.ids), + classes: buildMap(data.classes), + scopes: scopes + }; +} + +module.exports = { + buildIndex: buildIndex +}; |
