backoffice/node_modules/clean-css/lib/clean.js

438 lines
13 KiB
JavaScript

/**
* Clean-css - https://github.com/GoalSmashers/clean-css
* Released under the terms of MIT license
*
* Copyright (C) 2011-2014 GoalSmashers.com
*/
var ColorShortener = require('./colors/shortener');
var ColorHSLToHex = require('./colors/hsl-to-hex');
var ColorRGBToHex = require('./colors/rgb-to-hex');
var ColorLongToShortHex = require('./colors/long-to-short-hex');
var ImportInliner = require('./imports/inliner');
var UrlRebase = require('./images/url-rebase');
var EmptyRemoval = require('./selectors/empty-removal');
var CommentsProcessor = require('./text/comments');
var ExpressionsProcessor = require('./text/expressions');
var FreeTextProcessor = require('./text/free');
var UrlsProcessor = require('./text/urls');
var NameQuotesProcessor = require('./text/name-quotes');
var Splitter = require('./text/splitter');
var SelectorsOptimizer = require('./selectors/optimizer');
var CleanCSS = module.exports = function CleanCSS(options) {
options = options || {};
// back compat
if (!(this instanceof CleanCSS))
return new CleanCSS(options);
options.keepBreaks = options.keepBreaks || false;
//active by default
if (undefined === options.processImport)
options.processImport = true;
this.options = options;
this.stats = {};
this.context = {
errors: [],
warnings: [],
debug: options.debug
};
this.errors = this.context.errors;
this.warnings = this.context.warnings;
this.lineBreak = process.platform == 'win32' ? '\r\n' : '\n';
};
CleanCSS.prototype.minify = function(data, callback) {
var options = this.options;
if (Buffer.isBuffer(data))
data = data.toString();
if (options.processImport || data.indexOf('@shallow') > 0) {
// inline all imports
var self = this;
var runner = callback ?
process.nextTick :
function(callback) { return callback(); };
return runner(function() {
return new ImportInliner(self.context, options.inliner).process(data, {
localOnly: !callback,
root: options.root || process.cwd(),
relativeTo: options.relativeTo,
whenDone: function(data) {
return minify.call(self, data, callback);
}
});
});
} else {
return minify.call(this, data, callback);
}
};
var minify = function(data, callback) {
var startedAt;
var stats = this.stats;
var options = this.options;
var context = this.context;
var lineBreak = this.lineBreak;
var commentsProcessor = new CommentsProcessor(
context,
'keepSpecialComments' in options ? options.keepSpecialComments : '*',
options.keepBreaks,
lineBreak
);
var expressionsProcessor = new ExpressionsProcessor();
var freeTextProcessor = new FreeTextProcessor();
var urlsProcessor = new UrlsProcessor(context);
var nameQuotesProcessor = new NameQuotesProcessor();
if (options.debug) {
this.startedAt = process.hrtime();
this.stats.originalSize = data.length;
}
var replace = function() {
if (typeof arguments[0] == 'function')
arguments[0]();
else
data = data.replace.apply(data, arguments);
};
// replace function
if (options.benchmark) {
var originalReplace = replace;
replace = function(pattern, replacement) {
var name = typeof pattern == 'function' ?
/function (\w+)\(/.exec(pattern.toString())[1] :
pattern;
var start = process.hrtime();
originalReplace(pattern, replacement);
var itTook = process.hrtime(start);
console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000);
};
}
if (options.debug) {
startedAt = process.hrtime();
stats.originalSize = data.length;
}
replace(function escapeComments() {
data = commentsProcessor.escape(data);
});
// replace all escaped line breaks
replace(/\\(\r\n|\n)/gm, '');
// strip parentheses in urls if possible (no spaces inside)
replace(/url\((['"])([^\)]+)['"]\)/g, function(match, quote, url) {
var unsafeDataURI = url.indexOf('data:') === 0 && url.match(/data:\w+\/[^;]+;base64,/) === null;
if (url.match(/[ \t]/g) !== null || unsafeDataURI)
return 'url(' + quote + url + quote + ')';
else
return 'url(' + url + ')';
});
// strip parentheses in animation & font names
replace(function removeQuotes() {
data = nameQuotesProcessor.process(data);
});
// strip parentheses in @keyframes
replace(/@(\-moz\-|\-o\-|\-webkit\-)?keyframes ([^{]+)/g, function(match, prefix, name) {
prefix = prefix || '';
return '@' + prefix + 'keyframes ' + (name.indexOf(' ') > -1 ? name : name.replace(/['"]/g, ''));
});
// IE shorter filters, but only if single (IE 7 issue)
replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\([^\)]+\))([;}'"])/g, function(match, filter, args, suffix) {
return filter.toLowerCase() + args + suffix;
});
replace(function escapeExpressions() {
data = expressionsProcessor.escape(data);
});
// strip parentheses in attribute values
replace(/\[([^\]]+)\]/g, function(match, content) {
var eqIndex = content.indexOf('=');
var singleQuoteIndex = content.indexOf('\'');
var doubleQuoteIndex = content.indexOf('"');
if (eqIndex < 0 && singleQuoteIndex < 0 && doubleQuoteIndex < 0)
return match;
if (singleQuoteIndex === 0 || doubleQuoteIndex === 0)
return match;
var key = content.substring(0, eqIndex);
var value = content.substring(eqIndex + 1, content.length);
if (/^['"](?:[a-zA-Z][a-zA-Z\d\-_]+)['"]$/.test(value))
return '[' + key + '=' + value.substring(1, value.length - 1) + ']';
else
return match;
});
replace(function escapeFreeText() {
data = freeTextProcessor.escape(data);
});
replace(function escapeUrls() {
data = urlsProcessor.escape(data);
});
// remove invalid special declarations
replace(/@charset [^;]+;/ig, function (match) {
return match.indexOf('@charset') > -1 ? match : '';
});
// whitespace inside attribute selectors brackets
replace(/\[([^\]]+)\]/g, function(match) {
return match.replace(/\s/g, '');
});
// line breaks
replace(/[\r]?\n/g, ' ');
// multiple whitespace
replace(/[\t ]+/g, ' ');
// multiple semicolons (with optional whitespace)
replace(/;[ ]?;+/g, ';');
// multiple line breaks to one
replace(/ (?:\r\n|\n)/g, lineBreak);
replace(/(?:\r\n|\n)+/g, lineBreak);
// remove spaces around selectors
replace(/ ([+~>]) /g, '$1');
// remove extra spaces inside content
replace(/([!\(\{\}:;=,\n]) /g, '$1');
replace(/ ([!\)\{\};=,\n])/g, '$1');
replace(/(?:\r\n|\n)\}/g, '}');
replace(/([\{;,])(?:\r\n|\n)/g, '$1');
replace(/ :([^\{\};]+)([;}])/g, ':$1$2');
// restore spaces inside IE filters (IE 7 issue)
replace(/progid:[^(]+\(([^\)]+)/g, function(match) {
return match.replace(/,/g, ', ');
});
// trailing semicolons
replace(/;\}/g, '}');
replace(function hsl2Hex() {
data = new ColorHSLToHex(data).process();
});
replace(function rgb2Hex() {
data = new ColorRGBToHex(data).process();
});
replace(function longToShortHex() {
data = new ColorLongToShortHex(data).process();
});
replace(function shortenColors() {
data = new ColorShortener(data).process();
});
// replace font weight with numerical value
replace(/(font\-weight|font):(normal|bold)([ ;\}!])(\w*)/g, function(match, property, weight, suffix, next) {
if (suffix == ' ' && (next.indexOf('/') > -1 || next == 'normal' || /[1-9]00/.test(next)))
return match;
if (weight == 'normal')
return property + ':400' + suffix + next;
else if (weight == 'bold')
return property + ':700' + suffix + next;
else
return match;
});
// minus zero to zero
// repeated twice on purpose as if not it doesn't process rgba(-0,-0,-0,-0) correctly
var zerosRegexp = /(\s|:|,|\()\-0([^\.])/g;
replace(zerosRegexp, '$10$2');
replace(zerosRegexp, '$10$2');
// zero(s) + value to value
replace(/(\s|:|,)0+([1-9])/g, '$1$2');
// round pixels to 2nd decimal place
var precision = 'roundingPrecision' in options ? options.roundingPrecision : 2;
var decimalMultiplier = Math.pow(10, precision);
replace(new RegExp('(\\d*\\.\\d{' + (precision + 1) + ',})px', 'g'), function(match, number) {
return Math.round(parseFloat(number) * decimalMultiplier) / decimalMultiplier + 'px';
});
// .0 to 0
// repeated twice on purpose as if not it doesn't process {padding: .0 .0 .0 .0} correctly
var leadingDecimalRegexp = /(\D)\.0+(\D)/g;
replace(leadingDecimalRegexp, '$10$2');
replace(leadingDecimalRegexp, '$10$2');
// fraction zeros removal
replace(/\.([1-9]*)0+(\D)/g, function(match, nonZeroPart, suffix) {
return (nonZeroPart.length > 0 ? '.' : '') + nonZeroPart + suffix;
});
// zero + unit to zero
var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%'];
if (['ie7', 'ie8'].indexOf(options.compatibility) == -1)
units.push('rem');
replace(new RegExp('(\\s|:|,)\\-?0(?:' + units.join('|') + ')', 'g'), '$1' + '0');
replace(new RegExp('(\\s|:|,)\\-?(\\d+)\\.(\\D)', 'g'), '$1$2$3');
replace(new RegExp('rect\\(0(?:' + units.join('|') + ')', 'g'), 'rect(0');
// restore % in rgb/rgba and hsl/hsla
replace(/(rgb|rgba|hsl|hsla)\(([^\)]+)\)/g, function(match, colorFunction, colorDef) {
var tokens = colorDef.split(',');
var applies = colorFunction == 'hsl' || colorFunction == 'hsla' || tokens[0].indexOf('%') > -1;
if (!applies)
return match;
if (tokens[1].indexOf('%') == -1)
tokens[1] += '%';
if (tokens[2].indexOf('%') == -1)
tokens[2] += '%';
return colorFunction + '(' + tokens.join(',') + ')';
});
// transparent rgba/hsla to 'transparent' unless in compatibility mode
if (!options.compatibility) {
replace(/:([^;]*)(?:rgba|hsla)\(0,0%?,0%?,0\)/g, function (match, prefix) {
if (new Splitter(',').split(match).pop().indexOf('gradient(') > -1)
return match;
return ':' + prefix + 'transparent';
});
}
// none to 0
replace(/outline:none/g, 'outline:0');
// background:none to background:0 0
replace(/background:(?:none|transparent)([;}])/g, 'background:0 0$1');
// multiple zeros into one
replace(/box-shadow:0 0 0 0([^\.])/g, 'box-shadow:0 0$1');
replace(/:0 0 0 0([^\.])/g, ':0$1');
replace(/([: ,=\-])0\.(\d)/g, '$1.$2');
// restore rect(...) zeros syntax for 4 zeros
replace(/rect\(\s?0(\s|,)0[ ,]0[ ,]0\s?\)/g, 'rect(0$10$10$10)');
// remove universal selector when not needed (*#id, *.class etc)
// pending a better fix
if (options.compatibility != 'ie7') {
replace(/([^,]?)(\*[^ \+\{]*\+html[^\{]*)(\{[^\}]*\})/g, function (match, prefix, selector, body) {
var notHackedSelectors = new Splitter(',').split(selector).filter(function (m) {
return !/^\*[^ \+\{]*\+html/.test(m);
});
return notHackedSelectors.length > 0 ?
prefix + notHackedSelectors.join(',') + body :
prefix;
});
replace(/\*([\.#:\[])/g, '$1');
}
// Restore spaces inside calc back
replace(/calc\([^\}]+\}/g, function(match) {
return match.replace(/\+/g, ' + ');
});
// get rid of IE hacks if not in compatibility mode
if (!options.compatibility)
replace(/([;\{])[\*_][\w\-]+:[^;\}]+/g, '$1');
if (options.noAdvanced) {
if (options.keepBreaks)
replace(/\}/g, '}' + lineBreak);
} else {
replace(function optimizeSelectors() {
data = new SelectorsOptimizer(data, context, {
keepBreaks: options.keepBreaks,
lineBreak: lineBreak,
compatibility: options.compatibility,
aggressiveMerging: !options.noAggressiveMerging
}).process();
});
}
// replace ' / ' in border-*-radius with '/'
replace(/(border-\w+-\w+-radius:\S+)\s+\/\s+/g, '$1/');
// replace same H/V values in border-radius
replace(/(border-\w+-\w+-radius):([^;\}]+)/g, function (match, property, value) {
var parts = value.split('/');
if (parts.length > 1 && parts[0] == parts[1])
return property + ':' + parts[0];
else
return match;
});
replace(function restoreUrls() {
data = urlsProcessor.restore(data);
});
replace(function rebaseUrls() {
data = options.noRebase ? data : new UrlRebase(options, context).process(data);
});
replace(function restoreFreeText() {
data = freeTextProcessor.restore(data);
});
replace(function restoreComments() {
data = commentsProcessor.restore(data);
});
replace(function restoreExpressions() {
data = expressionsProcessor.restore(data);
});
// move first charset to the beginning
replace(function moveCharset() {
// get first charset in stylesheet
var match = data.match(/@charset [^;]+;/);
var firstCharset = match ? match[0] : null;
if (!firstCharset)
return;
// reattach first charset and remove all subsequent
data = firstCharset +
(options.keepBreaks ? lineBreak : '') +
data.replace(new RegExp('@charset [^;]+;(' + lineBreak + ')?', 'g'), '').trim();
});
if (options.noAdvanced) {
replace(function removeEmptySelectors() {
data = new EmptyRemoval(data).process();
});
}
// trim spaces at beginning and end
data = data.trim();
if (options.debug) {
var elapsed = process.hrtime(startedAt);
stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
stats.efficiency = 1 - data.length / stats.originalSize;
stats.minifiedSize = data.length;
}
return callback ?
callback.call(this, this.context.errors.length > 0 ? this.context.errors : null, data) :
data;
};