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;