Permalink
Browse files

See #731 - adds granular control over level 2 optimizations.

Why:

* So users can selectively disable certain optimizations if they
  want to.
  • Loading branch information...
1 parent 47486aa commit 0b81de23105b52b3235635052b0102cfbe6cfc91 @jakubpawlowicz committed Jan 9, 2017
View
@@ -19,6 +19,7 @@
* Fixed issue [#685](https://github.com/jakubpawlowicz/clean-css/issues/685) - adds lowercasing hex colors optimization.
* Fixed issue [#686](https://github.com/jakubpawlowicz/clean-css/issues/686) - adds rounding precision for all units.
* Fixed issue [#703](https://github.com/jakubpawlowicz/clean-css/issues/703) - changes default IE compatibility to 10+.
+* Fixed issue [#731](https://github.com/jakubpawlowicz/clean-css/issues/731) - adds granular control over level 2 optimizations.
* Fixed issue [#739](https://github.com/jakubpawlowicz/clean-css/issues/739) - error when a closing brace is missing.
* Fixed issue [#750](https://github.com/jakubpawlowicz/clean-css/issues/750) - allows `width` overriding.
* Fixed issue [#756](https://github.com/jakubpawlowicz/clean-css/issues/756) - adds disabling font-weight optimizations.
View
@@ -129,10 +129,16 @@ Level 2 optimizations:
```bash
cleancss -O2 one.css
cleancss -O2 mediaMerging:off;restructuring:off;semanticMerging:on;shorthandCompacting:off one.css
-# `mediaMerging` controls `@media` merging behavior; defaults to `on` (alias to `true`)
-# `restructuring` controls content restructuring behavior; defaults `off` (alias to `false`)
-# `semanticMerging` controls semantic merging behavior; defaults to `off` (alias to `false`)
-# `shorthandCompacting` controls shorthand compacting behavior; defaults to `on` (alias to `true`)
+# `adjacentRulesMerging` controls adjacent rules merging; defaults to `on`
+# `duplicateFontRulesRemoving` controls duplicate `@font-face` removing; defaults to `on`
+# `duplicateMediaRemoving` controls duplicate `@media` removing; defaults to `on`
+# `duplicateRulesRemoving` controls duplicate rules removing; defaults to `on`
+# `mediaMerging` controls `@media` merging; defaults to `on`
+# `nonAdjacentRulesMerging` controls non-adjacent rule merging; defaults to `on`
+# `nonAdjacentRulesReducing` controls non-adjacent rule reducing; defaults to `on`
+# `restructuring` controls content restructuring; defaults to `off`
+# `semanticMerging` controls semantic merging; defaults to `off`
+# `shorthandCompacting` controls shorthand compacting; defaults to `on`
```
### How to use clean-css API?
@@ -194,10 +200,16 @@ new CleanCSS({
new CleanCSS({
level: {
2: {
- mediaMerging: true, // controls `@media` merging behavior; defaults to true
- restructuring: false, // controls content restructuring behavior; defaults to false
- semanticMerging: false, // controls semantic merging behavior; defaults to false
- shorthandCompacting: true // controls shorthand compacting behavior; defaults to true
+ adjacentRulesMerging: true, // controls adjacent rules merging; defaults to true
+ duplicateFontRulesRemoving: true, // controls duplicate `@font-face` removing; defaults to true
+ duplicateMediaRemoving: true, // controls duplicate `@media` removing; defaults to true
+ duplicateRulesRemoving: true, // controls duplicate rules removing; defaults to true
+ mediaMerging: true, // controls `@media` merging; defaults to true
+ nonAdjacentRulesMerging: true, // controls non-adjacent rule merging; defaults to true
+ nonAdjacentRulesReducing: true, // controls non-adjacent rule reducing; defaults to true
+ restructuring: false, // controls content restructuring; defaults to false
+ semanticMerging: false, // controls semantic merging; defaults to false
+ shorthandCompacting: true, // controls shorthand compacting; defaults to true
}
}
});
View
@@ -45,10 +45,16 @@ commands.on('--help', function () {
console.log(' Level 2 optimizations:');
console.log(' %> cleancss -O2 one.css');
console.log(' %> cleancss -O2 mediaMerging:off;restructuring:off;semanticMerging:on;shorthandCompacting:off one.css');
- console.log(' %> # `mediaMerging` controls `@media` merging behavior; defaults to `on` (alias to `true`)');
- console.log(' %> # `restructuring` controls content restructuring behavior; defaults to `off` (alias to `false`)');
- console.log(' %> # `semanticMerging` controls semantic merging behavior; defaults to `off` (alias to `false`)');
- console.log(' %> # `shorthandCompacting` controls shorthand compacting behavior; defaults to `on` (alias to `true`)');
+ console.log(' %> # `adjacentRulesMerging` controls adjacent rules merging; defaults to `on`');
+ console.log(' %> # `duplicateFontRulesRemoving` controls duplicate `@font-face` removing; defaults to `on`');
+ console.log(' %> # `duplicateMediaRemoving` controls duplicate `@media` removing; defaults to `on`');
+ console.log(' %> # `duplicateRulesRemoving` controls duplicate rules removing; defaults to `on`');
+ console.log(' %> # `mediaMerging` controls `@media` merging; defaults to `on`');
+ console.log(' %> # `nonAdjacentRulesMerging` controls non-adjacent rule merging; defaults to `on`');
+ console.log(' %> # `nonAdjacentRulesReducing` controls non-adjacent rule reducing; defaults to `on`');
+ console.log(' %> # `restructuring` controls content restructuring; defaults to `off`');
+ console.log(' %> # `semanticMerging` controls semantic merging; defaults to `off`');
+ console.log(' %> # `shorthandCompacting` controls shorthand compacting; defaults to `on`');
process.exit();
});
@@ -65,27 +65,53 @@ function recursivelyOptimizeProperties(tokens, context) {
}
function level2Optimize(tokens, context, withRestructuring) {
+ var levelOptions = context.options.level[OptimizationLevel.Two];
+ var reduced;
+ var i;
+
recursivelyOptimizeBlocks(tokens, context);
recursivelyOptimizeProperties(tokens, context);
- removeDuplicates(tokens, context);
- mergeAdjacent(tokens, context);
- reduceNonAdjacent(tokens, context);
+ if (levelOptions.duplicateRulesRemoving) {
+ removeDuplicates(tokens, context);
+ }
+
+ if (levelOptions.adjacentRulesMerging) {
+ mergeAdjacent(tokens, context);
+ }
+
+ if (levelOptions.nonAdjacentRulesReducing) {
+ reduceNonAdjacent(tokens, context);
+ }
+
+ if (levelOptions.nonAdjacentRulesMerging && levelOptions.nonAdjacentRulesMerging != 'body') {
+ mergeNonAdjacentBySelector(tokens, context);
+ }
- mergeNonAdjacentBySelector(tokens, context);
- mergeNonAdjacentByBody(tokens, context);
+ if (levelOptions.nonAdjacentRulesMerging && levelOptions.nonAdjacentRulesMerging != 'selector') {
+ mergeNonAdjacentByBody(tokens, context);
+ }
- if (context.options.level[OptimizationLevel.Two].restructuring && withRestructuring) {
+ if (levelOptions.restructuring && levelOptions.adjacentRulesMerging && withRestructuring) {
restructure(tokens, context);
mergeAdjacent(tokens, context);
}
- removeDuplicateFontAtRules(tokens, context);
+ if (levelOptions.restructuring && !levelOptions.adjacentRulesMerging && withRestructuring) {
+ restructure(tokens, context);
+ }
+
+ if (levelOptions.duplicateFontRulesRemoving) {
+ removeDuplicateFontAtRules(tokens, context);
+ }
- if (context.options.level[OptimizationLevel.Two].mediaMerging) {
+ if (levelOptions.duplicateMediaRemoving) {
removeDuplicateMediaQueries(tokens, context);
- var reduced = mergeMediaQueries(tokens, context);
- for (var i = reduced.length - 1; i >= 0; i--) {
+ }
+
+ if (levelOptions.mediaMerging) {
+ reduced = mergeMediaQueries(tokens, context);
+ for (i = reduced.length - 1; i >= 0; i--) {
level2Optimize(reduced[i][2], context, false);
}
}
@@ -15,7 +15,13 @@ DEFAULTS[OptimizationLevel.One] = {
specialComments: 'all'
};
DEFAULTS[OptimizationLevel.Two] = {
+ adjacentRulesMerging: true,
+ duplicateFontRulesRemoving: true,
+ duplicateMediaRemoving: true,
+ duplicateRulesRemoving: true,
mediaMerging: true,
+ nonAdjacentRulesMerging: true,
+ nonAdjacentRulesReducing: true,
restructuring: false,
semanticMerging: false,
shorthandCompacting: true
@@ -99,6 +99,14 @@ vows.describe('remove duplicates')
}, { level: { 2: { restructuring: true } } })
)
.addBatch(
+ optimizerContext('with level 2 off but only adjacentRuleMerging on', {
+ 'same context': [
+ 'a{background:url(image.png)}a{display:block;width:75px;background-repeat:no-repeat}',
+ 'a{background:url(image.png);display:block;width:75px;background-repeat:no-repeat}',
+ ],
+ }, { level: { 2: { all: false, adjacentRulesMerging: true } } })
+ )
+ .addBatch(
optimizerContext('with level 2 off', {
'same context': [
'a{color:red}a{display:block;width:75px}div{color:#fff}',
@@ -35,6 +35,22 @@ vows.describe('merge non djacent by body')
}, { level: 2 })
)
.addBatch(
+ optimizerContext('with level 2 off but nonAdjacentRulesMerging on', {
+ 'of element selectors': [
+ 'p{color:red}div{display:block}span{color:red}',
+ 'p,span{color:red}div{display:block}'
+ ]
+ }, { level: { 2: { all: false, nonAdjacentRulesMerging: true } } })
+ )
+ .addBatch(
+ optimizerContext('with level 2 off but nonAdjacentRulesMerging set to selector', {
+ 'of element selectors': [
+ 'p{color:red}div{display:block}span{color:red}',
+ 'p{color:red}div{display:block}span{color:red}'
+ ]
+ }, { level: { 2: { all: false, nonAdjacentRulesMerging: 'selector' } } })
+ )
+ .addBatch(
optimizerContext('with level 2 off', {
'with repeated selectors': [
'#zero>p,.one,.two{color:red}#zero>p,.three,.two{color:red}',
@@ -19,6 +19,22 @@ vows.describe('merge non djacent by selector')
}, { level: 2 })
)
.addBatch(
+ optimizerContext('with level 2 off but nonAdjacentRulesMerging on', {
+ 'of element selectors': [
+ '.one{color:red}.two{color:#fff}.one{font-weight:400}',
+ '.one{color:red;font-weight:400}.two{color:#fff}'
+ ]
+ }, { level: { 2: { all: false, nonAdjacentRulesMerging: true } } })
+ )
+ .addBatch(
+ optimizerContext('with level 2 off but nonAdjacentRulesMerging set to body', {
+ 'of element selectors': [
+ '.one{color:red}.two{color:#fff}.one{font-weight:400}',
+ '.one{color:red}.two{color:#fff}.one{font-weight:400}'
+ ]
+ }, { level: { 2: { all: false, nonAdjacentRulesMerging: 'body' } } })
+ )
+ .addBatch(
optimizerContext('level 2 off', {
'up': [
'.one{color:red}.two{color:#fff}.one{font-weight:400}',
@@ -127,6 +127,14 @@ vows.describe('remove duplicates')
}, { aggressiveMerging: false, level: { 2: { restructuring: true } } })
)
.addBatch(
+ optimizerContext('level 2 off but nonAdjacentRulesReducing on', {
+ 'non-adjacent with multi selectors': [
+ 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+ 'a{margin:0;color:red}.one{color:red}a,p{color:red;padding:0}'
+ ]
+ }, { level: { 2: { all: false, nonAdjacentRulesReducing: true } } })
+ )
+ .addBatch(
optimizerContext('level 2 off', {
'non-adjacent': [
'a{color:red;display:block}.one{font-size:13px}a{color:#fff;margin:2px}',
@@ -19,6 +19,14 @@ vows.describe('remove duplicate @font-face at-rules')
}, { level: 2 })
)
.addBatch(
+ optimizerContext('level 2 off but duplicateFontRulesRemoving on', {
+ 'non-adjacent': [
+ '@font-face{font-family:test;src:url(fonts/test.woff2)}.one{color:red}@font-face{font-family:test;src:url(fonts/test.woff2)}',
+ '@font-face{font-family:test;src:url(fonts/test.woff2)}.one{color:red}'
+ ]
+ }, { level: { 2: { all: false, duplicateFontRulesRemoving: true } } })
+ )
+ .addBatch(
optimizerContext('level 2 off', {
'keeps content same': [
'@font-face{font-family:test;src:url(fonts/test.woff2)}@font-face{font-family:test;src:url(fonts/test.woff2)}',
@@ -23,6 +23,14 @@ vows.describe('remove duplicate media queries')
}, { level: 2 })
)
.addBatch(
+ optimizerContext('level 2 off but duplicateMediaRemoving on', {
+ 'non-adjacent': [
+ '@media screen{a{color:red}}@media print{a{color:#fff}}@media screen{a{color:red}}',
+ '@media print{a{color:#fff}}@media screen{a{color:red}}'
+ ]
+ }, { level: { 2: { all: false, duplicateMediaRemoving: true } } })
+ )
+ .addBatch(
optimizerContext('level 2 off', {
'keeps content same': [
'@media screen{a{color:red}}@media screen{a{color:red}}',
@@ -33,8 +41,8 @@ vows.describe('remove duplicate media queries')
.addBatch(
optimizerContext('media merging off', {
'keeps content same': [
- '@media screen{a{color:red}}@media screen{a{color:red}}',
- '@media screen{a{color:red}}@media screen{a{color:red}}'
+ '@media screen{a{color:red}}@media screen{div{color:red}}',
+ '@media screen{a{color:red}}@media screen{div{color:red}}'
]
}, { level: { 2: { mediaMerging: false } } })
)
@@ -43,6 +43,14 @@ vows.describe('remove duplicates')
}, { level: 2 })
)
.addBatch(
+ optimizerContext('level 2 off but removing duplicates on', {
+ 'same context': [
+ 'a{color:red}div{color:blue}a{color:red}',
+ 'div{color:#00f}a{color:red}'
+ ]
+ }, { level: { 2: { all: false, duplicateRulesRemoving: true } } })
+ )
+ .addBatch(
optimizerContext('level 2 off', {
'same context': [
'a{color:red}div{color:blue}a{color:red}',
@@ -70,7 +70,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: true,
+ duplicateFontRulesRemoving: true,
+ duplicateMediaRemoving: true,
+ duplicateRulesRemoving: true,
mediaMerging: true,
+ nonAdjacentRulesMerging: true,
+ nonAdjacentRulesReducing: true,
restructuring: false,
semanticMerging: false,
shorthandCompacting: true
@@ -106,7 +112,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: true,
+ duplicateFontRulesRemoving: true,
+ duplicateMediaRemoving: true,
+ duplicateRulesRemoving: true,
mediaMerging: true,
+ nonAdjacentRulesMerging: true,
+ nonAdjacentRulesReducing: true,
restructuring: false,
semanticMerging: false,
shorthandCompacting: true
@@ -131,7 +143,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: false,
+ duplicateFontRulesRemoving: false,
+ duplicateMediaRemoving: false,
+ duplicateRulesRemoving: false,
mediaMerging: true,
+ nonAdjacentRulesMerging: false,
+ nonAdjacentRulesReducing: false,
restructuring: false,
semanticMerging: false,
shorthandCompacting: false
@@ -156,7 +174,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: false,
+ duplicateFontRulesRemoving: false,
+ duplicateMediaRemoving: false,
+ duplicateRulesRemoving: false,
mediaMerging: true,
+ nonAdjacentRulesMerging: false,
+ nonAdjacentRulesReducing: false,
restructuring: false,
semanticMerging: false,
shorthandCompacting: false
@@ -198,7 +222,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: true,
+ duplicateFontRulesRemoving: true,
+ duplicateMediaRemoving: true,
+ duplicateRulesRemoving: true,
mediaMerging: false,
+ nonAdjacentRulesMerging: true,
+ nonAdjacentRulesReducing: true,
restructuring: false,
semanticMerging: true,
shorthandCompacting: true
@@ -223,7 +253,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: true,
+ duplicateFontRulesRemoving: true,
+ duplicateMediaRemoving: true,
+ duplicateRulesRemoving: true,
mediaMerging: false,
+ nonAdjacentRulesMerging: true,
+ nonAdjacentRulesReducing: true,
restructuring: false,
semanticMerging: true,
shorthandCompacting: true
@@ -248,7 +284,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: false,
+ duplicateFontRulesRemoving: false,
+ duplicateMediaRemoving: false,
+ duplicateRulesRemoving: false,
mediaMerging: true,
+ nonAdjacentRulesMerging: false,
+ nonAdjacentRulesReducing: false,
restructuring: false,
semanticMerging: true,
shorthandCompacting: false
@@ -273,7 +315,13 @@ vows.describe(optimizationLevelFrom)
},
'has level 2 options': function (levelOptions) {
assert.deepEqual(levelOptions['2'], {
+ adjacentRulesMerging: false,
+ duplicateFontRulesRemoving: false,
+ duplicateMediaRemoving: false,
+ duplicateRulesRemoving: false,
mediaMerging: true,
+ nonAdjacentRulesMerging: false,
+ nonAdjacentRulesReducing: false,
restructuring: false,
semanticMerging: true,
shorthandCompacting: false

0 comments on commit 0b81de2

Please sign in to comment.