Permalink
Browse files

Fixes #425 - enables natural method of sorting selectors.

Still defaults to standard sorting but natural becomes
a new option. Controlled via `selectorsSortingMethod`
option in level 1 optimizations.

Why:

* Natural way could be a more compression-efficient way;
* it's easier to read for a human.
  • Loading branch information...
1 parent 7b2b357 commit d0320e81b216c083af67e85eb51aec1d42c67e08 @jakubpawlowicz committed Jan 10, 2017
View
@@ -136,6 +136,7 @@ cleancss -O1 all:off;specialComments:1 one.css
# `replaceTimeUnits` controls replacing time units with shorter values; defaults to `on
# `replaceZeroUnits` controls replacing zero values with units; defaults to `on`
# `roundingPrecision` rounds pixel values to `N` decimal places; `off` disables rounding; defaults to `off`
+# `selectorsSortingMethod` denotes selector sorting method; can be `natural` or `standard`; defaults to `standard`
# `specialComments` denotes a number of /*! ... */ comments preserved; defaults to `all`
# `tidyAtRules` controls at-rules (e.g. `@charset`, `@import`) optimizing; defaults to `on`
# `tidyBlockScopes` controls block scopes (e.g. `@media`) optimizing; defaults to `on`
@@ -222,6 +223,7 @@ new CleanCSS({
replaceTimeUnits: true, // controls replacing time units with shorter values; defaults to `true`
replaceZeroUnits: true, // controls replacing zero values with units; defaults to `true`
roundingPrecision: false, // rounds pixel values to `N` decimal places; `false` disables rounding; defaults to `false`
+ selectorsSortingMethod: 'standard', // denotes selector sorting method; can be `natural` or `standard`; defaults to `standard`
specialComments: 'all', // denotes a number of /*! ... */ comments preserved; defaults to `all`
tidyAtRules: true, // controls at-rules (e.g. `@charset`, `@import`) optimizing; defaults to `true`
tidyBlockScopes: true, // controls block scopes (e.g. `@media`) optimizing; defaults to `true`
View
@@ -55,6 +55,7 @@ commands.on('--help', function () {
console.log(' %> # `replaceTimeUnits` controls replacing time units with shorter values; defaults to `on');
console.log(' %> # `replaceZeroUnits` controls replacing zero values with units; defaults to `on`');
console.log(' %> # `roundingPrecision` rounds pixel values to `N` decimal places; `off` disables rounding; defaults to `off`');
+ console.log(' %> # `selectorsSortingMethod` denotes selector sorting method; can be `natural` or `standard`; defaults to `standard`');
console.log(' %> # `specialComments` denotes a number of /*! ... */ comments preserved; defaults to `all`');
console.log(' %> # `tidyAtRules` controls at-rules (e.g. `@charset`, `@import`) optimizing; defaults to `on`');
console.log(' %> # `tidyBlockScopes` controls block scopes (e.g. `@media`) optimizing; defaults to `on`');
@@ -1,6 +1,7 @@
var shortenHex = require('./shorten-hex');
var shortenHsl = require('./shorten-hsl');
var shortenRgb = require('./shorten-rgb');
+var sortSelectors = require('./sort-selectors');
var tidyRules = require('./tidy-rules');
var tidyBlock = require('./tidy-block');
var tidyAtRule = require('./tidy-at-rule');
@@ -664,6 +665,7 @@ function level1Optimize(tokens, context) {
break;
case Token.RULE:
token[1] = levelOptions.tidySelectors ? tidyRules(token[1], !ie7Hack, adjacentSpace, beautify, context.warnings) : token[1];
+ token[1] = token[1].length > 1 ? sortSelectors(token[1], levelOptions.selectorsSortingMethod) : token[1];
optimizeBody(token[2], context);
afterRules = true;
break;
@@ -0,0 +1,25 @@
+var naturalCompare = require('../../utils/natural-compare');
+
+function naturalSorter(scope1, scope2) {
+ return naturalCompare(scope1[1], scope2[1]);
+}
+
+function standardSorter(scope1, scope2) {
+ return scope1[1] > scope2[1] ? 1 : -1;
+}
+
+function sortSelectors(selectors, method) {
+ var sorter;
+
+ switch (method) {
+ case 'natural':
+ sorter = naturalSorter;
+ break;
+ case 'standard':
+ sorter = standardSorter;
+ }
+
+ return selectors.sort(sorter);
+}
+
+module.exports = sortSelectors;
@@ -135,10 +135,6 @@ function removeQuotes(value) {
.replace(/="([a-zA-Z][a-zA-Z\d\-_]+)"/g, '=$1');
}
-function ruleSorter(s1, s2) {
- return s1[1] > s2[1] ? 1 : -1;
-}
-
function tidyRules(rules, removeUnsupported, adjacentSpace, beautify, warnings) {
var list = [];
var repeated = [];
@@ -194,7 +190,7 @@ function tidyRules(rules, removeUnsupported, adjacentSpace, beautify, warnings)
list = [];
}
- return list.sort(ruleSorter);
+ return list;
}
module.exports = tidyRules;
@@ -2,8 +2,11 @@ var isMergeable = require('./is-mergeable');
var compactorOptimize = require('./compacting/optimize');
+var sortSelectors = require('../level-1/sort-selectors');
var tidyRules = require('../level-1/tidy-rules');
+var OptimizationLevel = require('../../options/optimization-level').OptimizationLevel;
+
var serializeBody = require('../../writer/one-time').body;
var serializeRules = require('../../writer/one-time').rules;
@@ -13,6 +16,7 @@ function mergeAdjacent(tokens, context) {
var lastToken = [null, [], []];
var options = context.options;
var adjacentSpace = options.compatibility.selectors.adjacentSpace;
+ var selectorsSortingMethod = options.level[OptimizationLevel.One].selectorsSortingMethod;
var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
@@ -33,6 +37,7 @@ function mergeAdjacent(tokens, context) {
isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements) &&
isMergeable(serializeRules(lastToken[1]), mergeablePseudoClasses, mergeablePseudoElements)) {
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] = [];
} else {
lastToken = token;
@@ -1,5 +1,6 @@
var isMergeable = require('./is-mergeable');
+var sortSelectors = require('../level-1/sort-selectors');
var tidyRules = require('../level-1/tidy-rules');
var OptimizationLevel = require('../../options/optimization-level').OptimizationLevel;
@@ -38,6 +39,7 @@ function mergeNonAdjacentByBody(tokens, context) {
var options = context.options;
var mergeSemantically = options.level[OptimizationLevel.Two].mergeSemantically;
var adjacentSpace = options.compatibility.selectors.adjacentSpace;
+ var selectorsSortingMethod = options.level[OptimizationLevel.One].selectorsSortingMethod;
var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
var candidates = {};
@@ -58,9 +60,13 @@ function mergeNonAdjacentByBody(tokens, context) {
if (oldToken &&
isMergeable(serializeRules(token[1]), mergeablePseudoClasses, mergeablePseudoElements) &&
isMergeable(serializeRules(oldToken[1]), mergeablePseudoClasses, mergeablePseudoElements)) {
- token[1] = token[2].length > 0 ?
- tidyRules(oldToken[1].concat(token[1]), false, adjacentSpace, false, context.warnings) :
- oldToken[1].concat(token[1]);
+
+ if (token[2].length > 0) {
+ token[1] = tidyRules(oldToken[1].concat(token[1]), false, adjacentSpace, false, context.warnings);
+ token[1] = token[1].length > 1 ? sortSelectors(token[1], selectorsSortingMethod) : token[1];
+ } else {
+ token[1] = oldToken[1].concat(token[1]);
+ }
oldToken[2] = [];
candidates[candidateBody] = null;
@@ -26,6 +26,7 @@ DEFAULTS[OptimizationLevel.One] = {
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -132,6 +132,34 @@ vows.describe('level 1 optimizations')
}, { level: 1 })
)
.addBatch(
+ optimizerContext('selectors - sorting when tidySelectors is off', {
+ 'no numbers': [
+ '.block,.another-block,.one-more-block{color:red}',
+ '.another-block,.block,.one-more-block{color:red}'
+ ]
+ }, { level: { 1: { tidySelectors: false } } })
+ )
+ .addBatch(
+ optimizerContext('selectors - natural order', {
+ 'no numbers': [
+ '.block,.another-block,.one-more-block{color:red}',
+ '.another-block,.block,.one-more-block{color:red}'
+ ],
+ 'some numbers': [
+ '.block-3,.block-11,.block{color:red}',
+ '.block,.block-3,.block-11{color:red}'
+ ],
+ 'all numbers': [
+ '.block-3,.block-11,.block-1{color:red}',
+ '.block-1,.block-3,.block-11{color:red}'
+ ],
+ 'complex numbers': [
+ '.block-1__element-11,.block-1__element-2,.block-12__element-1,.block-3__element-1{color:red}',
+ '.block-1__element-2,.block-1__element-11,.block-3__element-1,.block-12__element-1{color:red}'
+ ],
+ }, { level: { 1: { selectorsSortingMethod: 'natural' } } })
+ )
+ .addBatch(
optimizerContext('selectors - ie8', {
'+html': [
'*+html .foo{color:red}',
@@ -34,6 +34,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -79,6 +80,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -113,6 +115,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -172,6 +175,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 0,
tidyAtRules: true,
tidyBlockScopes: true,
@@ -220,6 +224,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: false,
replaceZeroUnits: false,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: false,
tidyBlockScopes: false,
@@ -254,6 +259,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: false,
replaceZeroUnits: false,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: false,
tidyBlockScopes: false,
@@ -288,6 +294,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 0,
tidyAtRules: true,
tidyBlockScopes: true,
@@ -336,6 +343,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 0,
tidyAtRules: true,
tidyBlockScopes: true,
@@ -384,6 +392,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(3),
+ selectorsSortingMethod: 'standard',
specialComments: 0,
tidyAtRules: true,
tidyBlockScopes: true,
@@ -418,6 +427,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -466,6 +476,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -514,6 +525,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -562,6 +574,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -610,6 +623,7 @@ vows.describe(optimizationLevelFrom)
replaceTimeUnits: true,
replaceZeroUnits: true,
roundingPrecision: roundingPrecisionFrom(undefined),
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -661,6 +675,7 @@ vows.describe(optimizationLevelFrom)
'vw': 4,
'%': 4
},
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,
@@ -712,6 +727,7 @@ vows.describe(optimizationLevelFrom)
'vw': 5,
'%': 1
},
+ selectorsSortingMethod: 'standard',
specialComments: 'all',
tidyAtRules: true,
tidyBlockScopes: true,

0 comments on commit d0320e8

Please sign in to comment.