Skip to content

Instantly share code, notes, and snippets.

@danielgindi
Last active July 12, 2017 10:12
Show Gist options
  • Save danielgindi/4b67ea1ee0c69a8adcb47dc0cd4d66a2 to your computer and use it in GitHub Desktop.
Save danielgindi/4b67ea1ee0c69a8adcb47dc0cd4d66a2 to your computer and use it in GitHub Desktop.
CSS minifier (JS)
var CssMinifier = (function () {
function unescapeString(value) {
// CSS RFC states that the escape character (\) escapes every character to itself,
// except for a variable length hex-digit sequence that is converted to a character by that hex value.
// An escaped hex value can be ended with an optional whitespace ( \t\n).
var out = '';
var quoted = false;
var hexEscapeSeq = '';
for (var i = 0, len = value.length; i < len; i++) {
var c = value[i];
if (quoted) {
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
hexEscapeSeq += c;
} else if (hexEscapeSeq) {
out += String.fromCharCode(parseInt(hexEscapeSeq, 16));
hexEscapeSeq = '';
// Avoid consuming a non-whitespace character
if (c !== ' ' && c !== '\t' && c !== '\n') {
i--;
}
quoted = false;
} else {
out += c;
quoted = false;
}
} else {
if (c === '\\') {
quoted = true;
} else {
out += c;
}
}
}
if (hexEscapeSeq) {
out += String.fromCharCode(parseInt(hexEscapeSeq, 10));
}
return out;
}
function processComment(css, keepImportantComments) {
if (keepImportantComments && css[2] === '!') {
if (css[1] === '/') {
css = '/*' + css.substr(2).replace(/\*\//g, '* /').replace(/\/\*/g, '/ *') + '*/';
}
return css;
}
return '';
}
function processCss(css) {
return css
// multiline spacing
.replace(/[\n\r]+\s*/g, '')
// whitespace at any length, so we can turn it into a single space
.replace(/\s+/g, ' ')
// all spaces around meta chars/operators (excluding + and -)
.replace(/\s*([*$~^|]?=|[{};,>~]|!important\b)\s*/g, '$1')
// spaces right of ( [ :
.replace(/([[(:])\s+/g, '$1')
// spaces left of ) ]
.replace(/\s+([\])])/g, '$1')
// spaces left (and right) of : (but not in selectors!)
.replace(/\s+(:)(?![^}]*{)/g, '$1')
// spaces around + and - (in selectors only!)
.replace(/\s*([+-])\s*(?=[^}]*{)/g, '$1')
// extra semicolons at the end of blocks
.replace(/;}/g, '}')
// empty tags
.replace(/(^|}|;)[^{};]+{\s*}/g, '$1');
}
function processUrl(url, urlReplacer) {
if (url[0] === '"' || url[0] === '\'') {
url = url.substr(1, url.length - 2);
}
if (urlReplacer) {
url = urlReplacer(url);
}
return 'url("' + unescapeString(url).replace(/"/g, '\\"') + '")';
}
function processString(value) {
return '"' + unescapeString(value.substr(1, value.length - 2)).replace(/"/g, '\\"') + '"';
}
/** Minifiy a CSS stylesheet
* @param {String} css - CSS content
* @param {Object} [options={}] - Options
* @param {Boolean} [options.keepImportantComments=true] - Keep important comments?
* @param {Function} [options.urlReplacer=null] - A callback to replace urls (if changing folders etc.)
*/
function minifyCss(css, options) {
options = options || Object.assign({}, { keepImportantComments: true }, options);
// Matches urls, strings (not spanning to lines), and comments (multiline and singleline), taking escaping into account
let stringRegex = /url\(\s*("(?:[^"\\]|\\.)*?"|'(?:[^'\\]|\\.)*?'|(?!\s*["'])(?:[^)\\]|\\.)*?)\s*\)|(@import[\s]*)?("(?:[^"\\]|\\.)*?"|'(?:[^'\\]|\\.)*?')|(\/\*[\d\D]*?\*\/|\/\/.*$)/gim;
let lastIndex = 0;
let match;
let minified = '';
while (match = stringRegex.exec(css)) {
if (match.index > lastIndex) {
minified += processCss(css.substr(lastIndex, match.index - lastIndex);
}
if (match[1]) {
minified += processUrl(match[1], options.urlReplacer);
} else if (match[2]) {
minified += '@import ' + processUrl(match[3], options.urlReplacer);
} else if (match[3]) {
minified += processString(match[3]);
} else /*if (match[4])*/ {
minified += processComment(match[4], options.keepImportantComments);
}
lastIndex = match.index + match[0].length;
}
if (lastIndex < css.length) {
minified += processCss(css.substr(lastIndex));
}
return minified.trim();
}
return minifyCss;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment