123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 |
- /**
- * UglifyCSS
- * Port of YUI CSS Compressor to NodeJS
- * Author: Franck Marcia - https://github.com/fmarcia
- * MIT licenced
- */
-
- /**
- * cssmin.js
- * Author: Stoyan Stefanov - http://phpied.com/
- * This is a JavaScript port of the CSS minification tool
- * distributed with YUICompressor, itself a port
- * of the cssmin utility by Isaac Schlueter - http://foohack.com/
- * Permission is hereby granted to use the JavaScript version under the same
- * conditions as the YUICompressor (original YUICompressor note below).
- */
-
- /**
- * YUI Compressor
- * http://developer.yahoo.com/yui/compressor/
- * Author: Julien Lecomte - http://www.julienlecomte.net/
- * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
- * The copyrights embodied in the content of this file are licensed
- * by Yahoo! Inc. under the BSD (revised) open source license.
- */
-
- 'use strict';
-
- var fs = require('fs');
- var path = require('path');
-
- var SEP = "/";
- var PATH_SEP = path.sep;
- var ___PRESERVED_TOKEN_ = "___PRESERVED_TOKEN_";
-
- var defaultOptions = {
- maxLineLen: 0,
- expandVars: false,
- uglyComments: false,
- cuteComments: false,
- convertUrls: "",
- debug: false
- };
-
- /**
- * Utility method to convert relative urls and replace them with tokens before
- * we start compressing. It must be called *after* extractDataUrls
- *
- * @private
- * @function convertRelativeUrls
- * @param {String} css The input css
- * @param {Object} options Options
- * @param {Array} The global array of tokens to preserve
- * @returns String The processed css
- */
- function convertRelativeUrls(css, options, preservedTokens) {
-
- var maxIndex = css.length - 1,
- appendIndex = 0,
- startIndex,
- endIndex,
- terminator,
- foundTerminator,
- sb = [],
- m,
- preserver,
- token,
- url,
- file,
- target,
- pattern = /(url\s*\()\s*(["']?)/g;
-
- // Since we need to account for non-base64 data urls, we need to handle
- // ' and ) being part of the data string. Hence switching to indexOf,
- // to determine whether or not we have matching string terminators and
- // handling sb appends directly, instead of using matcher.append* methods.
-
- while ((m = pattern.exec(css)) !== null) {
-
- startIndex = m.index + m[1].length; // "url(".length()
- terminator = m[2]; // ', " or empty (not quoted)
-
- if (terminator.length === 0) {
- terminator = ")";
- }
-
- foundTerminator = false;
-
- endIndex = pattern.lastIndex - 1;
-
- while(foundTerminator === false && endIndex+1 <= maxIndex) {
- endIndex = css.indexOf(terminator, endIndex + 1);
-
- // endIndex == 0 doesn't really apply here
- if ((endIndex > 0) && (css.charAt(endIndex - 1) !== '\\')) {
- foundTerminator = true;
- if (")" != terminator) {
- endIndex = css.indexOf(")", endIndex);
- }
- }
- }
-
- // Enough searching, start moving stuff over to the buffer
- sb.push(css.substring(appendIndex, m.index));
-
- if (foundTerminator) {
-
- token = css.substring(startIndex, endIndex).replace(/(^\s*|\s*$)/g, "");
- if (token.slice(0, 19) !== ___PRESERVED_TOKEN_) {
-
- if (terminator === "'" || terminator === '"') {
- token = token.slice(1, -1);
- } else if (terminator === ")") {
- terminator = "";
- }
-
- if (options.convertUrls && token.charAt(0) !== SEP && token.slice(0, 7) !== "http://" && token.slice(0, 8) !== "https://") {
-
- // build path of detected urls
- target = options.target.slice();
- token = token.split(SEP).join(PATH_SEP); // assuming urls in css use "/"
- url = path.resolve(options.source.join(PATH_SEP), token).split(PATH_SEP);
-
- file = url.pop();
-
- // remove common part of both paths
- while (target[0] === url[0]) {
- target.shift();
- url.shift();
- }
-
- for (var i = 0, l = target.length; i < l; ++i) {
- target[i] = "..";
- }
- url = terminator + target.concat(url, file).join(SEP) + terminator;
-
- } else {
- url = terminator + token + terminator;
- }
-
- preservedTokens.push(url);
-
- preserver = "url(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___)";
- sb.push(preserver);
-
- } else {
- sb.push("url(" + token + ")");
- }
-
- appendIndex = endIndex + 1;
-
- } else {
- // No end terminator found, re-add the whole match. Should we throw/warn here?
- sb.push(css.substring(m.index, pattern.lastIndex));
- appendIndex = pattern.lastIndex;
- }
- }
-
- sb.push(css.substring(appendIndex));
-
- return sb.join("");
- }
-
- /**
- * Utility method to replace all data urls with tokens before we start
- * compressing, to avoid performance issues running some of the subsequent
- * regexes against large strings chunks.
- *
- * @private
- * @function extractDataUrls
- * @param {String} css The input css
- * @param {Array} The global array of tokens to preserve
- * @returns String The processed css
- */
- function extractDataUrls(css, preservedTokens) {
-
- // Leave data urls alone to increase parse performance.
- var maxIndex = css.length - 1,
- appendIndex = 0,
- startIndex,
- endIndex,
- terminator,
- foundTerminator,
- sb = [],
- m,
- preserver,
- token,
- pattern = /url\(\s*(["']?)data\:/g;
-
- // Since we need to account for non-base64 data urls, we need to handle
- // ' and ) being part of the data string. Hence switching to indexOf,
- // to determine whether or not we have matching string terminators and
- // handling sb appends directly, instead of using matcher.append* methods.
-
- while ((m = pattern.exec(css)) !== null) {
-
- startIndex = m.index + 4; // "url(".length()
- terminator = m[1]; // ', " or empty (not quoted)
-
- if (terminator.length === 0) {
- terminator = ")";
- }
-
- foundTerminator = false;
-
- endIndex = pattern.lastIndex - 1;
-
- while(foundTerminator === false && endIndex+1 <= maxIndex) {
- endIndex = css.indexOf(terminator, endIndex + 1);
-
- // endIndex == 0 doesn't really apply here
- if ((endIndex > 0) && (css.charAt(endIndex - 1) !== '\\')) {
- foundTerminator = true;
- if (")" != terminator) {
- endIndex = css.indexOf(")", endIndex);
- }
- }
- }
-
- // Enough searching, start moving stuff over to the buffer
- sb.push(css.substring(appendIndex, m.index));
-
- if (foundTerminator) {
-
- token = css.substring(startIndex, endIndex);
- var parts = token.split(",");
- if (parts.length > 1 && parts[0].slice(-7) == ";base64") {
- token = token.replace(/\s+/g, "");
- } else {
- token = token.replace(/\n/g, " ");
- token = token.replace(/\s+/g, " ");
- token = token.replace(/(^\s+|\s+$)/g, "");
- }
-
- preservedTokens.push(token);
-
- preserver = "url(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___)";
- sb.push(preserver);
-
- appendIndex = endIndex + 1;
- } else {
- // No end terminator found, re-add the whole match. Should we throw/warn here?
- sb.push(css.substring(m.index, pattern.lastIndex));
- appendIndex = pattern.lastIndex;
- }
- }
-
- sb.push(css.substring(appendIndex));
-
- return sb.join("");
- }
-
- /**
- * Utility method to compress hex color values of the form #AABBCC to #ABC.
- *
- * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
- * e.g. #AddressForm { ... }
- *
- * DOES NOT compress IE filters, which have hex color values (which would break things).
- * e.g. filter: chroma(color="#FFFFFF");
- *
- * DOES NOT compress invalid hex values.
- * e.g. background-color: #aabbccdd
- *
- * @private
- * @function compressHexColors
- * @param {String} css The input css
- * @returns String The processed css
- */
- function compressHexColors(css) {
-
- // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
- var pattern = /(\=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/gi,
- m,
- index = 0,
- isFilter,
- sb = [];
-
- while ((m = pattern.exec(css)) !== null) {
-
- sb.push(css.substring(index, m.index));
-
- isFilter = m[1];
-
- if (isFilter) {
- // Restore, maintain case, otherwise filter will break
- sb.push(m[1] + "#" + (m[2] + m[3] + m[4] + m[5] + m[6] + m[7]));
- } else {
- if (m[2].toLowerCase() == m[3].toLowerCase() &&
- m[4].toLowerCase() == m[5].toLowerCase() &&
- m[6].toLowerCase() == m[7].toLowerCase()) {
-
- // Compress.
- sb.push("#" + (m[3] + m[5] + m[7]).toLowerCase());
- } else {
- // Non compressible color, restore but lower case.
- sb.push("#" + (m[2] + m[3] + m[4] + m[5] + m[6] + m[7]).toLowerCase());
- }
- }
-
- index = pattern.lastIndex = pattern.lastIndex - m[8].length;
- }
-
- sb.push(css.substring(index));
-
- return sb.join("");
- }
-
- // Preserve 0 followed by unit in keyframes steps
-
- function keyframes(content, preservedTokens) {
-
- var level,
- buffer,
- buffers,
- pattern = /@[a-z0-9-_]*keyframes\s+[a-z0-9-_]+\s*{/gi,
- index = 0,
- len,
- c,
- startIndex;
-
- var preserve = function (part, index) {
- part = part.replace(/(^\s|\s$)/g, '');
- if (part.charAt(0) === '0') {
- preservedTokens.push(part);
- buffer[index] = ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
- }
- };
-
- while (true) {
-
- level = 0;
- buffer = '';
-
- startIndex = content.slice(index).search(pattern);
- if (startIndex < 0) {
- break;
- }
-
- index += startIndex;
- startIndex = index;
- len = content.length;
- buffers = [];
-
- for (; index < len; ++index) {
-
- c = content.charAt(index);
-
- if (c === '{') {
-
- if (level === 0) {
- buffers.push(buffer.replace(/(^\s|\s$)/g, ''));
-
- } else if (level === 1) {
-
- buffer = buffer.split(',');
-
- buffer.forEach(preserve);
-
- buffers.push(buffer.join(',').replace(/(^\s|\s$)/g, ''));
- }
-
- buffer = '';
- level += 1;
-
- } else if (c === '}') {
-
- if (level === 2) {
- buffers.push('{' + buffer.replace(/(^\s|\s$)/g, '') + '}');
- buffer = '';
-
- } else if (level === 1) {
- content = content.slice(0, startIndex) +
- buffers.shift() + '{' +
- buffers.join('') +
- content.slice(index);
- break;
- }
-
- level -= 1;
- }
-
- if (level < 0) {
- break;
-
- } else if (c !== '{' && c !== '}') {
- buffer += c;
- }
- }
- }
-
- return content;
- }
-
- // Collect all comment blocks and return new content with comment placeholders
- // (comments is an array thus passed by reference)
-
- function collectComments(content, comments) {
- var table = [];
- var from = 0;
- var start, end;
- while (true) {
- start = content.indexOf("/*", from);
- if (start > -1) {
- end = content.indexOf("*/", start + 2);
- if (end > -1) {
- comments.push(content.slice(start + 2, end));
- table.push(content.slice(from, start));
- table.push("/*___PRESERVE_CANDIDATE_COMMENT_" + (comments.length - 1) + "___*/");
- from = end + 2;
- } else {
- // unterminated comment
- end = -2;
- break;
- }
- } else {
- break;
- }
- }
- table.push(content.slice(end + 2));
- return table.join("");
- }
-
- // Uglify a CSS string
-
- function processString(content, options) {
-
- var startIndex,
- comments = [],
- preservedTokens = [],
- token,
- len = content.length,
- pattern,
- quote,
- rgbcolors,
- hexcolor,
- placeholder,
- val,
- i,
- c,
- line = [],
- lines = [],
- vars = {};
-
- options = options || defaultOptions;
- content = extractDataUrls(content, preservedTokens);
- content = convertRelativeUrls(content, options, preservedTokens);
- content = collectComments(content, comments);
-
- // preserve strings so their content doesn't get accidentally minified
- pattern = /("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g;
- content = content.replace(pattern, function (token) {
- quote = token.substring(0, 1);
- token = token.slice(1, -1);
- // maybe the string contains a comment-like substring or more? put'em back then
- if (token.indexOf("___PRESERVE_CANDIDATE_COMMENT_") >= 0) {
- for (i = 0, len = comments.length; i < len; i += 1) {
- token = token.replace("___PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[i]);
- }
- }
- // minify alpha opacity in filter strings
- token = token.replace(/progid:DXImageTransform.Microsoft.Alpha\(Opacity=/gi, "alpha(opacity=");
- preservedTokens.push(token);
- return quote + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___" + quote;
- });
-
- // strings are safe, now wrestle the comments
- for (i = 0, len = comments.length; i < len; i += 1) {
-
- token = comments[i];
- placeholder = "___PRESERVE_CANDIDATE_COMMENT_" + i + "___";
-
- // ! in the first position of the comment means preserve
- // so push to the preserved tokens keeping the !
- if (token.charAt(0) === "!") {
- if (options.cuteComments) {
- preservedTokens.push(token.substring(1));
- } else if (options.uglyComments) {
- preservedTokens.push(token.substring(1).replace(/[\r\n]/g, ''));
- } else {
- preservedTokens.push(token);
- }
- content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
- continue;
- }
-
- // \ in the last position looks like hack for Mac/IE5
- // shorten that to /*\*/ and the next one to /**/
- if (token.charAt(token.length - 1) === "\\") {
- preservedTokens.push("\\");
- content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
- i = i + 1; // attn: advancing the loop
- preservedTokens.push("");
- content = content.replace(
- "___PRESERVE_CANDIDATE_COMMENT_" + i + "___",
- ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___"
- );
- continue;
- }
-
- // keep empty comments after child selectors (IE7 hack)
- // e.g. html >/**/ body
- if (token.length === 0) {
- startIndex = content.indexOf(placeholder);
- if (startIndex > 2) {
- if (content.charAt(startIndex - 3) === '>') {
- preservedTokens.push("");
- content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
- }
- }
- }
-
- // in all other cases kill the comment
- content = content.replace("/*" + placeholder + "*/", "");
- }
-
- if (options.expandVars) {
- // parse simple @variables blocks and remove them
- pattern = /@variables\s*\{\s*([^\}]+)\s*\}/g;
- content = content.replace(pattern, function (ignore, f1) {
- pattern = /\s*([a-z0-9\-]+)\s*:\s*([^;\}]+)\s*/gi;
- f1.replace(pattern, function (ignore, f1, f2) {
- if (f1 && f2) {
- vars[f1] = f2;
- }
- return '';
- });
- return '';
- });
-
- // replace var(x) with the value of x
- pattern = /var\s*\(\s*([^\)]+)\s*\)/g;
- content = content.replace(pattern, function (ignore, f1) {
- return vars[f1] || 'none';
- });
- }
-
- // normalize all whitespace strings to single spaces. Easier to work with that way.
- content = content.replace(/\s+/g, " ");
-
- // preserve formulas in calc() before removing spaces
- pattern = /calc\(([^;}]*)\)/g;
- content = content.replace(pattern, function (ignore, f1) {
- preservedTokens.push(
- 'calc(' +
- f1.replace(/(^\s*|\s*$)/g, "")
- .replace(/\( /g, "(")
- .replace(/ \)/g, ")") +
- ')'
- );
- return ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
- });
-
- // preserve matrix
- pattern = /\s*filter:\s*progid:DXImageTransform.Microsoft.Matrix\(([^\)]+)\);/g;
- content = content.replace(pattern, function (ignore, f1) {
- preservedTokens.push(f1);
- return "filter:progid:DXImageTransform.Microsoft.Matrix(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___);";
- });
-
- // remove the spaces before the things that should not have spaces before them.
- // but, be careful not to turn "p :link {...}" into "p:link{...}"
- // swap out any pseudo-class colons with the token, and then swap back.
- pattern = /(^|\})(([^\{:])+:)+([^\{]*\{)/g;
- content = content.replace(pattern, function (token) {
- return token.replace(/:/g, "___PSEUDOCLASSCOLON___");
- });
-
- // remove spaces before the things that should not have spaces before them.
- content = content.replace(/\s+([!{};:>+\(\)\],])/g, "$1");
-
- // restore spaces for !important
- content = content.replace(/!important/g, " !important");
-
- // bring back the colon
- content = content.replace(/___PSEUDOCLASSCOLON___/g, ":");
-
- // preserve 0 followed by a time unit for properties using time units
- pattern = /\s*(animation|animation-delay|animation-duration|transition|transition-delay|transition-duration):\s*([^;}]+)/gi;
- content = content.replace(pattern, function (ignore, f1, f2) {
-
- f2 = f2.replace(/(^|\D)0?\.?0(m?s)/gi, function (ignore, g1, g2) {
- preservedTokens.push('0' + g2);
- return g1 + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
- });
-
- return f1 + ":" + f2;
- });
-
- // preserve 0% in hsl and hsla color definitions
- content = content.replace(/(hsla?)\(([^)]+)\)/g, function (ignore, f1, f2) {
- var f0 = [];
- f2.split(',').forEach(function (part) {
- part = part.replace(/(^\s+|\s+$)/g, "");
- if (part === '0%') {
- preservedTokens.push('0%');
- f0.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
- } else {
- f0.push(part);
- }
- });
- return f1 + '(' + f0.join(',') + ')';
- });
-
- // preserve 0 followed by unit in keyframes steps (WIP)
- content = keyframes(content, preservedTokens);
-
- // retain space for special IE6 cases
- content = content.replace(/:first-(line|letter)(\{|,)/gi, function (ignore, f1, f2) {
- return ":first-" + f1.toLowerCase() + " " + f2;
- });
-
- // newlines before and after the end of a preserved comment
- if (options.cuteComments) {
- content = content.replace(/\s*\/\*/g, "___PRESERVED_NEWLINE___/*");
- content = content.replace(/\*\/\s*/g, "*/___PRESERVED_NEWLINE___");
- // no space after the end of a preserved comment
- } else {
- content = content.replace(/\*\/\s*/g, '*/');
- }
-
- // If there are multiple @charset directives, push them to the top of the file.
- pattern = /^(.*)(@charset)( "[^"]*";)/gi;
- content = content.replace(pattern, function (ignore, f1, f2, f3) {
- return f2.toLowerCase() + f3 + f1;
- });
-
- // When all @charset are at the top, remove the second and after (as they are completely ignored).
- pattern = /^((\s*)(@charset)( [^;]+;\s*))+/gi;
- content = content.replace(pattern, function (ignore, ignore2, f2, f3, f4) {
- return f2 + f3.toLowerCase() + f4;
- });
-
- // lowercase some popular @directives (@charset is done right above)
- pattern = /@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/gi;
- content = content.replace(pattern, function (ignore, f1) {
- return '@' + f1.toLowerCase();
- });
-
- // lowercase some more common pseudo-elements
- pattern = /:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/gi;
- content = content.replace(pattern, function (ignore, f1) {
- return ':' + f1.toLowerCase();
- });
-
- // if there is a @charset, then only allow one, and push to the top of the file.
- content = content.replace(/^(.*)(@charset \"[^\"]*\";)/g, "$2$1");
- content = content.replace(/^(\s*@charset [^;]+;\s*)+/g, "$1");
-
- // lowercase some more common functions
- pattern = /:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?any)\(/gi;
- content = content.replace(pattern, function (ignore, f1) {
- return ':' + f1.toLowerCase() + '(';
- });
-
- // lower case some common function that can be values
- // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
- pattern = /([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/gi;
- content = content.replace(pattern, function (ignore, f1, f2) {
- return f1 + f2.toLowerCase();
- });
-
- // put the space back in some cases, to support stuff like
- // @media screen and (-webkit-min-device-pixel-ratio:0){
- content = content.replace(/\band\(/gi, "and (");
-
- // remove the spaces after the things that should not have spaces after them.
- content = content.replace(/([!{}:;>+\(\[,])\s+/g, "$1");
-
- // remove unnecessary semicolons
- content = content.replace(/;+\}/g, "}");
-
- // replace 0(px,em,%) with 0.
- content = content.replace(/(^|[^.0-9\\])(?:0?\.)?0(?:ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%)/gi, "$10");
-
- // Replace x.0(px,em,%) with x(px,em,%).
- content = content.replace(/([0-9])\.0(ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%| |;)/gi, "$1$2");
-
- // replace 0 0 0 0; with 0.
- content = content.replace(/:0 0 0 0(;|\})/g, ":0$1");
- content = content.replace(/:0 0 0(;|\})/g, ":0$1");
- content = content.replace(/:0 0(;|\})/g, ":0$1");
-
- // replace background-position:0; with background-position:0 0;
- // same for transform-origin and box-shadow
- pattern = /(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin|box-shadow):0(;|\})/gi;
- content = content.replace(pattern, function (ignore, f1, f2) {
- return f1.toLowerCase() + ":0 0" + f2;
- });
-
- // replace 0.6 to .6, but only when preceded by : or a white-space
- content = content.replace(/(:|\s)0+\.(\d+)/g, "$1.$2");
-
- // shorten colors from rgb(51,102,153) to #336699
- // this makes it more likely that it'll get further compressed in the next step.
- pattern = /rgb\s*\(\s*([0-9,\s]+)\s*\)/gi;
- content = content.replace(pattern, function (ignore, f1) {
- rgbcolors = f1.split(",");
- hexcolor = "#";
- for (i = 0; i < rgbcolors.length; i += 1) {
- val = parseInt(rgbcolors[i], 10);
- if (val < 16) {
- hexcolor += "0";
- }
- if (val > 255) {
- val = 255;
- }
- hexcolor += val.toString(16);
- }
- return hexcolor;
- });
-
- // Shorten colors from #AABBCC to #ABC.
- content = compressHexColors(content);
-
- // Replace #f00 -> red
- content = content.replace(/(:|\s)(#f00)(;|})/g, "$1red$3");
-
- // Replace other short color keywords
- content = content.replace(/(:|\s)(#000080)(;|})/g, "$1navy$3");
- content = content.replace(/(:|\s)(#808080)(;|})/g, "$1gray$3");
- content = content.replace(/(:|\s)(#808000)(;|})/g, "$1olive$3");
- content = content.replace(/(:|\s)(#800080)(;|})/g, "$1purple$3");
- content = content.replace(/(:|\s)(#c0c0c0)(;|})/g, "$1silver$3");
- content = content.replace(/(:|\s)(#008080)(;|})/g, "$1teal$3");
- content = content.replace(/(:|\s)(#ffa500)(;|})/g, "$1orange$3");
- content = content.replace(/(:|\s)(#800000)(;|})/g, "$1maroon$3");
-
- // border: none -> border:0
- pattern = /(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/gi;
- content = content.replace(pattern, function (ignore, f1, f2) {
- return f1.toLowerCase() + ":0" + f2;
- });
-
- // shorter opacity IE filter
- content = content.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=");
-
- // Find a fraction that is used for Opera's -o-device-pixel-ratio query
- // Add token to add the "\" back in later
- content = content.replace(/\(([\-A-Za-z]+):([0-9]+)\/([0-9]+)\)/g, "($1:$2___QUERY_FRACTION___$3)");
-
- // remove empty rules.
- content = content.replace(/[^\};\{\/]+\{\}/g, "");
-
- // Add "\" back to fix Opera -o-device-pixel-ratio query
- content = content.replace(/___QUERY_FRACTION___/g, "/");
-
- // some source control tools don't like it when files containing lines longer
- // than, say 8000 characters, are checked in. The linebreak option is used in
- // that case to split long lines after a specific column.
- if (options.maxLineLen > 0) {
- for (i = 0, len = content.length; i < len; i += 1) {
- c = content.charAt(i);
- line.push(c);
- if (c === '}' && line.length > options.maxLineLen) {
- lines.push(line.join(''));
- line = [];
- }
- }
- if (line.length) {
- lines.push(line.join(''));
- }
-
- content = lines.join('\n');
- }
-
- // replace multiple semi-colons in a row by a single one
- // see SF bug #1980989
- content = content.replace(/;;+/g, ";");
-
- // trim the final string (for any leading or trailing white spaces)
- content = content.replace(/(^\s*|\s*$)/g, "");
-
- // restore preserved tokens
- for (i = preservedTokens.length - 1; i >= 0 ; i--) {
- content = content.replace(___PRESERVED_TOKEN_ + i + "___", preservedTokens[i], "g");
- }
-
- // restore preserved newlines
- content = content.replace(/___PRESERVED_NEWLINE___/g, '\n');
-
- // return
- return content;
- }
-
- // Uglify CSS files
-
- function processFiles(filenames, options) {
-
- var nFiles = filenames.length,
- uglies = [],
- index,
- filename,
- content;
-
- options = options || defaultOptions;
-
- if (options.convertUrls) {
- options.target = path.resolve(process.cwd(), options.convertUrls).split(PATH_SEP);
- }
-
- // process files
- for (index = 0; index < nFiles; index += 1) {
- filename = filenames[index];
- try {
- content = fs.readFileSync(filename, 'utf8');
- if (content.length) {
- if (options.convertUrls) {
- options.source = path.resolve(process.cwd(), filename).split(PATH_SEP);
- options.source.pop();
- }
- uglies.push(processString(content, options));
- }
- } catch (e) {
- if (options.debug) {
- console.error('uglifycss: unable to process "' + filename + '"\n' + e.stack);
- } else {
- console.error('uglifycss: unable to process "' + filename + '"\n\t' + e);
- }
- process.exit(1);
- }
- }
-
- // return concat'd results
- return uglies.join('');
- }
-
- module.exports = {
- defaultOptions: defaultOptions,
- processString: processString,
- processFiles: processFiles
- };
|