[79a0317] | 1 | var sortSelectors = require('./sort-selectors');
|
---|
| 2 | var tidyRules = require('./tidy-rules');
|
---|
| 3 | var tidyBlock = require('./tidy-block');
|
---|
| 4 | var tidyAtRule = require('./tidy-at-rule');
|
---|
| 5 |
|
---|
| 6 | var Hack = require('../hack');
|
---|
| 7 | var removeUnused = require('../remove-unused');
|
---|
| 8 | var restoreFromOptimizing = require('../restore-from-optimizing');
|
---|
| 9 | var wrapForOptimizing = require('../wrap-for-optimizing').all;
|
---|
| 10 |
|
---|
| 11 | var configuration = require('../configuration');
|
---|
| 12 | var optimizers = require('./value-optimizers');
|
---|
| 13 |
|
---|
| 14 | var OptimizationLevel = require('../../options/optimization-level').OptimizationLevel;
|
---|
| 15 |
|
---|
| 16 | var Token = require('../../tokenizer/token');
|
---|
| 17 | var Marker = require('../../tokenizer/marker');
|
---|
| 18 |
|
---|
| 19 | var formatPosition = require('../../utils/format-position');
|
---|
| 20 |
|
---|
| 21 | var serializeRules = require('../../writer/one-time').rules;
|
---|
| 22 |
|
---|
| 23 | var CHARSET_TOKEN = '@charset';
|
---|
| 24 | var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');
|
---|
| 25 |
|
---|
| 26 | var DEFAULT_ROUNDING_PRECISION = require('../../options/rounding-precision').DEFAULT;
|
---|
| 27 |
|
---|
| 28 | var VARIABLE_PROPERTY_NAME_PATTERN = /^--\S+$/;
|
---|
| 29 | var PROPERTY_NAME_PATTERN = /^(?:-chrome-|-[\w-]+\w|\w[\w-]+\w|\w{1,})$/;
|
---|
| 30 | var IMPORT_PREFIX_PATTERN = /^@import/i;
|
---|
| 31 | var URL_PREFIX_PATTERN = /^url\(/i;
|
---|
| 32 |
|
---|
| 33 | function startsAsUrl(value) {
|
---|
| 34 | return URL_PREFIX_PATTERN.test(value);
|
---|
| 35 | }
|
---|
| 36 |
|
---|
| 37 | function isImport(token) {
|
---|
| 38 | return IMPORT_PREFIX_PATTERN.test(token[1]);
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | function isLegacyFilter(property) {
|
---|
| 42 | var value;
|
---|
| 43 |
|
---|
| 44 | if (property.name == 'filter' || property.name == '-ms-filter') {
|
---|
| 45 | value = property.value[0][1];
|
---|
| 46 |
|
---|
| 47 | return value.indexOf('progid') > -1
|
---|
| 48 | || value.indexOf('alpha') === 0
|
---|
| 49 | || value.indexOf('chroma') === 0;
|
---|
| 50 | }
|
---|
| 51 | return false;
|
---|
| 52 | }
|
---|
| 53 |
|
---|
| 54 | function noop() {}
|
---|
| 55 |
|
---|
| 56 | function noopValueOptimizer(_name, value, _options) { return value; }
|
---|
| 57 |
|
---|
| 58 | function optimizeBody(rule, properties, context) {
|
---|
| 59 | var options = context.options;
|
---|
| 60 | var valueOptimizers;
|
---|
| 61 | var property, name, type, value;
|
---|
| 62 | var propertyToken;
|
---|
| 63 | var propertyOptimizer;
|
---|
| 64 | var serializedRule = serializeRules(rule);
|
---|
| 65 | var _properties = wrapForOptimizing(properties);
|
---|
| 66 | var pluginValueOptimizers = context.options.plugins.level1Value;
|
---|
| 67 | var pluginPropertyOptimizers = context.options.plugins.level1Property;
|
---|
| 68 | var isVariable;
|
---|
| 69 | var i, l;
|
---|
| 70 |
|
---|
| 71 | for (i = 0, l = _properties.length; i < l; i++) {
|
---|
| 72 | var j, k, m, n;
|
---|
| 73 |
|
---|
| 74 | property = _properties[i];
|
---|
| 75 | name = property.name;
|
---|
| 76 | propertyOptimizer = configuration[name] && configuration[name].propertyOptimizer || noop;
|
---|
| 77 | valueOptimizers = configuration[name] && configuration[name].valueOptimizers || [optimizers.whiteSpace];
|
---|
| 78 | isVariable = VARIABLE_PROPERTY_NAME_PATTERN.test(name);
|
---|
| 79 |
|
---|
| 80 | if (isVariable) {
|
---|
| 81 | valueOptimizers = options.variableOptimizers.length > 0
|
---|
| 82 | ? options.variableOptimizers
|
---|
| 83 | : [optimizers.whiteSpace];
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | if (!isVariable && !PROPERTY_NAME_PATTERN.test(name)) {
|
---|
| 87 | propertyToken = property.all[property.position];
|
---|
| 88 | context.warnings.push('Invalid property name \'' + name + '\' at ' + formatPosition(propertyToken[1][2][0]) + '. Ignoring.');
|
---|
| 89 | property.unused = true;
|
---|
| 90 | continue;
|
---|
| 91 | }
|
---|
| 92 |
|
---|
| 93 | if (property.value.length === 0) {
|
---|
| 94 | propertyToken = property.all[property.position];
|
---|
| 95 | context.warnings.push('Empty property \'' + name + '\' at ' + formatPosition(propertyToken[1][2][0]) + '. Ignoring.');
|
---|
| 96 | property.unused = true;
|
---|
| 97 | continue;
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | if (property.hack && (
|
---|
| 101 | (property.hack[0] == Hack.ASTERISK || property.hack[0] == Hack.UNDERSCORE)
|
---|
| 102 | && !options.compatibility.properties.iePrefixHack
|
---|
| 103 | || property.hack[0] == Hack.BACKSLASH && !options.compatibility.properties.ieSuffixHack
|
---|
| 104 | || property.hack[0] == Hack.BANG && !options.compatibility.properties.ieBangHack)) {
|
---|
| 105 | property.unused = true;
|
---|
| 106 | continue;
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | if (!options.compatibility.properties.ieFilters && isLegacyFilter(property)) {
|
---|
| 110 | property.unused = true;
|
---|
| 111 | continue;
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | if (property.block) {
|
---|
| 115 | optimizeBody(rule, property.value[0][1], context);
|
---|
| 116 | continue;
|
---|
| 117 | }
|
---|
| 118 |
|
---|
| 119 | for (j = 0, m = property.value.length; j < m; j++) {
|
---|
| 120 | type = property.value[j][0];
|
---|
| 121 | value = property.value[j][1];
|
---|
| 122 |
|
---|
| 123 | if (type == Token.PROPERTY_BLOCK) {
|
---|
| 124 | property.unused = true;
|
---|
| 125 | context.warnings.push('Invalid value token at ' + formatPosition(value[0][1][2][0]) + '. Ignoring.');
|
---|
| 126 | break;
|
---|
| 127 | }
|
---|
| 128 |
|
---|
| 129 | if (startsAsUrl(value) && !context.validator.isUrl(value)) {
|
---|
| 130 | property.unused = true;
|
---|
| 131 | context.warnings.push('Broken URL \'' + value + '\' at ' + formatPosition(property.value[j][2][0]) + '. Ignoring.');
|
---|
| 132 | break;
|
---|
| 133 | }
|
---|
| 134 |
|
---|
| 135 | for (k = 0, n = valueOptimizers.length; k < n; k++) {
|
---|
| 136 | value = valueOptimizers[k](name, value, options);
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | for (k = 0, n = pluginValueOptimizers.length; k < n; k++) {
|
---|
| 140 | value = pluginValueOptimizers[k](name, value, options);
|
---|
| 141 | }
|
---|
| 142 |
|
---|
| 143 | property.value[j][1] = value;
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | propertyOptimizer(serializedRule, property, options);
|
---|
| 147 |
|
---|
| 148 | for (j = 0, m = pluginPropertyOptimizers.length; j < m; j++) {
|
---|
| 149 | pluginPropertyOptimizers[j](serializedRule, property, options);
|
---|
| 150 | }
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | restoreFromOptimizing(_properties);
|
---|
| 154 | removeUnused(_properties);
|
---|
| 155 | removeComments(properties, options);
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | function removeComments(tokens, options) {
|
---|
| 159 | var token;
|
---|
| 160 | var i;
|
---|
| 161 |
|
---|
| 162 | for (i = 0; i < tokens.length; i++) {
|
---|
| 163 | token = tokens[i];
|
---|
| 164 |
|
---|
| 165 | if (token[0] != Token.COMMENT) {
|
---|
| 166 | continue;
|
---|
| 167 | }
|
---|
| 168 |
|
---|
| 169 | optimizeComment(token, options);
|
---|
| 170 |
|
---|
| 171 | if (token[1].length === 0) {
|
---|
| 172 | tokens.splice(i, 1);
|
---|
| 173 | i--;
|
---|
| 174 | }
|
---|
| 175 | }
|
---|
| 176 | }
|
---|
| 177 |
|
---|
| 178 | function optimizeComment(token, options) {
|
---|
| 179 | if (token[1][2] == Marker.EXCLAMATION && (options.level[OptimizationLevel.One].specialComments == 'all' || options.commentsKept < options.level[OptimizationLevel.One].specialComments)) {
|
---|
| 180 | options.commentsKept++;
|
---|
| 181 | return;
|
---|
| 182 | }
|
---|
| 183 |
|
---|
| 184 | token[1] = [];
|
---|
| 185 | }
|
---|
| 186 |
|
---|
| 187 | function cleanupCharsets(tokens) {
|
---|
| 188 | var hasCharset = false;
|
---|
| 189 |
|
---|
| 190 | for (var i = 0, l = tokens.length; i < l; i++) {
|
---|
| 191 | var token = tokens[i];
|
---|
| 192 |
|
---|
| 193 | if (token[0] != Token.AT_RULE) { continue; }
|
---|
| 194 |
|
---|
| 195 | if (!CHARSET_REGEXP.test(token[1])) { continue; }
|
---|
| 196 |
|
---|
| 197 | if (hasCharset || token[1].indexOf(CHARSET_TOKEN) == -1) {
|
---|
| 198 | tokens.splice(i, 1);
|
---|
| 199 | i--;
|
---|
| 200 | l--;
|
---|
| 201 | } else {
|
---|
| 202 | hasCharset = true;
|
---|
| 203 | tokens.splice(i, 1);
|
---|
| 204 | tokens.unshift([Token.AT_RULE, token[1].replace(CHARSET_REGEXP, CHARSET_TOKEN)]);
|
---|
| 205 | }
|
---|
| 206 | }
|
---|
| 207 | }
|
---|
| 208 |
|
---|
| 209 | function buildUnitRegexp(options) {
|
---|
| 210 | var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%'];
|
---|
| 211 | var otherUnits = ['ch', 'rem', 'vh', 'vm', 'vmax', 'vmin', 'vw'];
|
---|
| 212 |
|
---|
| 213 | otherUnits.forEach(function(unit) {
|
---|
| 214 | if (options.compatibility.units[unit]) {
|
---|
| 215 | units.push(unit);
|
---|
| 216 | }
|
---|
| 217 | });
|
---|
| 218 |
|
---|
| 219 | return new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')(\\W|$)', 'g');
|
---|
| 220 | }
|
---|
| 221 |
|
---|
| 222 | function buildPrecisionOptions(roundingPrecision) {
|
---|
| 223 | var precisionOptions = {
|
---|
| 224 | matcher: null,
|
---|
| 225 | units: {}
|
---|
| 226 | };
|
---|
| 227 | var optimizable = [];
|
---|
| 228 | var unit;
|
---|
| 229 | var value;
|
---|
| 230 |
|
---|
| 231 | for (unit in roundingPrecision) {
|
---|
| 232 | value = roundingPrecision[unit];
|
---|
| 233 |
|
---|
| 234 | if (value != DEFAULT_ROUNDING_PRECISION) {
|
---|
| 235 | precisionOptions.units[unit] = {};
|
---|
| 236 | precisionOptions.units[unit].value = value;
|
---|
| 237 | precisionOptions.units[unit].multiplier = 10 ** value;
|
---|
| 238 |
|
---|
| 239 | optimizable.push(unit);
|
---|
| 240 | }
|
---|
| 241 | }
|
---|
| 242 |
|
---|
| 243 | if (optimizable.length > 0) {
|
---|
| 244 | precisionOptions.enabled = true;
|
---|
| 245 | precisionOptions.decimalPointMatcher = new RegExp('(\\d)\\.($|' + optimizable.join('|') + ')($|\\W)', 'g');
|
---|
| 246 | precisionOptions.zeroMatcher = new RegExp('(\\d*)(\\.\\d+)(' + optimizable.join('|') + ')', 'g');
|
---|
| 247 | }
|
---|
| 248 |
|
---|
| 249 | return precisionOptions;
|
---|
| 250 | }
|
---|
| 251 |
|
---|
| 252 | function buildVariableOptimizers(options) {
|
---|
| 253 | return options.level[OptimizationLevel.One].variableValueOptimizers.map(function(optimizer) {
|
---|
| 254 | if (typeof (optimizer) == 'string') {
|
---|
| 255 | return optimizers[optimizer] || noopValueOptimizer;
|
---|
| 256 | }
|
---|
| 257 |
|
---|
| 258 | return optimizer;
|
---|
| 259 | });
|
---|
| 260 | }
|
---|
| 261 |
|
---|
| 262 | function level1Optimize(tokens, context) {
|
---|
| 263 | var options = context.options;
|
---|
| 264 | var levelOptions = options.level[OptimizationLevel.One];
|
---|
| 265 | var ie7Hack = options.compatibility.selectors.ie7Hack;
|
---|
| 266 | var adjacentSpace = options.compatibility.selectors.adjacentSpace;
|
---|
| 267 | var spaceAfterClosingBrace = options.compatibility.properties.spaceAfterClosingBrace;
|
---|
| 268 | var format = options.format;
|
---|
| 269 | var mayHaveCharset = false;
|
---|
| 270 | var afterRules = false;
|
---|
| 271 |
|
---|
| 272 | options.unitsRegexp = options.unitsRegexp || buildUnitRegexp(options);
|
---|
| 273 | options.precision = options.precision || buildPrecisionOptions(levelOptions.roundingPrecision);
|
---|
| 274 | options.commentsKept = options.commentsKept || 0;
|
---|
| 275 | options.variableOptimizers = options.variableOptimizers || buildVariableOptimizers(options);
|
---|
| 276 |
|
---|
| 277 | for (var i = 0, l = tokens.length; i < l; i++) {
|
---|
| 278 | var token = tokens[i];
|
---|
| 279 |
|
---|
| 280 | switch (token[0]) {
|
---|
| 281 | case Token.AT_RULE:
|
---|
| 282 | token[1] = isImport(token) && afterRules ? '' : token[1];
|
---|
| 283 | token[1] = levelOptions.tidyAtRules ? tidyAtRule(token[1]) : token[1];
|
---|
| 284 | mayHaveCharset = true;
|
---|
| 285 | break;
|
---|
| 286 | case Token.AT_RULE_BLOCK:
|
---|
| 287 | optimizeBody(token[1], token[2], context);
|
---|
| 288 | afterRules = true;
|
---|
| 289 | break;
|
---|
| 290 | case Token.NESTED_BLOCK:
|
---|
| 291 | token[1] = levelOptions.tidyBlockScopes ? tidyBlock(token[1], spaceAfterClosingBrace) : token[1];
|
---|
| 292 | level1Optimize(token[2], context);
|
---|
| 293 | afterRules = true;
|
---|
| 294 | break;
|
---|
| 295 | case Token.COMMENT:
|
---|
| 296 | optimizeComment(token, options);
|
---|
| 297 | break;
|
---|
| 298 | case Token.RULE:
|
---|
| 299 | token[1] = levelOptions.tidySelectors
|
---|
| 300 | ? tidyRules(token[1], !ie7Hack, adjacentSpace, format, context.warnings)
|
---|
| 301 | : token[1];
|
---|
| 302 | token[1] = token[1].length > 1 ? sortSelectors(token[1], levelOptions.selectorsSortingMethod) : token[1];
|
---|
| 303 | optimizeBody(token[1], token[2], context);
|
---|
| 304 | afterRules = true;
|
---|
| 305 | break;
|
---|
| 306 | }
|
---|
| 307 |
|
---|
| 308 | if (token[0] == Token.COMMENT
|
---|
| 309 | && token[1].length === 0
|
---|
| 310 | || levelOptions.removeEmpty
|
---|
| 311 | && (token[1].length === 0 || (token[2] && token[2].length === 0))) {
|
---|
| 312 | tokens.splice(i, 1);
|
---|
| 313 | i--;
|
---|
| 314 | l--;
|
---|
| 315 | }
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | if (levelOptions.cleanupCharsets && mayHaveCharset) {
|
---|
| 319 | cleanupCharsets(tokens);
|
---|
| 320 | }
|
---|
| 321 |
|
---|
| 322 | return tokens;
|
---|
| 323 | }
|
---|
| 324 |
|
---|
| 325 | module.exports = level1Optimize;
|
---|