Skip to content

Commit

Permalink
See #731 - adds granular control over level 2 optimizations.
Browse files Browse the repository at this point in the history
Why:

* So users can selectively disable certain optimizations if they
  want to.
  • Loading branch information
jakubpawlowicz committed Jan 10, 2017
1 parent 47486aa commit 0b81de2
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 24 deletions.
1 change: 1 addition & 0 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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
}
}
});
Expand Down
14 changes: 10 additions & 4 deletions bin/cleancss
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
46 changes: 36 additions & 10 deletions lib/optimizer/level-2/optimize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
6 changes: 6 additions & 0 deletions lib/options/optimization-level.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions test/optimizer/level-2/merge-adjacent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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': [
Expand Down
16 changes: 16 additions & 0 deletions test/optimizer/level-2/merge-non-adjacent-by-body-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,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': [
Expand Down
16 changes: 16 additions & 0 deletions test/optimizer/level-2/merge-non-adjacent-by-selector-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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': [
Expand Down
8 changes: 8 additions & 0 deletions test/optimizer/level-2/reduce-non-adjacent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,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': [
Expand Down
8 changes: 8 additions & 0 deletions test/optimizer/level-2/remove-duplicate-font-at-rules-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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': [
Expand Down
12 changes: 10 additions & 2 deletions test/optimizer/level-2/remove-duplicate-media-queries-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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': [
Expand All @@ -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 } } })
)
Expand Down
8 changes: 8 additions & 0 deletions test/optimizer/level-2/remove-duplicates-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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': [
Expand Down
48 changes: 48 additions & 0 deletions test/options/optimization-level-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 0b81de2

Please sign in to comment.