Fixes #375 - unit compatibility switches.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 4 May 2015 11:11:10 +0000 (12:11 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 4 May 2015 11:15:23 +0000 (12:15 +0100)
Adds compatibility switches for `ch`, `vh`, `vm`, `vmax`, `vmin`,
and `vw` units.

Note we need #290 to fully support all use cases. Currently it'll be
just for implemented shorthands.

History.md
README.md
lib/properties/validator.js
lib/selectors/optimizers/simple.js
lib/utils/compatibility.js
test/integration-test.js
test/properties/optimizer-test.js
test/selectors/optimizers/simple-test.js
test/utils/compatibility-test.js

index 9f0ab96..584d7d8 100644 (file)
@@ -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.
index cbc27e0..2a117b8 100644 (file)
--- 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`.
 
index b995808..343bc2a 100644 (file)
@@ -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) {
index d73a1f2..888e95b 100644 (file)
@@ -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) {
index ff1823a..c39778b 100644 (file)
@@ -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,
     }
   }
 };
index bf5925a..330391a 100644 (file)
@@ -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}'
index e09c7fe..2933e11 100644 (file)
@@ -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);
index a972598..55e6610 100644 (file)
@@ -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' })
   )
index 2c12871..a4b7f9d 100644 (file)
@@ -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);
       }
     }
   })