Skip to content

Instantly share code, notes, and snippets.

@GoalSmashers
Created February 2, 2014 10:10
Show Gist options
  • Select an option

  • Save GoalSmashers/8765857 to your computer and use it in GitHub Desktop.

Select an option

Save GoalSmashers/8765857 to your computer and use it in GitHub Desktop.
clean-css patch 1
commit 29fbb23145254bab63a83e261a02fcf5dc09b5fd
Author: Jakub Pawlowicz <contact@jakubpawlowicz.com>
Date: Sun Jan 19 16:51:07 2014 +0000
Cleaned up line breaks.
diff --git a/lib/properties/compact.js b/lib/properties/compact.js
index d3f5884..af51026 100644
--- a/lib/properties/compact.js
+++ b/lib/properties/compact.js
@@ -15,14 +15,14 @@ module.exports = (function () {
var makeDefaultProperties = function (props, important) {
return props.map(function(prop) { return makeDefaultProperty(prop, important); });
};
-
+
// Regexes used for stuff
var cssUnitRegexStr = '(\\.?\\d+(px|%|em|rem|in|cm|mm|ex|pt|pc|)|auto|inherit)';
var cssFunctionNoVendorRegexStr = '[A-Z]?(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]| |\\+|\\-|%|\\.)*\\)';
var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]| |\\+|\\-|%|\\.)*\\)';
var cssFunctionAnyRegexStr = '(' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
var cssUnitAnyRegexStr = '(' + cssUnitRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
-
+
// Validator
// NOTE: The point here is not semantical but syntactical validity
var validator = {
@@ -96,7 +96,7 @@ module.exports = (function () {
}
};
validator = Object.freeze(validator);
-
+
// Merge functions
var canMerge = {
// Use when two tokens of the same property can always be merged
@@ -113,17 +113,17 @@ module.exports = (function () {
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
// Understandability: (unit without functions) > (vendor prefixed functions) > (standard functions) > anything else
// NOTE: vendor-specific property values are not taken into consideration here at the moment
-
+
if (validator.isValidFunctionWithVendorPrefix(t2.value))
return true;
if (validator.isValidFunctionWithVendorPrefix(t1.value))
return false;
-
+
if (validator.isValidFunctionWithoutVendorPrefix(t2.value))
return true;
if (validator.isValidFunctionWithoutVendorPrefix(t1.value))
return false;
-
+
return t1.value === t2.value;
},
// Use for color properties (color, background-color, border-color, etc.)
@@ -131,19 +131,19 @@ module.exports = (function () {
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
// Understandability: (hex | named) > (rgba | hsla) > anything else
// NOTE: at this point rgb and hsl are replaced by hex values by clean-css
-
+
// (hex | named)
if (validator.isValidNamedColor(t2.value) || validator.isValidHexColor(t2.value))
return true;
if (validator.isValidNamedColor(t1.value) || validator.isValidHexColor(t1.value))
return false;
-
+
// (rgba|hsla)
if (validator.isValidRgbaColor(t2.value) || validator.isValidHslColor(t2.value) || validator.isValidHslaColor(t2.value))
return true;
if (validator.isValidRgbaColor(t1.value) || validator.isValidHslColor(t1.value) || validator.isValidHslaColor(t1.value))
return false;
-
+
// anything else
return t1.value === t2.value;
},
@@ -151,24 +151,24 @@ module.exports = (function () {
backgroundImage: function (t1, t2) {
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
// Understandability: (none | url) > anything else > none --- yes, none both overrides anything and is overridden by anyting
-
+
// none
if (t1.value === 'none')
return true;
-
+
// (none | url)
if (t2.value === 'none' || validator.isValidUrl(t2.value))
return true;
if (t1.value === 'none' || validator.isValidUrl(t1.value))
return false;
-
+
// anything else
return t1.value === t2.value;
}
// TODO: add more
};
canMerge = Object.freeze(canMerge);
-
+
// Functions for breaking up shorthands to components
var breakUp = {
// Use this for properties with 4 unit values (like margin or padding)
@@ -177,7 +177,7 @@ module.exports = (function () {
var descriptor = processable[token.prop];
var result = [];
var splitval = token.value.match(new RegExp(cssUnitAnyRegexStr, 'gi'));
-
+
if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) {
// This token is malformed and we have no idea how to fix it. So let's just keep it intact
return [token];
@@ -196,7 +196,7 @@ module.exports = (function () {
// foo{margin:1px 2px 3px} -> foo{margin:1px 2px 3px 2px}
splitval[3] = splitval[1];
}
-
+
// Now break it up to its components
for (var i = 0; i < descriptor.components.length; i++) {
var t = {
@@ -206,7 +206,7 @@ module.exports = (function () {
};
result.push(t);
}
-
+
return result;
},
// Breaks up a background property value
@@ -214,23 +214,23 @@ module.exports = (function () {
// Default values
var result = makeDefaultProperties(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant);
var color = result[0], image = result[1], repeat = result[2], position = result[3], attachment = result[4];
-
+
// Take care of inherit
if (token.value === 'inherit') {
// NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
color.value = image.value = repeat.value = position.value = 'inherit';
return result;
}
-
+
// Break the background up into parts
var parts = token.value.split(' ');
if (parts.length === 0) {
return result;
}
-
+
// The trick here is that we start going through the parts from the end, then stop after background repeat,
// then start from the from the beginning until we find a valid color value. What remains will be treated as background-image.
-
+
var currentIndex = parts.length - 1;
var current = parts[currentIndex];
// Attachment
@@ -277,15 +277,15 @@ module.exports = (function () {
// Default values
var result = makeDefaultProperties(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
var type = result[0], position = result[1], image = result[2];
-
+
if (token.value === 'inherit') {
type.value = position.value = image.value = 'inherit';
return result;
}
-
+
var parts = token.value.split(' ');
var ci = 0;
-
+
// Type
if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
type.value = parts[ci];
@@ -300,7 +300,7 @@ module.exports = (function () {
if (ci < parts.length) {
image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
}
-
+
return result;
},
// Breaks up outline
@@ -308,22 +308,22 @@ module.exports = (function () {
// Default values
var result = makeDefaultProperties(['outline-color', 'outline-style', 'outline-width'], token.isImportant);
var color = result[0], style = result[1], width = result[2];
-
+
// Take care of inherit
if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
color.value = style.value = width.value = 'inherit';
return result;
}
-
+
// NOTE: usually users don't follow the required order of parts in this shorthand,
// so we'll try to parse it caring as little about order as possible
-
+
var parts = token.value.split(' '), w;
-
+
if (parts.length === 0) {
return result;
}
-
+
if (parts.length >= 1) {
// Try to find outline-width, excluding inherit because that can be anything
w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
@@ -354,18 +354,18 @@ module.exports = (function () {
// * if there was any value (including 'inherit') that could be the color, we already got it
// * we already found any suitable value for outline-style and outline-width, excluding 'inherit'
// Conclusion: if there are parts left, that could only mean that outline-style and possibly outline-width is 'inherit'
-
+
style.value = 'inherit';
if (parts.length >= 2) {
width.value = 'inherit';
}
}
-
+
return result;
}
};
breakUp = Object.freeze(breakUp);
-
+
// Contains functions that can put together shorthands from their components
var putTogether = {
// Use this for properties which have four unit values (margin, padding, etc.)
@@ -401,7 +401,7 @@ module.exports = (function () {
value: '',
isImportant: isImportant
};
-
+
// Go through all tokens and concatenate their values as necessary
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
@@ -411,24 +411,24 @@ module.exports = (function () {
result.defaultsOut[token.prop] = true;
continue;
}
-
+
result.value += ' ' + token.value;
}
-
+
result.value = result.value.trim();
if (!result.value) {
result.defaultsOutAll = true;
result.value = processable[prop].defaultValue;
}
-
+
return result;
}
};
putTogether = Object.freeze(putTogether);
-
+
// Properties to process
// Extend this object in order to add support for more properties in the optimizer.
- //
+ //
// Each key in this object represents a CSS property and should be an object.
// Such an object contains properties that describe how the represented CSS property should be handled.
// Possible options:
@@ -603,36 +603,36 @@ module.exports = (function () {
// TODO: add more
};
processable = Object.freeze(processable);
-
+
var isHackValue = function(t) { return t.value === '__hack'; };
var important = '!important';
-
+
// Breaks up the CSS properties so that they can be handled more easily
var tokenize = function (input) {
// Split the input by semicolons and parse the parts
var tokens = input.split(';').map(function(fullProp) {
- // Find first colon
+ // Find first colon
var colonPos = fullProp.indexOf(':');
-
+
if (colonPos < 0) {
// This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
return {
value: fullProp
};
}
-
+
// Parse parts of the property
var prop = fullProp.substr(0, colonPos).trim();
var value = fullProp.substr(colonPos + 1).trim();
var isImportant = false;
var importantPos = value.indexOf(important);
-
+
// Check if the property is important
if (importantPos >= 1 && importantPos === value.length - important.length) {
value = value.substr(0, importantPos).trim();
isImportant = true;
}
-
+
// Return result
return {
prop: prop,
@@ -640,15 +640,15 @@ module.exports = (function () {
isImportant: isImportant
};
});
-
+
return tokens;
};
-
+
// Transforms tokens back into CSS properties
var detokenize = function(tokens) {
var str = tokens.map(function(token) {
var result = '';
-
+
// NOTE: malformed tokens will not have a 'prop' property
if (token.prop) {
result += token.prop + ':';
@@ -659,20 +659,20 @@ module.exports = (function () {
if (token.isImportant) {
result += important;
}
-
+
return result;
}).join(';');
-
+
return str;
};
-
+
// Breaks up shorthand properties into their components
var breakUpShorthands = function(tokens) {
var result = [];
-
+
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
-
+
if (!token.prop) {
// Malformed token, but keep it intact
result.push(token);
@@ -684,10 +684,10 @@ module.exports = (function () {
result.push(token);
continue;
}
-
+
// We know how to break it up, so let's do it
var components = processable[token.prop].breakUp(token);
-
+
for (var j = 0; j < components.length; j++) {
result.push(components[j]);
}
@@ -697,21 +697,21 @@ module.exports = (function () {
result.push(token);
}
}
-
+
return result;
};
-
+
// Merges same properties
// https://github.com/GoalSmashers/clean-css/issues/173
// https://github.com/GoalSmashers/clean-css/issues/168
var mergeSame = function(tokens) {
var result = [];
-
+
// Variables used inside the array
var can, token;
// Function used for finding the alreadyThere list
var alreadyThereFilter = function(t) { return t.prop === token.prop && can(t, token); };
-
+
for (var i = 0; i < tokens.length; i++) {
token = tokens[i];
// Skip malformed tokens that have no property name, but keep them intact
@@ -719,12 +719,12 @@ module.exports = (function () {
result.push(token);
continue;
}
-
+
// Get the function that determines if this token can be merged with others, default is same name and value
can = (processable[token.prop] && processable[token.prop].canMerge) || canMerge.sameValue;
// Find the definition that's mergable with token and is already there
var alreadyThere = result.filter(alreadyThereFilter);
-
+
// alreadyThere contains at most 1 item because we already merged those that came before it and might be mergeable with this one
if (alreadyThere.length) {
if (!alreadyThere[0].isImportant || token.isImportant) {
@@ -737,10 +737,10 @@ module.exports = (function () {
result.push(token);
}
}
-
+
return result;
};
-
+
// Compacts the tokens by transforming properties into their shorthand notations when possible
// https://github.com/GoalSmashers/clean-css/issues/134
var compactToShorthands = function(tokens, important) {
@@ -749,19 +749,19 @@ module.exports = (function () {
// Filter functions used inside the loop
var foundFilterFunc = function(t) { return t.isImportant === important && cc === t.prop; };
var importantTrickFunc = function(t) { return t.isImportant === true && cc === t.prop; };
-
+
// Go through all possible shorthand notations
shorthandsLoop:
for (var i in processable) {
// Omit current token if it's unknown or isn't a shorthand or if it's a shorthand but we don't know how to put it together
if (!processable.hasOwnProperty(i) || !processable[i].components || !processable[i].components.length || !processable[i].putTogether)
continue;
-
+
// The shorthand we currently work with
var sh = processable[i];
// The last compacted shorthand
var lastCompacted = null;
-
+
// Same shorthand can occour multiple times; so we only stop this loop if no sufficient components can be found for one more
while (true) {
// Components found among the tokens
@@ -770,7 +770,7 @@ module.exports = (function () {
var trickComps = [];
// Position of new shorthand
var shorthandPosition = -1;
-
+
// Find the first definition of every component
for (var j = 0; j < sh.components.length; j++) {
cc = sh.components[j];
@@ -804,20 +804,20 @@ module.exports = (function () {
continue shorthandsLoop;
}
}
-
+
if (shorthandPosition === -1 || comps.length < sh.components.length) {
// There are not enough components and no amount of trickery could help it
continue shorthandsLoop;
}
-
+
var compacted = processable[i].putTogether(i, comps, important);
-
+
// If no components are tricked, the shorthand is always shorter; otherwise we should check if it's truly shorter
// NOTE: this is the place where we also forcibly get rid of the hacked-in default for list-style-type
if (trickComps.length === 0 || detokenize([compacted]).length <= detokenize(comps).length || comps.some(isHackValue)) {
// Put the compacted token in the place of the first component
tokens[shorthandPosition] = compacted;
-
+
// Remove other tokens
for (var k = 0; k < comps.length; k++) {
index = tokens.indexOf(comps[k]);
@@ -826,18 +826,18 @@ module.exports = (function () {
}
}
}
-
+
lastCompacted = compacted;
}
}
-
+
return tokens;
};
// Processes the input by calling the other functions
// input is the content of a selector block (excluding the braces), NOT a full selector
var process = function (input) {
-
+
// The algorithm here is designed to optimize properties in a CSS selector block
// and output the smallest possible equivalent code. Here are the steps:
//
@@ -853,9 +853,9 @@ module.exports = (function () {
// * Removes default values from shorthand declarations
// * Opens up opportunities for further optimalizations because granular components of shorthands are much easier to compare/process individually
//
-
+
var tokens = tokenize(input);
-
+
//console.log('\n> initial tokenization: ----------\n', JSON.stringify(tokens, 2, 2));
//console.log('\n> initial tokenization: ----------\n', detokenize(tokens));
tokens = breakUpShorthands(tokens);
@@ -870,10 +870,10 @@ module.exports = (function () {
tokens = compactToShorthands(tokens, true);
//console.log('\n> after compacting important shorthands ----------\n', JSON.stringify(tokens, 2, 2));
//console.log('\n> after compacting important shorthands ----------\n', detokenize(tokens));
-
+
return detokenize(tokens);
};
-
+
// Return the process function as module.exports
return process;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment