purify-css = function (searchThrough, css, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } options = getOptions(options); var cssString = FileUtil.filesToSource(css, 'css'); var content = FileUtil.filesToSource(searchThrough, 'content'); PrintUtil.startLog(minify(cssString).length); var wordsInContent = getAllWordsInContent(content); var selectorFilter = new SelectorFilter(wordsInContent, options.whitelist); var tree = new CssTreeWalker(cssString, [selectorFilter]); tree.beginReading(); var source = tree.toString(); if (options.minify) { source = minify(source); } if (options.info && options.minify) { PrintUtil.printInfo(source.length); } else if (options.info && !options.minify) { PrintUtil.printInfo(minify(source).length); } if (options.rejected) { PrintUtil.printRejected(selectorFilter.rejectedSelectors); } if (!options.output) { return callback ? callback(source) : source; } else { fs.writeFile(options.output, source, function (err) { if (err) { return err; } }); } }
n/a
CssTreeWalker = function (code, plugins) { EventEmitter.call(this); this.startingSource = code; this.ast = null; plugins.forEach(function (plugin) { plugin.initialize(this); }.bind(this)); }
n/a
SelectorFilter = function (contentWords, whitelist) { this.contentWords = contentWords; this.rejectedSelectors = []; this.wildcardWhitelist = []; this.parseWhitelist(whitelist); }
n/a
CssTreeWalker = function (code, plugins) { EventEmitter.call(this); this.startingSource = code; this.ast = null; plugins.forEach(function (plugin) { plugin.initialize(this); }.bind(this)); }
n/a
beginReading = function () { this.ast = rework(this.startingSource) .use(this.readPlugin.bind(this)); }
...
PrintUtil.startLog(minify(cssString).length);
var wordsInContent = getAllWordsInContent(content);
var selectorFilter = new SelectorFilter(wordsInContent, options.whitelist);
var tree = new CssTreeWalker(cssString, [selectorFilter]);
tree.beginReading();
var source = tree.toString();
if (options.minify) {
source = minify(source);
}
if (options.info && options.minify) {
...
constructor = function (code, plugins) { EventEmitter.call(this); this.startingSource = code; this.ast = null; plugins.forEach(function (plugin) { plugin.initialize(this); }.bind(this)); }
n/a
readPlugin = function (tree) { this.readRules(tree.rules); this.removeEmptyRules(tree.rules); }
n/a
readRules = function (rules) { rules.forEach(function (rule) { var ruleType = rule.type; if (ruleType === RULE_TYPE) { this.emit('readRule', rule.selectors, rule); } if (ruleType === MEDIA_TYPE) { this.readRules(rule.rules); } }.bind(this)); }
...
CssTreeWalker.prototype.beginReading = function () {
this.ast = rework(this.startingSource)
.use(this.readPlugin.bind(this));
};
CssTreeWalker.prototype.readPlugin = function (tree) {
this.readRules(tree.rules);
this.removeEmptyRules(tree.rules);
};
CssTreeWalker.prototype.readRules = function (rules) {
rules.forEach(function (rule) {
var ruleType = rule.type;
...
removeEmptyRules = function (rules) { var emptyRules = []; rules.forEach(function (rule) { 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); } } }.bind(this)); emptyRules.forEach(function (emptyRule) { var index = rules.indexOf(emptyRule); rules.splice(index, 1); }); }
...
CssTreeWalker.prototype.beginReading = function () {
this.ast = rework(this.startingSource)
.use(this.readPlugin.bind(this));
};
CssTreeWalker.prototype.readPlugin = function (tree) {
this.readRules(tree.rules);
this.removeEmptyRules(tree.rules);
};
CssTreeWalker.prototype.readRules = function (rules) {
rules.forEach(function (rule) {
var ruleType = rule.type;
if (ruleType === RULE_TYPE) {
...
toString = function () { if (this.ast) { return this.ast.toString().replace(/,\n/g, ','); } return ''; }
...
this.readRules(rule.rules);
}
}.bind(this));
};
CssTreeWalker.prototype.toString = function () {
if (this.ast) {
return this.ast.toString().replace(/,\n/g, ',');
}
return '';
};
CssTreeWalker.prototype.removeEmptyRules = function (rules) {
var emptyRules = [];
...
getAllWordsInContent = function (content) { var used = { // Always include html and body. html: true, body: true }; var word = ''; for (var i = 0; i < content.length; i++) { var chr = content[i]; if (chr.match(/[a-z]+/)) { word += chr; } else { used[word] = true; word = ''; } } used[word] = true; return used; }
n/a
getAllWordsInSelector = function (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.indexOf('[') !== -1 || selector.indexOf(']') !== -1) { return []; } var words = []; var word = ''; var skipNextWord = false; for (var i = 0; i < selector.length; i++) { var letter = selector[i]; if (skipNextWord && (letter !== '.' || letter !== '#' || letter !== ' ')) { continue; } // If pseudoclass or universal selector, skip the next word if (letter === ':' || letter === '*') { addWord(words, word); word = ''; skipNextWord = true; continue; } if (letter.match(/[a-z]+/)) { word += letter; } else { addWord(words, word); word = ''; skipNextWord = false; } } addWord(words, word); return words; }
n/a
compressCode = function (code) {
try {
// Try to minimize the code as much as possible, removing noise.
var ast = UglifyJS.parse(code);
ast.figure_out_scope();
/* eslint-disable new-cap */
var compressor = UglifyJS.Compressor({warnings: false});
/* eslint-enable new-cap */
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();
}
...
return files.reduce(function (total, file) {
var code = '';
try {
code = fs.readFileSync(file, 'utf8');
if (options.compress) {
code = FileUtil.compressCode(code);
}
} catch (e) {
console.warn('\nWARNING: Could not read ' + file + '.');
}
return total + code + ' ';
}, '');
...
concatFiles = function (files, options) { options = options || {}; return files.reduce(function (total, file) { var code = ''; try { code = fs.readFileSync(file, 'utf8'); if (options.compress) { code = FileUtil.compressCode(code); } } catch (e) { console.warn('\nWARNING: Could not read ' + file + '.'); } return total + code + ' '; }, ''); }
...
filesToSource: function (files, type) {
var isContent = type === 'content';
var options = {compress: isContent};
if (Array.isArray(files)) {
files = FileUtil.getFilesFromPatternArray(files);
return FileUtil.concatFiles(files, options);
}
// 'files' is already a source string.
if (isContent) {
return FileUtil.compressCode(files);
} else {
return files;
...
filesToSource = function (files, type) { var isContent = type === 'content'; var options = {compress: isContent}; if (Array.isArray(files)) { files = FileUtil.getFilesFromPatternArray(files); return FileUtil.concatFiles(files, options); } // 'files' is already a source string. if (isContent) { return FileUtil.compressCode(files); } else { return files; } }
...
var purify = function (searchThrough, css, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
options = getOptions(options);
var cssString = FileUtil.filesToSource(css, 'css');
var content = FileUtil.filesToSource(searchThrough, 'content');
PrintUtil.startLog(minify(cssString).length);
var wordsInContent = getAllWordsInContent(content);
var selectorFilter = new SelectorFilter(wordsInContent, options.whitelist);
...
getFilesFromPatternArray = function (fileArray) { var sourceFiles = {}; fileArray.forEach(function (string) { try { // See if string is a filepath, not a file pattern. fs.statSync(string); sourceFiles[string] = true; } catch (e) { var files = glob.sync(string); files.forEach(function (file) { sourceFiles[file] = true; }); } }); return Object.keys(sourceFiles); }
...
},
filesToSource: function (files, type) {
var isContent = type === 'content';
var options = {compress: isContent};
if (Array.isArray(files)) {
files = FileUtil.getFilesFromPatternArray(files);
return FileUtil.concatFiles(files, options);
}
// 'files' is already a source string.
if (isContent) {
return FileUtil.compressCode(files);
} else {
...
printInfo = function (endingLength) { var logFn = console.error; logFn.call(null, '##################################'); logFn.call(null, 'PurifyCSS has reduced the file size by ~' + (((beginningLength - endingLength) / beginningLength) * 100).toFixed(1) + '%'); logFn.call(null, '##################################'); logFn.call(null, 'This function took: ', new Date() - startTime, 'ms'); }
...
var source = tree.toString();
if (options.minify) {
source = minify(source);
}
if (options.info && options.minify) {
PrintUtil.printInfo(source.length);
} else if (options.info && !options.minify) {
PrintUtil.printInfo(minify(source).length);
}
if (options.rejected) {
PrintUtil.printRejected(selectorFilter.rejectedSelectors);
}
...
printRejected = function (rejectedTwigs) { var logFn = console.error; logFn.call(null, '##################################'); logFn.call(null, 'Rejected selectors:'); logFn.call(null, rejectedTwigs.join('\n')); logFn.call(null, '##################################'); }
...
if (options.info && options.minify) {
PrintUtil.printInfo(source.length);
} else if (options.info && !options.minify) {
PrintUtil.printInfo(minify(source).length);
}
if (options.rejected) {
PrintUtil.printRejected(selectorFilter.rejectedSelectors);
}
if (!options.output) {
return callback ? callback(source) : source;
} else {
fs.writeFile(options.output, source, function (err) {
if (err) {
...
startLog = function (cssLength) { startTime = new Date(); beginningLength = cssLength; }
...
options = {};
}
options = getOptions(options);
var cssString = FileUtil.filesToSource(css, 'css');
var content = FileUtil.filesToSource(searchThrough, 'content');
PrintUtil.startLog(minify(cssString).length);
var wordsInContent = getAllWordsInContent(content);
var selectorFilter = new SelectorFilter(wordsInContent, options.whitelist);
var tree = new CssTreeWalker(cssString, [selectorFilter]);
tree.beginReading();
...
SelectorFilter = function (contentWords, whitelist) { this.contentWords = contentWords; this.rejectedSelectors = []; this.wildcardWhitelist = []; this.parseWhitelist(whitelist); }
n/a
filterSelectors = function (selectors) { var contentWords = this.contentWords; var rejectedSelectors = this.rejectedSelectors; var wildcardWhitelist = this.wildcardWhitelist; var usedSelectors = []; selectors.forEach(function (selector) { if (hasWhitelistMatch(selector, wildcardWhitelist)) { usedSelectors.push(selector); return; } var words = getAllWordsInSelector(selector); var usedWords = words.filter(function (word) { return contentWords[word]; }); if (usedWords.length === words.length) { usedSelectors.push(selector); } else { rejectedSelectors.push(selector); } }); return usedSelectors; }
...
this.contentWords[word] = true;
}.bind(this));
}
}.bind(this));
};
SelectorFilter.prototype.parseRule = function (selectors, rule) {
rule.selectors = this.filterSelectors(selectors);
};
SelectorFilter.prototype.filterSelectors = function (selectors) {
var contentWords = this.contentWords;
var rejectedSelectors = this.rejectedSelectors;
var wildcardWhitelist = this.wildcardWhitelist;
var usedSelectors = [];
...
initialize = function (CssSyntaxTree) { CssSyntaxTree.on('readRule', this.parseRule.bind(this)); }
...
var CssTreeWalker = function (code, plugins) {
EventEmitter.call(this);
this.startingSource = code;
this.ast = null;
plugins.forEach(function (plugin) {
plugin.initialize(this);
}.bind(this));
};
CssTreeWalker.prototype = Object.create(EventEmitter.prototype);
CssTreeWalker.prototype.constructor = CssTreeWalker;
CssTreeWalker.prototype.beginReading = function () {
...
parseRule = function (selectors, rule) { rule.selectors = this.filterSelectors(selectors); }
n/a
parseWhitelist = function (whitelist) { 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; }.bind(this)); } }.bind(this)); }
...
}
var SelectorFilter = function (contentWords, whitelist) {
this.contentWords = contentWords;
this.rejectedSelectors = [];
this.wildcardWhitelist = [];
this.parseWhitelist(whitelist);
};
SelectorFilter.prototype.initialize = function (CssSyntaxTree) {
CssSyntaxTree.on('readRule', this.parseRule.bind(this));
};
SelectorFilter.prototype.parseWhitelist = function (whitelist) {
...