326 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var sortSelectors = require('./sort-selectors');
 | |
| var tidyRules = require('./tidy-rules');
 | |
| var tidyBlock = require('./tidy-block');
 | |
| var tidyAtRule = require('./tidy-at-rule');
 | |
| 
 | |
| var Hack = require('../hack');
 | |
| var removeUnused = require('../remove-unused');
 | |
| var restoreFromOptimizing = require('../restore-from-optimizing');
 | |
| var wrapForOptimizing = require('../wrap-for-optimizing').all;
 | |
| 
 | |
| var configuration = require('../configuration');
 | |
| var optimizers = require('./value-optimizers');
 | |
| 
 | |
| var OptimizationLevel = require('../../options/optimization-level').OptimizationLevel;
 | |
| 
 | |
| var Token = require('../../tokenizer/token');
 | |
| var Marker = require('../../tokenizer/marker');
 | |
| 
 | |
| var formatPosition = require('../../utils/format-position');
 | |
| 
 | |
| var serializeRules = require('../../writer/one-time').rules;
 | |
| 
 | |
| var CHARSET_TOKEN = '@charset';
 | |
| var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');
 | |
| 
 | |
| var DEFAULT_ROUNDING_PRECISION = require('../../options/rounding-precision').DEFAULT;
 | |
| 
 | |
| var VARIABLE_PROPERTY_NAME_PATTERN = /^--\S+$/;
 | |
| var PROPERTY_NAME_PATTERN = /^(?:-chrome-|-[\w-]+\w|\w[\w-]+\w|\w{1,})$/;
 | |
| var IMPORT_PREFIX_PATTERN = /^@import/i;
 | |
