diff --git a/History.md b/History.md index 9f0ab96c3..584d7d8e4 100644 --- a/History.md +++ b/History.md @@ -7,6 +7,7 @@ * Moves tokenizer code into lib/tokenizer. * Moves URL scanner into lib/urls/reduce (was named incorrectly before). * Moves URL rebasing & rewriting into lib/urls. +* Fixed issue [#375](https://github.com/jakubpawlowicz/clean-css/issues/375) - unit compatibility switches. * Fixed issue [#436](https://github.com/jakubpawlowicz/clean-css/issues/436) - refactors URI rewriting. * Fixed issue [#448](https://github.com/jakubpawlowicz/clean-css/issues/448) - rebasing no protocol URIs. * Fixed issue [#517](https://github.com/jakubpawlowicz/clean-css/issues/517) - turning off color optimizations. diff --git a/README.md b/README.md index cbc27e076..2a117b8d8 100644 --- a/README.md +++ b/README.md @@ -317,9 +317,15 @@ with the following options available: * `'[+-]selectors.adjacentSpace'` - turn on / off extra space before `nav` element * `'[+-]selectors.ie7Hack'` - turn on / off IE7 selector hack removal (`*+html...`) * `'[+-]selectors.special'` - a regular expression with all special, unmergeable selectors (leave it empty unless you know what you are doing) +* `'[+-]units.ch'` - turn on / off treating `ch` as a proper unit * `'[+-]units.rem'` - turn on / off treating `rem` as a proper unit +* `'[+-]units.vh'` - turn on / off treating `vh` as a proper unit +* `'[+-]units.vm'` - turn on / off treating `vm` as a proper unit +* `'[+-]units.vmax'` - turn on / off treating `vmax` as a proper unit +* `'[+-]units.vmin'` - turn on / off treating `vmin` as a proper unit +* `'[+-]units.vm'` - turn on / off treating `vm` as a proper unit -For example, this declaration `--compatibility 'ie8,+units.rem'` will ensure IE8 compatiblity while enabling `rem` units so the following style `margin:0px 0rem` can be shortened to `margin:0`, while in pure IE8 mode it can't be. +For example, using `--compatibility 'ie8,+units.rem'` will ensure IE8 compatiblity while enabling `rem` units so the following style `margin:0px 0rem` can be shortened to `margin:0`, while in pure IE8 mode it can't be. To pass a single off (-) switch in CLI please use the following syntax `--compatibility *,-units.rem`. diff --git a/lib/properties/validator.js b/lib/properties/validator.js index b99580848..343bc2aad 100644 --- a/lib/properties/validator.js +++ b/lib/properties/validator.js @@ -3,7 +3,7 @@ var Splitter = require('../utils/splitter'); var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial']; -var allUnits = ['px', '%', 'em', 'rem', 'in', 'cm', 'mm', 'ex', 'pt', 'pc', 'vw', 'vh', 'vmin', 'vmax']; +var allUnits = ['px', '%', 'em', 'in', 'cm', 'mm', 'ex', 'pt', 'pc', 'ch', 'rem', 'vh', 'vm', 'vmin', 'vmax', 'vw']; var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + allUnits.join('|') + '|)|auto|inherit)'; var cssCalcRegexStr = '(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)'; var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)'; @@ -31,18 +31,13 @@ var listStyleTypeKeywords = ['armenian', 'circle', 'cjk-ideographic', 'decimal', var listStylePositionKeywords = ['inside', 'outside', 'inherit']; function Validator(compatibility) { - if (compatibility.units.rem) { - this.compatibleCssUnitRegex = cssUnitRegex; - this.compatibleCssUnitAnyRegex = cssUnitAnyRegex; - } else { - var validUnits = allUnits.slice(0).filter(function (value) { - return value != 'rem'; - }); - - var compatibleCssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + validUnits.join('|') + ')|auto|inherit)'; - this.compatibleCssUnitRegex = new RegExp('^' + compatibleCssUnitRegexStr + '$', 'i'); - this.compatibleCssUnitAnyRegex = new RegExp('^(none|' + widthKeywords.join('|') + '|' + compatibleCssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')$', 'i'); - } + var validUnits = allUnits.slice(0).filter(function (value) { + return !(value in compatibility.units) || compatibility.units[value] === true; + }); + + var compatibleCssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + validUnits.join('|') + '|)|auto|inherit)'; + this.compatibleCssUnitRegex = new RegExp('^' + compatibleCssUnitRegexStr + '$', 'i'); + this.compatibleCssUnitAnyRegex = new RegExp('^(none|' + widthKeywords.join('|') + '|' + compatibleCssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')$', 'i'); } Validator.prototype.isValidHexColor = function (s) { diff --git a/lib/selectors/optimizers/simple.js b/lib/selectors/optimizers/simple.js index d73a1f292..888e95bd5 100644 --- a/lib/selectors/optimizers/simple.js +++ b/lib/selectors/optimizers/simple.js @@ -16,9 +16,14 @@ function SimpleOptimizer(options) { this.options = options; var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%']; - if (options.compatibility.units.rem) - units.push('rem'); - options.unitsRegexp = new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')', 'g'); + var otherUnits = ['ch', 'rem', 'vh', 'vm', 'vmax', 'vmin', 'vw']; + + otherUnits.forEach(function (unit) { + if (options.compatibility.units[unit]) + units.push(unit); + }); + + options.unitsRegexp = new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')(\\W|$)', 'g'); options.precision = {}; options.precision.value = options.roundingPrecision === undefined ? @@ -106,7 +111,9 @@ function unitMinifier(_, value, unitsRegexp) { if (/^(?:\-moz\-calc|\-webkit\-calc|calc)\(/.test(value)) return value; - return value.replace(unitsRegexp, '$1' + '0'); + return value + .replace(unitsRegexp, '$1' + '0' + '$2') + .replace(unitsRegexp, '$1' + '0' + '$2'); } function multipleZerosMinifier(property) { diff --git a/lib/utils/compatibility.js b/lib/utils/compatibility.js index ff1823ab3..c39778b41 100644 --- a/lib/utils/compatibility.js +++ b/lib/utils/compatibility.js @@ -21,7 +21,13 @@ var DEFAULTS = { special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right)/ // special selectors which prevent merging }, units: { - rem: true + ch: true, + rem: true, + vh: true, + vm: true, // vm is vmin on IE9+ see https://developer.mozilla.org/en-US/docs/Web/CSS/length + vmax: true, + vmin: true, + vw: true } }, 'ie8': { @@ -44,7 +50,13 @@ var DEFAULTS = { special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/ }, units: { - rem: false + ch: false, + rem: false, + vh: false, + vm: false, + vmax: false, + vmin: false, + vw: false } }, 'ie7': { @@ -67,7 +79,13 @@ var DEFAULTS = { special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:focus|:before|:after|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/ }, units: { - rem: false + ch: false, + rem: false, + vh: false, + vm: false, + vmax: false, + vmin: false, + vw: false, } } }; diff --git a/test/integration-test.js b/test/integration-test.js index bf5925a8c..330391a79 100644 --- a/test/integration-test.js +++ b/test/integration-test.js @@ -523,10 +523,6 @@ vows.describe('integration tests').addBatch({ 'a{box-shadow:0 0 0 .15em #EBEBEB}' ], 'box shadow with three zeros and a value': 'a{box-shadow:0 0 0 15px #EBEBEB}', - 'rems': [ - 'div{width:0rem;height:0rem}', - 'div{width:0;height:0}' - ], 'prefixed box shadow zeros': [ 'a{-webkit-box-shadow:0 0 0 0; -moz-box-shadow:0 0 0 0}', 'a{-webkit-box-shadow:0 0;-moz-box-shadow:0 0}' diff --git a/test/properties/optimizer-test.js b/test/properties/optimizer-test.js index e09c7feb4..2933e11a1 100644 --- a/test/properties/optimizer-test.js +++ b/test/properties/optimizer-test.js @@ -9,10 +9,11 @@ var Compatibility = require('../../lib/utils/compatibility'); var Validator = require('../../lib/properties/validator'); var addOptimizationMetadata = require('../../lib/selectors/optimization-metadata'); -var compatibility = new Compatibility().toOptions(); -var validator = new Validator(compatibility); -function _optimize(source, mergeAdjacent, aggressiveMerging) { +function _optimize(source, mergeAdjacent, aggressiveMerging, compatibilityOptions) { + var compatibility = new Compatibility(compatibilityOptions).toOptions(); + var validator = new Validator(compatibility); + var tokens = tokenize(source, { options: {}, sourceTracker: new SourceTracker(), @@ -384,6 +385,27 @@ vows.describe(optimize) [['border-top-width', false , false], ['calc(100%)']] ]); } + }, + 'understandable - non adjacent units': { + 'topic': 'a{margin-top:100px;padding-top:30px;margin-top:10vmin}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic, false, true), [ + [['padding-top', false , false], ['30px']], + [['margin-top', false , false], ['10vmin']] + ]); + } + } + }) + .addBatch({ + 'understandable - non adjacent units in IE8 mode 123': { + 'topic': 'a{margin-top:100px;padding-top:30px;margin-top:10vmin}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic, false, true, 'ie8'), [ + [['margin-top', false , false], ['100px']], + [['padding-top', false , false], ['30px']], + [['margin-top', false , false], ['10vmin']] + ]); + } } }) .export(module); diff --git a/test/selectors/optimizers/simple-test.js b/test/selectors/optimizers/simple-test.js index a9725983b..55e6610ba 100644 --- a/test/selectors/optimizers/simple-test.js +++ b/test/selectors/optimizers/simple-test.js @@ -454,14 +454,46 @@ vows.describe(SimpleOptimizer) 'div{transform:rotate(10deg) skew(.5deg)}', [['transform', 'rotate(10deg)', 'skew(.5deg)']] ], + 'ch': [ + 'div{width:0ch;height:0ch}', + [['width', '0'], ['height', '0']] + ], + 'rem': [ + 'div{width:0rem;height:0rem}', + [['width', '0'], ['height', '0']] + ], + 'vh': [ + 'div{width:0vh;height:0vh}', + [['width', '0'], ['height', '0']] + ], + 'vm': [ + 'div{width:0vm;height:0vm}', + [['width', '0'], ['height', '0']] + ], + 'vmax': [ + 'div{width:0vmax;height:0vmax}', + [['width', '0'], ['height', '0']] + ], + 'vmin': [ + 'div{width:0vmin;height:0vmin}', + [['width', '0'], ['height', '0']] + ], + 'vw': [ + 'div{width:0vw;height:0vw}', + [['width', '0'], ['height', '0']] + ], 'mixed units': [ 'a{margin:0em 0rem 0px 0pt}', [['margin', '0']] ], - 'mixed vales': [ + 'mixed values #1': [ 'a{padding:10px 0em 30% 0rem}', [['padding', '10px', '0', '30%', '0']] ], + 'mixed values #2': [ + 'a{padding:10ch 0vm 30vmin 0vw}', + [['padding', '10ch', '0', '30vmin', '0']] + ], 'inside calc': [ 'a{font-size:calc(100% + 0px)}', [['font-size', 'calc(100% + 0px)']] @@ -478,9 +510,13 @@ vows.describe(SimpleOptimizer) 'a{margin:0em 0rem 0px 0pt}', [['margin', '0', '0rem', '0', '0']] ], - 'mixed vales': [ + 'mixed values #1': [ 'a{padding:10px 0em 30% 0rem}', [['padding', '10px', '0', '30%', '0rem']] + ], + 'mixed values #2': [ + 'a{padding:10ch 0vm 30vmin 0vw}', + [['padding', '10ch', '0vm', '30vmin', '0vw']] ] }, { compatibility: 'ie8' }) ) diff --git a/test/utils/compatibility-test.js b/test/utils/compatibility-test.js index 2c128710c..a4b7f9de6 100644 --- a/test/utils/compatibility-test.js +++ b/test/utils/compatibility-test.js @@ -21,7 +21,13 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isFalse(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right)/); + assert.isTrue(options.units.ch); assert.isTrue(options.units.rem); + assert.isTrue(options.units.vh); + assert.isTrue(options.units.vm); + assert.isTrue(options.units.vmax); + assert.isTrue(options.units.vmin); + assert.isTrue(options.units.vw); } }, 'not given': { @@ -34,7 +40,7 @@ vows.describe(Compatibility) }, 'as a populated hash': { 'topic': function () { - return new Compatibility({ units: { rem: false }, properties: { prefix: true } }).toOptions(); + return new Compatibility({ units: { rem: false, vmax: false }, properties: { prefix: true } }).toOptions(); }, 'gets merged options': function(options) { assert.isTrue(options.colors.opacity); @@ -48,7 +54,13 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isFalse(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right)/); + assert.isTrue(options.units.ch); assert.isFalse(options.units.rem); + assert.isTrue(options.units.vh); + assert.isTrue(options.units.vm); + assert.isFalse(options.units.vmax); + assert.isTrue(options.units.vmin); + assert.isTrue(options.units.vw); } } }) @@ -70,7 +82,13 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isFalse(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/); + assert.isFalse(options.units.ch); assert.isFalse(options.units.rem); + assert.isFalse(options.units.vh); + assert.isFalse(options.units.vm); + assert.isFalse(options.units.vmax); + assert.isFalse(options.units.vmin); + assert.isFalse(options.units.vw); } }, 'as an ie7 template': { @@ -90,7 +108,13 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isTrue(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:focus|:before|:after|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/); + assert.isFalse(options.units.ch); assert.isFalse(options.units.rem); + assert.isFalse(options.units.vh); + assert.isFalse(options.units.vm); + assert.isFalse(options.units.vmax); + assert.isFalse(options.units.vmin); + assert.isFalse(options.units.vw); } }, 'as an unknown template': { @@ -120,7 +144,13 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isFalse(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/); + assert.isFalse(options.units.ch); assert.isFalse(options.units.rem); + assert.isFalse(options.units.vh); + assert.isFalse(options.units.vm); + assert.isFalse(options.units.vmax); + assert.isFalse(options.units.vmin); + assert.isFalse(options.units.vw); } }, 'as a single string value without group': { @@ -140,7 +170,14 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isFalse(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right)/); - assert.isTrue(options.units.rem); } + assert.isTrue(options.units.ch); + assert.isTrue(options.units.rem); + assert.isTrue(options.units.vh); + assert.isTrue(options.units.vm); + assert.isTrue(options.units.vmax); + assert.isTrue(options.units.vmin); + assert.isTrue(options.units.vw); + } }, 'as a complex string value without group': { 'topic': function () { @@ -159,7 +196,13 @@ vows.describe(Compatibility) assert.isFalse(options.selectors.adjacentSpace); assert.isFalse(options.selectors.ie7Hack); assert.deepEqual(options.selectors.special, /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right)/); + assert.isTrue(options.units.ch); assert.isFalse(options.units.rem); + assert.isTrue(options.units.vh); + assert.isTrue(options.units.vm); + assert.isTrue(options.units.vmax); + assert.isTrue(options.units.vmin); + assert.isTrue(options.units.vw); } } })