[4.1.0-pre / 20xx-xx-xx](https://github.com/jakubpawlowicz/clean-css/compare/4.0...HEAD)
==================
+* Fixed issue [#916](https://github.com/jakubpawlowicz/clean-css/issues/916) - maximum number of merged selectors.
* Fixed issue [#893](https://github.com/jakubpawlowicz/clean-css/issues/893) - `inline: false` as alias to `inline: 'none'`.
* Fixed issue [#890](https://github.com/jakubpawlowicz/clean-css/issues/890) - adds toggle to disable empty tokens removal.
* Fixed issue [#886](https://github.com/jakubpawlowicz/clean-css/issues/886) - better multi pseudo class / element merging.
* `multiplePseudoMerging` compatibility flag controlling merging of rules with multiple pseudo classes / elements;
* `removeEmpty` flag in level 1 optimizations controlling removal of rules and nested blocks;
* `removeEmpty` flag in level 2 optimizations controlling removal of rules and nested blocks;
+* `compatibility: { selectors: { mergeLimit: <number> } }` flag in compatibility settings controlling maximum number of selectors in a single rule;
## Constructor options
ie7Hack: true, // controls removal of IE7 selector hacks, e.g. `*+html...`
mergeablePseudoClasses: [':active', ...], // controls a whitelist of mergeable pseudo classes
mergeablePseudoElements: ['::after', ...], // controls a whitelist of mergeable pseudo elements
+ mergeLimit: 8191, // controls maximum number of selectors in a single rule
multiplePseudoMerging: true // controls merging of rules with multiple pseudo classes / elements (since 4.1.0-pre)
},
units: {
var selectorsSortingMethod = options.level[OptimizationLevel.One].selectorsSortingMethod;
var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
+ var mergeLimit = options.compatibility.selectors.mergeLimit;
var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging;
for (var i = 0, l = tokens.length; i < l; i++) {
token[2] = [];
} else if (lastToken[0] == Token.RULE && serializeBody(token[2]) == serializeBody(lastToken[2]) &&
isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) &&
- isMergeable(serializeRules(lastToken[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging)) {
+ isMergeable(serializeRules(lastToken[1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) &&
+ lastToken[1].length < mergeLimit) {
lastToken[1] = tidyRules(lastToken[1].concat(token[1]), false, adjacentSpace, false, context.warnings);
lastToken[1] = lastToken.length > 1 ? sortSelectors(lastToken[1], selectorsSortingMethod) : lastToken[1];
token[2] = [];
var options = context.options;
var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
+ var mergeLimit = options.compatibility.selectors.mergeLimit;
var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging;
var specificityCache = context.cache.specificity;
var movableTokens = {};
for (k = 0; k < movedCount; k++) {
var movedProperty = movedProperties[k];
- if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty, specificityCache) && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token)) {
+ if (movedToBeDropped.indexOf(k) == -1 && (!canReorderSingle(property, movedProperty, specificityCache) && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token) ||
+ movableTokens[movedProperty[4]] && movableTokens[movedProperty[4]].length === mergeLimit)) {
dropPropertiesAt(i + 1, movedProperty, token);
if (movedToBeDropped.indexOf(k) == -1) {
continue;
var key = property[4];
- movableTokens[key] = movableTokens[key] || [];
- movableTokens[key].push(token);
+
+ if (movedSameProperty && movedProperties[samePropertyAt][5].length + property[5].length > mergeLimit) {
+ dropPropertiesAt(i + 1, movedProperties[samePropertyAt]);
+ movedProperties.splice(samePropertyAt, 1);
+ movableTokens[key] = [token];
+ movedSameProperty = false;
+ } else {
+ movableTokens[key] = movableTokens[key] || [];
+ movableTokens[key].push(token);
+ }
if (movedSameProperty) {
movedProperties[samePropertyAt] = cloneAndMergeSelectors(movedProperties[samePropertyAt], property);
'::first-letter',
'::first-line'
], // selectors with these pseudo-elements can be merged as these are universally supported
+ mergeLimit: 8191, // number of rules that can be safely merged together
multiplePseudoMerging: true
},
units: {
]
}, { level: 2 })
)
+ .addBatch(
+ optimizerContext('limit rule merging', {
+ 'adjacent with as many rules as limit': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}',
+ '.block--1,.block--2,.block--3{color:red}'
+ ],
+ 'adjacent with extra rule': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}.block--4{color:red}',
+ '.block--1,.block--2,.block--3{color:red}.block--4{color:red}'
+ ],
+ 'adjacent with extra two rules': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}.block--4{color:red}.block--5{color:red}',
+ '.block--1,.block--2,.block--3{color:red}.block--4,.block--5{color:red}'
+ ],
+ 'adjacent with extra three rules': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}.block--4{color:red}.block--5{color:red}.block--6{color:red}',
+ '.block--1,.block--2,.block--3{color:red}.block--4,.block--5,.block--6{color:red}'
+ ],
+ 'non-adjacent': [
+ '.block--1{color:red}.other-block--1{width:0}.block--2{color:red}.other-block--2{height:0}.block--3{color:red}.other-block--3{opacity:0}.block--4{color:red}',
+ '.block--1{color:red}.other-block--1{width:0}.block--2,.block--3,.block--4{color:red}.other-block--2{height:0}.other-block--3{opacity:0}'
+ ]
+ }, { compatibility: { selectors: { mergeLimit: 3 } }, level: { 2: { all: true } } })
+ )
.addBatch(
optimizerContext('level 2 off', {
'repeated' : [
]
}, { level: { 2: { all: false, restructureRules: true, removeEmpty: true } } })
)
+ .addBatch(
+ optimizerContext('only rule restructuring with rule merging limit', {
+ 'adjacent with as many rules as limit': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}',
+ '.block--1,.block--2,.block--3{color:red}'
+ ],
+ 'adjacent with extra rule': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}.block--4{color:red}',
+ '.block--1{color:red}.block--2,.block--3,.block--4{color:red}'
+ ],
+ 'adjacent with extra two rules 123': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}.block--4{color:red}.block--5{color:red}',
+ '.block--1,.block--2{color:red}.block--3,.block--4,.block--5{color:red}'
+ ],
+ 'adjacent with extra three rules': [
+ '.block--1{color:red}.block--2{color:red}.block--3{color:red}.block--4{color:red}.block--5{color:red}.block--6{color:red}',
+ '.block--1,.block--2,.block--3{color:red}.block--4,.block--5,.block--6{color:red}'
+ ],
+ 'non-adjacent': [
+ '.block--1{color:red}.other-block--1{width:0}.block--2{color:red}.other-block--2{height:0}.block--3{color:red}.other-block--3{opacity:0}.block--4{color:red}',
+ '.block--1{color:red}.other-block--1{width:0}.block--2,.block--3,.block--4{color:red}.other-block--2{height:0}.other-block--3{opacity:0}'
+ ]
+ }, { compatibility: { selectors: { mergeLimit: 3 } }, level: { 2: { all: false, restructureRules: true, removeEmpty: true } } })
+ )
.addBatch(
optimizerContext('level 2 off', {
'up until changed': [