| var URL_PREFIX_PATTERN = /^url\(/i;
 | |
| 
 | |
| function startsAsUrl(value) {
 | |
|   return URL_PREFIX_PATTERN.test(value);
 | |
| }
 | |
| 
 | |
| function isImport(token) {
 | |
|   return IMPORT_PREFIX_PATTERN.test(token[1]);
 | |
| }
 | |
| 
 | |
| function isLegacyFilter(property) {
 | |
|   var value;
 | |
| 
 | |
|   if (property.name == 'filter' || property.name == '-ms-filter') {
 | |
|     value = property.value[0][1];
 | |
| 
 | |
|     return value.indexOf('progid') > -1
 | |
|       || value.indexOf('alpha') === 0
 | |
|       || value.indexOf('chroma') === 0;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| function noop() {}
 | |
| 
 | |
| function noopValueOptimizer(_name, value, _options) { return value; }
 | |
| 
 | |
| function optimizeBody(rule, properties, context) {
 | |
|   var options = context.options;
 | |
|   var valueOptimizers;
 | |
|   var property, name, type, value;
 | |
|   var propertyToken;
 | |
|   var propertyOptimizer;
 | |
|   var serializedRule = serializeRules(rule);
 | |
|   var _properties = wrapForOptimizing(properties);
 | |
|   var pluginValueOptimizers = context.options.plugins.level1Value;
 | |
|   var pluginPropertyOptimizers = context.options.plugins.level1Property;
 | |
|   var isVariable;
 | |
|   var i, l;
 | |
| 
 | |
|   for (i = 0, l = _properties.length; i < l; i++) {
 | |
|     var j, k, m, n;
 | |
| 
 | |
|     property = _properties[i];
 | |
|     name = property.name;
 | |
|     propertyOptimizer = configuration[name] && configuration[name].propertyOptimizer || noop;
 | |
|     valueOptimizers = configuration[name] && configuration[name].valueOptimizers || [optimizers.whiteSpace];
 | |
|     isVariable = VARIABLE_PROPERTY_NAME_PATTERN.test(name);
 | |
| 
 | |
|     if (isVariable) {
 | |
|       valueOptimizers = options.variableOptimizers.length > 0
 | |
|         ? options.variableOptimizers
 | |
|         : [optimizers.whiteSpace];
 | |
|     }
 | |
| 
 | |
|     if (!isVariable && !PROPERTY_NAME_PATTERN.test(name)) {
 | |
|       propertyToken = property.all[property.position];
 | |
|       context.warnings.push('Invalid property name \'' + name + '\' at ' + formatPosition(propertyToken[1][2][0]) + '. Ignoring.');
 | |
|       property.unused = true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (property.value.length === 0) {
 | |
|       propertyToken = property.all[property.position];
 | |
|       context.warnings.push('Empty property \'' + name + '\' at ' + formatPosition(propertyToken[1][2][0]) + '. Ignoring.');
 | |
|       property.unused = true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (property.hack && (
 | |
|       (property.hack[0] == Hack.ASTERISK || property.hack[0] == Hack.UNDERSCORE)
 | |
|         && !options.compatibility.properties.iePrefixHack
 | |
|         || property.hack[0] == Hack.BACKSLASH && !options.compatibility.properties.ieSuffixHack
 | |
|         || property.hack[0] == Hack.BANG && !options.compatibility.properties.ieBangHack)) {
 | |
|       property.unused = true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!options.compatibility.properties.ieFilters && isLegacyFilter(property)) {
 | |
|       property.unused = true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (property.block) {
 | |
|       optimizeBody(rule, property.value[0][1], context);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     for (j = 0, m = property.value.length; j < m; j++) {
 | |
|       type = property.value[j][0];
 | |
|       value = property.value[j][1];
 | |
| 
 | |
|       if (type == Token.PROPERTY_BLOCK) {
 | |
|         property.unused = true;
 | |
|         context.warnings.push('Invalid value token at ' + formatPosition(value[0][1][2][0]) + '. Ignoring.');
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (startsAsUrl(value) && !context.validator.isUrl(value)) {
 | |
|         property.unused = true;
 | |
|         context.warnings.push('Broken URL \'' + value + '\' at ' + formatPosition(property.value[j][2][0]) + '. Ignoring.');
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       for (k = 0, n = valueOptimizers.length; k < n; k++) {
 | |
|         value = valueOptimizers[k](name, value, options);
 | |
|       }
 | |
| 
 | |
|       for (k = 0, n = pluginValueOptimizers.length; k < n; k++) {
 | |
|         value = pluginValueOptimizers[k](name, value, options);
 | |
|       }
 | |
| 
 | |
|       property.value[j][1] = value;
 | |
|     }
 | |
| 
 | |
|     propertyOptimizer(serializedRule, property, options);
 | |
| 
 | |
|     for (j = 0, m = pluginPropertyOptimizers.length; j < m; j++) {
 | |
|       pluginPropertyOptimizers[j](serializedRule, property, options);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   restoreFromOptimizing(_properties);
 | |
|   removeUnused(_properties);
 | |
|   removeComments(properties, options);
 | |
| }
 | |
| 
 | |
| function removeComments(tokens, options) {
 | |
|   var token;
 | |
|   var i;
 | |
| 
 | |
|   for (i = 0; i < tokens.length; i++) {
 | |
|     token = tokens[i];
 | |
| 
 | |
|     if (token[0] != Token.COMMENT) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     optimizeComment(token, options);
 | |
| 
 | |
|     if (token[1].length === 0) {
 | |
|       tokens.splice(i, 1);
 | |
|       i--;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function optimizeComment(token, options) {
 | |
|   if (token[1][2] == Marker.EXCLAMATION && (options.level[OptimizationLevel.One].specialComments == 'all' || options.commentsKept < options.level[OptimizationLevel.One].specialComments)) {
 | |
|     options.commentsKept++;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   token[1] = [];
 | |
| }
 | |
| 
 | |
| function cleanupCharsets(tokens) {
 | |
|   var hasCharset = false;
 | |
| 
 | |
|   for (var i = 0, l = tokens.length; i < l; i++) {
 | |
|     var token = tokens[i];
 | |
| 
 | |
|     if (token[0] != Token.AT_RULE) { continue; }
 | |
| 
 | |
|     if (!CHARSET_REGEXP.test(token[1])) { continue; }
 | |
| 
 | |
|     if (hasCharset || token[1].indexOf(CHARSET_TOKEN) == -1) {
 | |
|       tokens.splice(i, 1);
 | |
|       i--;
 | |
|       l--;
 | |
|     } else {
 | |
|       hasCharset = true;
 | |
|       tokens.splice(i, 1);
 | |
|       tokens.unshift([Token.AT_RULE, token[1].replace(CHARSET_REGEXP, CHARSET_TOKEN)]);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function buildUnitRegexp(options) {
 | |
|   var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%'];
 | |
|   var otherUnits = ['ch', 'rem', 'vh', 'vm', 'vmax', 'vmin', 'vw'];
 | |
| 
 | |
|   otherUnits.forEach(function(unit) {
 | |
|     if (options.compatibility.units[unit]) {
 | |
|       units.push(unit);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   return new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')(\\W|$)', 'g');
 | |
| }
 | |
| 
 | |
| function buildPrecisionOptions(roundingPrecision) {
 | |
|   var precisionOptions = {
 | |
|     matcher: null,
 | |
|     units: {}
 | |
|   };
 | |
|   var optimizable = [];
 | |
|   var unit;
 | |
|   var value;
 | |
| 
 | |
|   for (unit in roundingPrecision) {
 | |
|     value = roundingPrecision[unit];
 | |
| 
 | |
|     if (value != DEFAULT_ROUNDING_PRECISION) {
 | |
|       precisionOptions.units[unit] = {};
 | |
|       precisionOptions.units[unit].value = value;
 | |
|       precisionOptions.units[unit].multiplier = 10 ** value;
 | |
| 
 | |
|       optimizable.push(unit);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (optimizable.length > 0) {
 | |
|     precisionOptions.enabled = true;
 | |
|     precisionOptions.decimalPointMatcher = new RegExp('(\\d)\\.($|' + optimizable.join('|') + ')($|\\W)', 'g');
 | |
|     precisionOptions.zeroMatcher = new RegExp('(\\d*)(\\.\\d+)(' + optimizable.join('|') + ')', 'g');
 | |
|   }
 | |
| 
 | |
|   return precisionOptions;
 | |
| }
 | |
| 
 | |
| function buildVariableOptimizers(options) {
 | |
|   return options.level[OptimizationLevel.One].variableValueOptimizers.map(function(optimizer) {
 | |
|     if (typeof (optimizer) == 'string') {
 | |
|       return optimizers[optimizer] || noopValueOptimizer;
 | |
|     }
 | |
| 
 | |
|     return optimizer;
 | |
|   });
 | |
| }
 | |
| 
 | |
| function level1Optimize(tokens, context) {
 | |
|   var options = context.options;
 | |
|   var levelOptions = options.level[OptimizationLevel.One];
 | |
|   var ie7Hack = options.compatibility.selectors.ie7Hack;
 | |
|   var adjacentSpace = options.compatibility.selectors.adjacentSpace;
 | |
|   var spaceAfterClosingBrace = options.compatibility.properties.spaceAfterClosingBrace;
 | |
|   var format = options.format;
 | |
|   var mayHaveCharset = false;
 | |
|   var afterRules = false;
 | |
| 
 | |
|   options.unitsRegexp = options.unitsRegexp || buildUnitRegexp(options);
 | |
|   options.precision = options.precision || buildPrecisionOptions(levelOptions.roundingPrecision);
 | |
|   options.commentsKept = options.commentsKept || 0;
 | |
|   options.variableOptimizers = options.variableOptimizers || buildVariableOptimizers(options);
 | |
| 
 | |
|   for (var i = 0, l = tokens.length; i < l; i++) {
 | |
|     var token = tokens[i];
 | |
| 
 | |
|     switch (token[0]) {
 | |
|     case Token.AT_RULE:
 | |
|       token[1] = isImport(token) && afterRules ? '' : token[1];
 | |
|       token[1] = levelOptions.tidyAtRules ? tidyAtRule(token[1]) : token[1];
 | |
|       mayHaveCharset = true;
 | |
|       break;
 | |
|     case Token.AT_RULE_BLOCK:
 | |
|       optimizeBody(token[1], token[2], context);
 | |
|       afterRules = true;
 | |
|       break;
 | |
|     case Token.NESTED_BLOCK:
 | |
|       token[1] = levelOptions.tidyBlockScopes ? tidyBlock(token[1], spaceAfterClosingBrace) : token[1];
 | |
|       level1Optimize(token[2], context);
 | |
|       afterRules = true;
 | |
|       break;
 | |
|     case Token.COMMENT:
 | |
|       optimizeComment(token, options);
 | |
|       break;
 | |
|     case Token.RULE:
 | |
|       token[1] = levelOptions.tidySelectors
 | |
|         ? tidyRules(token[1], !ie7Hack, adjacentSpace, format, context.warnings)
 | |
|         : token[1];
 | |
|       token[1] = token[1].length > 1 ? sortSelectors(token[1], levelOptions.selectorsSortingMethod) : token[1];
 | |
|       optimizeBody(token[1], token[2], context);
 | |
|       afterRules = true;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (token[0] == Token.COMMENT
 | |
|       && token[1].length === 0
 | |
|       || levelOptions.removeEmpty
 | |
|       && (token[1].length === 0 || (token[2] && token[2].length === 0))) {
 | |
|       tokens.splice(i, 1);
 | |
|       i--;
 | |
|       l--;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (levelOptions.cleanupCharsets && mayHaveCharset) {
 | |
|     cleanupCharsets(tokens);
 | |
|   }
 | |
| 
 | |
|   return tokens;
 | |
| }
 | |
| 
 | |
| module.exports = level1Optimize;
 |