Skip to content

Commit

Permalink
Fixes #861 - new transition property optimizer.
Browse files Browse the repository at this point in the history
Why:

* To collapse multiple `transition-*` properties into a shorthand
  `transition` property;
* to merge `transition-*` properties into shorthand `transition`;
* to get rid of default values in shorthands.
  • Loading branch information
jakubpawlowicz committed Jun 16, 2017
1 parent 1897d49 commit 595cd47
Show file tree
Hide file tree
Showing 11 changed files with 697 additions and 46 deletions.
1 change: 1 addition & 0 deletions History.md
Expand Up @@ -2,6 +2,7 @@
==================

* Adds `process` method for compatibility with optimize-css-assets-webpack-plugin.
* Fixed issue [#861](https://github.com/jakubpawlowicz/clean-css/issues/861) - new `transition` property optimizer.

[4.1.4 / 2017-06-14](https://github.com/jakubpawlowicz/clean-css/compare/v4.1.3...v4.1.4)
==================
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -118,6 +118,7 @@ clean-css 4.1 introduces the following changes / features:
clean-css 4.2 will introduce the following changes / features:

* Adds `process` method for compatibility with optimize-css-assets-webpack-plugin;
* new `transition` property optimizer;

## Constructor options

Expand Down
52 changes: 50 additions & 2 deletions lib/optimizer/level-2/break-up.js
Expand Up @@ -105,7 +105,7 @@ function animation(property, compactable, validator) {
} else if (validator.isTime(value[1]) && !delaySet) {
delay.value = [value];
delaySet = true;
} else if ((validator.isGlobal(value[1]) || validator.isAnimationTimingFunction(value[1])) && !timingSet) {
} else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) {
timing.value = [value];
timingSet = true;
} else if ((validator.isAnimationIterationCountKeyword(value[1]) || validator.isPositiveNumber(value[1])) && !iterationSet) {
Expand Down Expand Up @@ -489,6 +489,53 @@ function listStyle(property, compactable, validator) {
return components;
}

function transition(property, compactable, validator) {
var prop = _wrapDefault(property.name + '-property', property, compactable);
var duration = _wrapDefault(property.name + '-duration', property, compactable);
var timing = _wrapDefault(property.name + '-timing-function', property, compactable);
var delay = _wrapDefault(property.name + '-delay', property, compactable);
var components = [prop, duration, timing, delay];
var values = property.value;
var value;
var durationSet = false;
var delaySet = false;
var propSet = false;
var timingSet = false;
var i;
var l;

if (property.value.length == 1 && property.value[0][1] == 'inherit') {
prop.value = duration.value = timing.value = delay.value = property.value;
return components;
}

if (values.length > 1 && _anyIsInherit(values)) {
throw new InvalidPropertyError('Invalid animation values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
}

for (i = 0, l = values.length; i < l; i++) {
value = values[i];

if (validator.isTime(value[1]) && !durationSet) {
duration.value = [value];
durationSet = true;
} else if (validator.isTime(value[1]) && !delaySet) {
delay.value = [value];
delaySet = true;
} else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) {
timing.value = [value];
timingSet = true;
} else if (validator.isIdentifier(value[1]) && !propSet) {
prop.value = [value];
propSet = true;
} else {
throw new InvalidPropertyError('Invalid animation value at ' + formatPosition(value[2][0]) + '. Ignoring.');
}
}

return components;
}

function widthStyleColor(property, compactable, validator) {
var descriptor = compactable[property.name];
var components = [
Expand Down Expand Up @@ -558,5 +605,6 @@ module.exports = {
fourValues: fourValues,
listStyle: listStyle,
multiplex: multiplex,
outline: widthStyleColor
outline: widthStyleColor,
transition: transition
};
35 changes: 22 additions & 13 deletions lib/optimizer/level-2/can-override.js
Expand Up @@ -20,16 +20,6 @@ function animationName(validator, value1, value2) {
return validator.isAnimationNameKeyword(value2) || validator.isIdentifier(value2);
}

function animationTimingFunction(validator, value1, value2) {
if (!understandable(validator, value1, value2, 0, true) && !(validator.isAnimationTimingFunction(value2) || validator.isGlobal(value2))) {
return false;
} else if (validator.isVariable(value1) && validator.isVariable(value2)) {
return true;
}

return validator.isAnimationTimingFunction(value2) || validator.isGlobal(value2);
}

function areSameFunction(validator, value1, value2) {
if (!validator.isFunction(value1) || !validator.isFunction(value2)) {
return false;
Expand Down Expand Up @@ -129,14 +119,22 @@ function keywordWithGlobal(propertyName) {
};
}

function propertyName(validator, value1, value2) {
if (!understandable(validator, value1, value2, 0, true) && !validator.isIdentifier(value2)) {
return false;
} else if (validator.isVariable(value1) && validator.isVariable(value2)) {
return true;
}

return validator.isIdentifier(value2);
}

function sameFunctionOrValue(validator, value1, value2) {
return areSameFunction(validator, value1, value2) ?
true :
value1 === value2;
}



function textShadow(validator, value1, value2) {
if (!understandable(validator, value1, value2, 0, true) && !(validator.isUnit(value2) || validator.isColor(value2) || validator.isGlobal(value2))) {
return false;
Expand Down Expand Up @@ -165,6 +163,16 @@ function time(validator, value1, value2) {
return sameFunctionOrValue(validator, value1, value2);
}

function timingFunction(validator, value1, value2) {
if (!understandable(validator, value1, value2, 0, true) && !(validator.isTimingFunction(value2) || validator.isGlobal(value2))) {
return false;
} else if (validator.isVariable(value1) && validator.isVariable(value2)) {
return true;
}

return validator.isTimingFunction(value2) || validator.isGlobal(value2);
}

function unit(validator, value1, value2) {
if (!understandable(validator, value1, value2, 0, true) && !validator.isUnit(value2)) {
return false;
Expand Down Expand Up @@ -206,7 +214,9 @@ module.exports = {
color: color,
components: components,
image: image,
propertyName: propertyName,
time: time,
timingFunction: timingFunction,
unit: unit
},
property: {
Expand All @@ -215,7 +225,6 @@ module.exports = {
animationIterationCount: animationIterationCount,
animationName: animationName,
animationPlayState: keywordWithGlobal('animation-play-state'),
animationTimingFunction: animationTimingFunction,
backgroundAttachment: keyword('background-attachment'),
backgroundClip: keywordWithGlobal('background-clip'),
backgroundOrigin: keyword('background-origin'),
Expand Down
80 changes: 78 additions & 2 deletions lib/optimizer/level-2/compactable.js
Expand Up @@ -38,7 +38,7 @@ var compactable = {
'animation': {
canOverride: canOverride.generic.components([
canOverride.generic.time,
canOverride.property.animationTimingFunction,
canOverride.generic.timingFunction,
canOverride.generic.time,
canOverride.property.animationIterationCount,
canOverride.property.animationDirection,
Expand Down Expand Up @@ -158,7 +158,7 @@ var compactable = {
]
},
'animation-timing-function': {
canOverride: canOverride.property.animationTimingFunction,
canOverride: canOverride.generic.timingFunction,
componentOf: [
'animation'
],
Expand Down Expand Up @@ -917,6 +917,82 @@ var compactable = {
'-webkit-'
]
},
'transition': {
breakUp: breakUp.multiplex(breakUp.transition),
canOverride: canOverride.generic.components([
canOverride.property.transitionProperty,
canOverride.generic.time,
canOverride.generic.timingFunction,
canOverride.generic.time
]),
components: [
'transition-property',
'transition-duration',
'transition-timing-function',
'transition-delay'
],
defaultValue: 'none',
restore: restore.multiplex(restore.withoutDefaults),
shorthand: true,
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-delay': {
canOverride: canOverride.generic.time,
componentOf: [
'transition'
],
defaultValue: '0s',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-duration': {
canOverride: canOverride.generic.time,
componentOf: [
'transition'
],
defaultValue: '0s',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-property': {
canOverride: canOverride.generic.propertyName,
componentOf: [
'transition'
],
defaultValue: 'all',
intoMultiplexMode: 'placeholder',
placeholderValue: '_', // it's a short value that won't match any property and still be a valid `transition-property`
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-timing-function': {
canOverride: canOverride.generic.timingFunction,
componentOf: [
'transition'
],
defaultValue: 'ease',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'vertical-align': {
canOverride: canOverride.property.verticalAlign,
defaultValue: 'baseline'
Expand Down
7 changes: 4 additions & 3 deletions lib/optimizer/level-2/properties/override-properties.js
Expand Up @@ -95,10 +95,11 @@ function turnShorthandValueIntoMultiplex(property, size) {
}

function turnLonghandValueIntoMultiplex(property, size) {
var withRealValue = compactable[property.name].intoMultiplexMode == 'real';
var withValue = withRealValue ?
var descriptor = compactable[property.name];
var withRealValue = descriptor.intoMultiplexMode == 'real';
var withValue = descriptor.intoMultiplexMode == 'real' ?
property.value.slice(0) :
compactable[property.name].defaultValue;
(descriptor.intoMultiplexMode == 'placeholder' ? descriptor.placeholderValue : descriptor.defaultValue);
var i = multiplexSize(property);
var j;
var m = withValue.length;
Expand Down
38 changes: 19 additions & 19 deletions lib/optimizer/validator.js
Expand Up @@ -3,7 +3,6 @@ var functionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(.*?\\)';
var variableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
var functionAnyRegexStr = '(' + variableRegexStr + '|' + functionNoVendorRegexStr + '|' + functionVendorRegexStr + ')';

var animationTimingFunctionRegex = /^(cubic\-bezier|steps)\([^\)]+\)$/;
var calcRegex = new RegExp('^(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)$', 'i');
var functionAnyRegex = new RegExp('^' + functionAnyRegexStr + '$', 'i');
var hslColorRegex = /^hsl\(\s*[\-\.\d]+\s*,\s*[\.\d]+%\s*,\s*[\.\d]+%\s*\)|hsla\(\s*[\-\.\d]+\s*,\s*[\.\d]+%\s*,\s*[\.\d]+%\s*,\s*[\.\d]+\s*\)$/;
Expand All @@ -14,6 +13,7 @@ var prefixRegex = /^-([a-z0-9]|-)*$/i;
var rgbColorRegex = /^rgb\(\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*\)|rgba\(\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*,\s*[\d]{1,3}\s*,\s*[\.\d]+\s*\)$/;
var shortHexColorRegex = /^#[0-9a-f]{3}$/i;
var timeRegex = new RegExp('^(\\-?\\+?\\.?\\d+\\.?\\d*(s|ms))$');
var timingFunctionRegex = /^(cubic\-bezier|steps)\([^\)]+\)$/;
var urlRegex = /^url\([\s\S]+\)$/i;
var variableRegex = new RegExp('^' + variableRegexStr + '$', 'i');

Expand All @@ -36,6 +36,15 @@ var Keywords = {
'ridge',
'solid'
],
'*-timing-function': [
'ease',
'ease-in',
'ease-in-out',
'ease-out',
'linear',
'step-end',
'step-start'
],
'animation-direction': [
'alternate',
'alternate-reverse',
Expand All @@ -58,15 +67,6 @@ var Keywords = {
'paused',
'running'
],
'animation-timing-function': [
'ease',
'ease-in',
'ease-in-out',
'ease-out',
'linear',
'step-end',
'step-start'
],
'background-attachment': [
'fixed',
'inherit',
Expand Down Expand Up @@ -337,14 +337,6 @@ var Units = [
'vw'
];

function isAnimationTimingFunction() {
var isTimingFunctionKeyword = isKeyword('animation-timing-function');

return function (value) {
return isTimingFunctionKeyword(value) || animationTimingFunctionRegex.test(value);
};
}

function isColor(value) {
return value != 'auto' &&
(
Expand Down Expand Up @@ -418,6 +410,14 @@ function isTime(value) {
return timeRegex.test(value);
}

function isTimingFunction() {
var isTimingFunctionKeyword = isKeyword('*-timing-function');

return function (value) {
return isTimingFunctionKeyword(value) || timingFunctionRegex.test(value);
};
}

function isUnit(compatibleCssUnitRegex, value) {
return compatibleCssUnitRegex.test(value);
}
Expand Down Expand Up @@ -446,7 +446,7 @@ function validator(compatibility) {
isAnimationIterationCountKeyword: isKeyword('animation-iteration-count'),
isAnimationNameKeyword: isKeyword('animation-name'),
isAnimationPlayStateKeyword: isKeyword('animation-play-state'),
isAnimationTimingFunction: isAnimationTimingFunction(),
isTimingFunction: isTimingFunction(),
isBackgroundAttachmentKeyword: isKeyword('background-attachment'),
isBackgroundClipKeyword: isKeyword('background-clip'),
isBackgroundOriginKeyword: isKeyword('background-origin'),
Expand Down

0 comments on commit 595cd47

Please sign in to comment.