purify-css
Version:
Removed unused css. Compatible with single-page apps.
1,038 lines (887 loc) • 30.8 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var CleanCss = _interopDefault(require('clean-css'));
var rework = _interopDefault(require('rework'));
var glob = _interopDefault(require('glob'));
var domain;
// This constructor is used to store event handlers. Instantiating this is
// faster than explicitly calling `Object.create(null)` to get a "clean" empty
// object (tested with v8 v4.9).
function EventHandlers() {}
EventHandlers.prototype = Object.create(null);
function EventEmitter() {
EventEmitter.init.call(this);
}
// nodejs oddity
// require('events') === require('events').EventEmitter
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.usingDomains = false;
EventEmitter.prototype.domain = undefined;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
EventEmitter.init = function() {
this.domain = null;
if (EventEmitter.usingDomains) {
// if there is an active domain, then attach to it.
if (domain.active && !(this instanceof domain.Domain)) {
this.domain = domain.active;
}
}
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
this._events = new EventHandlers();
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || isNaN(n))
throw new TypeError('"n" argument must be a positive number');
this._maxListeners = n;
return this;
};
function $getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return $getMaxListeners(this);
};
// These standalone emit* functions are used to optimize calling of event
// handlers for fast cases because emit() itself often has a variable number of
// arguments and can be deoptimized because of that. These functions always have
// the same number of arguments and thus do not get deoptimized, so the code
// inside them can execute faster.
function emitNone(handler, isFn, self) {
if (isFn)
handler.call(self);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self);
}
}
function emitOne(handler, isFn, self, arg1) {
if (isFn)
handler.call(self, arg1);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1);
}
}
function emitTwo(handler, isFn, self, arg1, arg2) {
if (isFn)
handler.call(self, arg1, arg2);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1, arg2);
}
}
function emitThree(handler, isFn, self, arg1, arg2, arg3) {
if (isFn)
handler.call(self, arg1, arg2, arg3);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1, arg2, arg3);
}
}
function emitMany(handler, isFn, self, args) {
if (isFn)
handler.apply(self, args);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].apply(self, args);
}
}
EventEmitter.prototype.emit = function emit(type) {
var er, handler, len, args, i, events, domain;
var needDomainExit = false;
var doError = (type === 'error');
events = this._events;
if (events)
doError = (doError && events.error == null);
else if (!doError)
return false;
domain = this.domain;
// If there is no 'error' event listener then throw.
if (doError) {
er = arguments[1];
if (domain) {
if (!er)
er = new Error('Uncaught, unspecified "error" event');
er.domainEmitter = this;
er.domain = domain;
er.domainThrown = false;
domain.emit('error', er);
} else if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
// At least give some kind of context to the user
var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
err.context = er;
throw err;
}
return false;
}
handler = events[type];
if (!handler)
return false;
var isFn = typeof handler === 'function';
len = arguments.length;
switch (len) {
// fast cases
case 1:
emitNone(handler, isFn, this);
break;
case 2:
emitOne(handler, isFn, this, arguments[1]);
break;
case 3:
emitTwo(handler, isFn, this, arguments[1], arguments[2]);
break;
case 4:
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
break;
// slower
default:
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
emitMany(handler, isFn, this, args);
}
if (needDomainExit)
domain.exit();
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
if (typeof listener !== 'function')
throw new TypeError('"listener" argument must be a function');
events = target._events;
if (!events) {
events = target._events = new EventHandlers();
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (!existing) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] = prepend ? [listener, existing] :
[existing, listener];
} else {
// If we've already got an array, just append.
if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
}
// Check for listener leak
if (!existing.warned) {
m = $getMaxListeners(target);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + type + ' listeners added. ' +
'Use emitter.setMaxListeners() to increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
emitWarning(w);
}
}
}
return target;
}
function emitWarning(e) {
typeof console.warn === 'function' ? console.warn(e) : console.log(e);
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function _onceWrap(target, type, listener) {
var fired = false;
function g() {
target.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(target, arguments);
}
}
g.listener = listener;
return g;
}
EventEmitter.prototype.once = function once(type, listener) {
if (typeof listener !== 'function')
throw new TypeError('"listener" argument must be a function');
this.on(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
if (typeof listener !== 'function')
throw new TypeError('"listener" argument must be a function');
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;
if (typeof listener !== 'function')
throw new TypeError('"listener" argument must be a function');
events = this._events;
if (!events)
return this;
list = events[type];
if (!list)
return this;
if (list === listener || (list.listener && list.listener === listener)) {
if (--this._eventsCount === 0)
this._events = new EventHandlers();
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
originalListener = list[i].listener;
position = i;
break;
}
}
if (position < 0)
return this;
if (list.length === 1) {
list[0] = undefined;
if (--this._eventsCount === 0) {
this._events = new EventHandlers();
return this;
} else {
delete events[type];
}
} else {
spliceOne(list, position);
}
if (events.removeListener)
this.emit('removeListener', type, originalListener || listener);
}
return this;
};
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events;
events = this._events;
if (!events)
return this;
// not listening for removeListener, no need to emit
if (!events.removeListener) {
if (arguments.length === 0) {
this._events = new EventHandlers();
this._eventsCount = 0;
} else if (events[type]) {
if (--this._eventsCount === 0)
this._events = new EventHandlers();
else
delete events[type];
}
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
for (var i = 0, key; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = new EventHandlers();
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners) {
// LIFO order
do {
this.removeListener(type, listeners[listeners.length - 1]);
} while (listeners[0]);
}
return this;
};
EventEmitter.prototype.listeners = function listeners(type) {
var evlistener;
var ret;
var events = this._events;
if (!events)
ret = [];
else {
evlistener = events[type];
if (!evlistener)
ret = [];
else if (typeof evlistener === 'function')
ret = [evlistener.listener || evlistener];
else
ret = unwrapListeners(evlistener);
}
return ret;
};
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
var events = this._events;
if (events) {
var evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener) {
return evlistener.length;
}
}
return 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
};
// About 1.5x faster than the two-arg version of Array#splice().
function spliceOne(list, index) {
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
list[i] = list[k];
list.pop();
}
function arrayClone(arr, i) {
var copy = new Array(i);
while (i--)
copy[i] = arr[i];
return copy;
}
function unwrapListeners(arr) {
var ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
ret[i] = arr[i].listener || arr[i];
}
return ret;
}
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var RULE_TYPE = "rule";
var MEDIA_TYPE = "media";
var CssTreeWalker = function (_EventEmitter) {
inherits(CssTreeWalker, _EventEmitter);
function CssTreeWalker(code, plugins) {
classCallCheck(this, CssTreeWalker);
var _this = possibleConstructorReturn(this, (CssTreeWalker.__proto__ || Object.getPrototypeOf(CssTreeWalker)).call(this));
_this.startingSource = code;
_this.ast = null;
plugins.forEach(function (plugin) {
plugin.initialize(_this);
});
return _this;
}
createClass(CssTreeWalker, [{
key: "beginReading",
value: function beginReading() {
this.ast = rework(this.startingSource).use(this.readPlugin.bind(this));
}
}, {
key: "readPlugin",
value: function readPlugin(tree) {
this.readRules(tree.rules);
this.removeEmptyRules(tree.rules);
}
}, {
key: "readRules",
value: function readRules(rules) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = rules[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var rule = _step.value;
if (rule.type === RULE_TYPE) {
this.emit("readRule", rule.selectors, rule);
}
if (rule.type === MEDIA_TYPE) {
this.readRules(rule.rules);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
}, {
key: "removeEmptyRules",
value: function removeEmptyRules(rules) {
var emptyRules = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = rules[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var rule = _step2.value;
var ruleType = rule.type;
if (ruleType === RULE_TYPE && rule.selectors.length === 0) {
emptyRules.push(rule);
}
if (ruleType === MEDIA_TYPE) {
this.removeEmptyRules(rule.rules);
if (rule.rules.length === 0) {
emptyRules.push(rule);
}
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
emptyRules.forEach(function (emptyRule) {
var index = rules.indexOf(emptyRule);
rules.splice(index, 1);
});
}
}, {
key: "toString",
value: function toString() {
if (this.ast) {
return this.ast.toString().replace(/,\n/g, ",");
}
return "";
}
}]);
return CssTreeWalker;
}(EventEmitter);
var UglifyJS = require("uglify-js");
var fs$1 = require("fs");
var compressCode = function compressCode(code) {
try {
// Try to minimize the code as much as possible, removing noise.
var ast = UglifyJS.parse(code);
ast.figure_out_scope();
var compressor = UglifyJS.Compressor({ warnings: false });
ast = ast.transform(compressor);
ast.figure_out_scope();
ast.compute_char_frequency();
ast.mangle_names({ toplevel: true });
code = ast.print_to_string().toLowerCase();
} catch (e) {
// If compression fails, assume it's not a JS file and return the full code.
}
return code.toLowerCase();
};
var concatFiles = function concatFiles(files, options) {
return files.reduce(function (total, file) {
var code = "";
try {
code = fs$1.readFileSync(file, "utf8");
code = options.compress ? compressCode(code) : code;
} catch (e) {
console.warn(e.message);
}
return "" + total + code + " ";
}, "");
};
var getFilesFromPatternArray = function getFilesFromPatternArray(fileArray) {
var sourceFiles = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = fileArray[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var string = _step.value;
try {
// See if string is a filepath, not a file pattern.
fs$1.statSync(string);
sourceFiles[string] = true;
} catch (e) {
var files = glob.sync(string);
files.forEach(function (file) {
sourceFiles[file] = true;
});
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return Object.keys(sourceFiles);
};
var filesToSource = function filesToSource(files, type) {
var isContent = type === "content";
var options = { compress: isContent };
if (Array.isArray(files)) {
files = getFilesFromPatternArray(files);
return concatFiles(files, options);
}
// 'files' is already a source string.
return isContent ? compressCode(files) : files;
};
var FileUtil = {
concatFiles: concatFiles,
filesToSource: filesToSource,
getFilesFromPatternArray: getFilesFromPatternArray
};
var startTime = void 0;
var beginningLength = void 0;
var printInfo = function printInfo(endingLength) {
var sizeReduction = ((beginningLength - endingLength) / beginningLength * 100).toFixed(1);
console.log("\n ________________________________________________\n |\n | PurifyCSS has reduced the file size by ~ " + sizeReduction + "% \n |\n ________________________________________________\n ");
};
var printRejected = function printRejected(rejectedTwigs) {
console.log("\n ________________________________________________\n |\n | PurifyCSS - Rejected selectors: \n | " + rejectedTwigs.join("\n |\t") + "\n |\n ________________________________________________\n ");
};
var startLog = function startLog(cssLength) {
startTime = new Date();
beginningLength = cssLength;
};
var PrintUtil = {
printInfo: printInfo,
printRejected: printRejected,
startLog: startLog
};
var addWord = function addWord(words, word) {
if (word) words.push(word);
};
var getAllWordsInContent = function getAllWordsInContent(content) {
var used = {
// Always include html and body.
html: true,
body: true
};
var words = content.split(/[^a-z]/g);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = words[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var word = _step.value;
used[word] = true;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return used;
};
var getAllWordsInSelector = function getAllWordsInSelector(selector) {
// Remove attr selectors. "a[href...]"" will become "a".
selector = selector.replace(/\[(.+?)\]/g, "").toLowerCase();
// If complex attr selector (has a bracket in it) just leave
// the selector in. ¯\_(ツ)_/¯
if (selector.includes("[") || selector.includes("]")) {
return [];
}
var skipNextWord = false,
word = "",
words = [];
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = selector[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var letter = _step2.value;
if (skipNextWord && !/[ #.]/.test(letter)) continue;
// If pseudoclass or universal selector, skip the next word
if (/[:*]/.test(letter)) {
addWord(words, word);
word = "";
skipNextWord = true;
continue;
}
if (/[a-z]/.test(letter)) {
word += letter;
} else {
addWord(words, word);
word = "";
skipNextWord = false;
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
addWord(words, word);
return words;
};
var isWildcardWhitelistSelector = function isWildcardWhitelistSelector(selector) {
return selector[0] === "*" && selector[selector.length - 1] === "*";
};
var hasWhitelistMatch = function hasWhitelistMatch(selector, whitelist) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = whitelist[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var el = _step.value;
if (selector.includes(el)) return true;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return false;
};
var SelectorFilter = function () {
function SelectorFilter(contentWords, whitelist) {
classCallCheck(this, SelectorFilter);
this.contentWords = contentWords;
this.rejectedSelectors = [];
this.wildcardWhitelist = [];
this.parseWhitelist(whitelist);
}
createClass(SelectorFilter, [{
key: "initialize",
value: function initialize(CssSyntaxTree) {
CssSyntaxTree.on("readRule", this.parseRule.bind(this));
}
}, {
key: "parseWhitelist",
value: function parseWhitelist(whitelist) {
var _this = this;
whitelist.forEach(function (whitelistSelector) {
whitelistSelector = whitelistSelector.toLowerCase();
if (isWildcardWhitelistSelector(whitelistSelector)) {
// If '*button*' then push 'button' onto list.
_this.wildcardWhitelist.push(whitelistSelector.substr(1, whitelistSelector.length - 2));
} else {
getAllWordsInSelector(whitelistSelector).forEach(function (word) {
_this.contentWords[word] = true;
});
}
});
}
}, {
key: "parseRule",
value: function parseRule(selectors, rule) {
rule.selectors = this.filterSelectors(selectors);
}
}, {
key: "filterSelectors",
value: function filterSelectors(selectors) {
var contentWords = this.contentWords,
rejectedSelectors = this.rejectedSelectors,
wildcardWhitelist = this.wildcardWhitelist,
usedSelectors = [];
selectors.forEach(function (selector) {
if (hasWhitelistMatch(selector, wildcardWhitelist)) {
usedSelectors.push(selector);
return;
}
var words = getAllWordsInSelector(selector),
usedWords = words.filter(function (word) {
return contentWords[word];
});
if (usedWords.length === words.length) {
usedSelectors.push(selector);
} else {
rejectedSelectors.push(selector);
}
});
return usedSelectors;
}
}]);
return SelectorFilter;
}();
var fs = require("fs");
var OPTIONS = {
output: false,
minify: false,
info: false,
rejected: false,
whitelist: [],
cleanCssOptions: {}
};
var getOptions = function getOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var opt = {};
for (var option in OPTIONS) {
opt[option] = options[option] || OPTIONS[option];
}
return opt;
};
var minify = function minify(cssSource, options) {
return new CleanCss(options).minify(cssSource).styles;
};
var purify = function purify(searchThrough, css, options, callback) {
if (typeof options === "function") {
callback = options;
options = {};
}
options = getOptions(options);
var cssString = FileUtil.filesToSource(css, "css"),
content = FileUtil.filesToSource(searchThrough, "content");
PrintUtil.startLog(minify(cssString).length);
var wordsInContent = getAllWordsInContent(content),
selectorFilter = new SelectorFilter(wordsInContent, options.whitelist),
tree = new CssTreeWalker(cssString, [selectorFilter]);
tree.beginReading();
var source = tree.toString();
source = options.minify ? minify(source, options.cleanCssOptions) : source;
// Option info = true
if (options.info) {
if (options.minify) {
PrintUtil.printInfo(source.length);
} else {
PrintUtil.printInfo(minify(source, options.cleanCssOptions).length);
}
}
// Option rejected = true
if (options.rejected && selectorFilter.rejectedSelectors.length) {
PrintUtil.printRejected(selectorFilter.rejectedSelectors);
}
if (options.output) {
fs.writeFile(options.output, source, function (err) {
if (err) return err;
});
} else {
return callback ? callback(source) : source;
}
};
module.exports = purify;