gulp-css-usage
Advanced tools
Comparing version 1.0.10 to 2.0.0
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -34,118 +34,120 @@ | ||
var getAllSelectorsFromCSSFile = function getAllSelectorsFromCSSFile(cssFile) { | ||
var contents = cssFile.contents.toString(); | ||
var selectors = {}; | ||
var matches = cssSelectorRegex.exec(contents); | ||
while (matches != null) { | ||
var selector = matches[1]; | ||
selectors[selector] = selector; | ||
matches = cssSelectorRegex.exec(contents); | ||
} | ||
var contents = cssFile.contents.toString(); | ||
var selectors = {}; | ||
var matches = cssSelectorRegex.exec(contents); | ||
while (matches != null) { | ||
var selector = matches[1]; | ||
selectors[selector] = selector; | ||
matches = cssSelectorRegex.exec(contents); | ||
} | ||
return selectors; | ||
return selectors; | ||
}; | ||
var makeDiff = function makeDiff(cssSelectors, jsxAttributes) { | ||
var needless = []; | ||
Object.keys(cssSelectors).forEach(function (selector) { | ||
if (!jsxAttributes[selector.substring(1)]) { | ||
needless.push(selector); | ||
} | ||
}); | ||
var needless = []; | ||
return needless; | ||
var cssSelectorKeys = Object.keys(cssSelectors); | ||
cssSelectorKeys.forEach(function (selector) { | ||
if (!jsxAttributes[selector.substring(1)]) { | ||
needless.push(selector); | ||
} | ||
}); | ||
var statistics = (needless.length / cssSelectorKeys.length * 100).toFixed(0); | ||
return { needless: needless, statistics: statistics }; | ||
}; | ||
var printNeedlessSelectorList = function printNeedlessSelectorList(list) { | ||
_gulpUtil2.default.log(''); | ||
_gulpUtil2.default.log(_gulpUtil2.default.colors.yellow(PLUGIN_NAME + ': The following selectors are not in use')); | ||
list.forEach(function (selector) { | ||
return _gulpUtil2.default.log(selector); | ||
}); | ||
_gulpUtil2.default.log(''); | ||
var printNeedlessSelectorList = function printNeedlessSelectorList(list, statistics) { | ||
_gulpUtil2.default.log(''); | ||
_gulpUtil2.default.log(_gulpUtil2.default.colors.yellow(PLUGIN_NAME + ': ' + statistics + '% of your css selectors are not in use!')); | ||
_gulpUtil2.default.log(_gulpUtil2.default.colors.yellow(PLUGIN_NAME + ': The selectors are:')); | ||
list.forEach(function (selector) { | ||
return _gulpUtil2.default.log(selector); | ||
}); | ||
_gulpUtil2.default.log(''); | ||
}; | ||
var parseAndExtractJsxAttributes = function parseAndExtractJsxAttributes(jsxFileContents) { | ||
var babylonPlugins = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
var babylonPlugins = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; | ||
var jsxAttributes = {}; | ||
var plugins = ['jsx', 'flow', 'classProperties']; | ||
if (babylonPlugins.length) { | ||
plugins = plugins.concat(babylonPlugins); | ||
} | ||
var jsxAttributes = {}; | ||
var plugins = ['jsx', 'flow', 'classProperties']; | ||
if (babylonPlugins.length) { | ||
plugins = plugins.concat(babylonPlugins); | ||
} | ||
// use babylon.parse and then babel traverse for dynamic class names and other attributes on the jsx code. | ||
// might come up with a bit more strings but the needless stuff are not here anyway. | ||
var ast = (0, _babylon.parse)(jsxFileContents, { sourceType: 'module', plugins: plugins }); | ||
(0, _babelTraverse2.default)(ast, { | ||
enter: function enter(path) { | ||
var _path$node = path.node; | ||
var type = _path$node.type; | ||
var value = _path$node.value; | ||
// use babylon.parse and then babel traverse for dynamic class names and other attributes on the jsx code. | ||
// might come up with a bit more strings but the needless stuff are not here anyway. | ||
var ast = (0, _babylon.parse)(jsxFileContents, { sourceType: 'module', plugins: plugins }); | ||
(0, _babelTraverse2.default)(ast, { | ||
enter: function enter(path) { | ||
var _path$node = path.node; | ||
var type = _path$node.type; | ||
var value = _path$node.value; | ||
if (type === 'StringLiteral') { | ||
var attributes = value.split(' '); | ||
attributes.forEach(function (attr) { | ||
return jsxAttributes[attr] = attr; | ||
}); | ||
} | ||
} | ||
}); | ||
if (type === 'StringLiteral') { | ||
var attributes = value.split(' '); | ||
attributes.forEach(function (attr) { | ||
return jsxAttributes[attr] = attr; | ||
}); | ||
} | ||
} | ||
}); | ||
return jsxAttributes; | ||
return jsxAttributes; | ||
}; | ||
var validateInput = function validateInput(cssFilePath, threshold) { | ||
if (!cssFilePath) { | ||
throw new PluginError(PLUGIN_NAME, 'Missing css field!'); | ||
} | ||
if (typeof cssFilePath !== 'string') { | ||
throw new PluginError(PLUGIN_NAME, 'css field must be a string!'); | ||
} | ||
if (threshold && (typeof threshold !== 'number' || threshold > 100 || threshold < 0)) { | ||
throw new PluginError(PLUGIN_NAME, 'threshold value should be a number between 0-100!'); | ||
} | ||
}; | ||
var gulpCssUsage = function gulpCssUsage() { | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var cssFilePath = options.css; | ||
var babylon = options.babylon; | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var cssFilePath = options.css; | ||
var babylon = options.babylon; | ||
var threshold = options.threshold; | ||
if (!cssFilePath) { | ||
throw new PluginError(PLUGIN_NAME, 'Missing css field!'); | ||
} | ||
if (typeof cssFilePath !== 'string') { | ||
throw new PluginError(PLUGIN_NAME, 'css field must be a string!'); | ||
} | ||
var cssFile = new _gulpUtil2.default.File({ path: cssFilePath, contents: _fs2.default.readFileSync(cssFilePath) }); | ||
var cssSelectors = getAllSelectorsFromCSSFile(cssFile); | ||
var fileBuffer = undefined; | ||
var allAttributes = {}; | ||
var transformers = function transformers(file, enc, cb) { | ||
var currentJsxAttributes = undefined; | ||
if (file.isNull()) { | ||
return cb(error, file); | ||
} | ||
if (file.isBuffer()) { | ||
currentJsxAttributes = parseAndExtractJsxAttributes(file.contents.toString(), babylon); | ||
Object.assign(allAttributes, currentJsxAttributes); | ||
cb(error, file); | ||
} | ||
if (file.isStream()) { | ||
_gulpUtil2.default.log(_gulpUtil2.default.colors.magenta(PLUGIN_NAME + ': streaming is deprecated and will not be supported soon')); | ||
if (enc !== 'utf8') { | ||
file.contents.setEncoding('utf8'); | ||
} | ||
if (!fileBuffer) { | ||
fileBuffer = new Buffer([]); // utf8 by default | ||
} | ||
validateInput(cssFilePath, threshold); | ||
file.contents.on('data', function (chunk) { | ||
return fileBuffer = Buffer.concat([new Buffer(chunk), fileBuffer]); | ||
}); | ||
file.contents.on('end', function () { | ||
currentJsxAttributes = parseAndExtractJsxAttributes(fileBuffer.toString(), babylon); | ||
Object.assign(allAttributes, currentJsxAttributes); | ||
fileBuffer = undefined; | ||
cb(error, file); | ||
}); | ||
} | ||
}; | ||
var cssFile = new _gulpUtil2.default.File({ path: cssFilePath, contents: _fs2.default.readFileSync(cssFilePath) }); | ||
var cssSelectors = getAllSelectorsFromCSSFile(cssFile); | ||
var flush = function flush(cb) { | ||
var needless = makeDiff(cssSelectors, allAttributes); | ||
printNeedlessSelectorList(needless); | ||
cb(); | ||
}; | ||
return _through2.default.obj(function (file, enc, cb) { | ||
var currentJsxAttributes = void 0; | ||
if (file.isNull()) { | ||
return cb(error, file); | ||
} | ||
if (file.isStream()) { | ||
return cb(new PluginError(PLUGIN_NAME, 'Streaming not supported')); | ||
} | ||
return _through2.default.obj({}, transformers, flush); | ||
var allAttributes = {}; | ||
// file is buffer | ||
currentJsxAttributes = parseAndExtractJsxAttributes(file.contents.toString(), babylon); | ||
Object.assign(allAttributes, currentJsxAttributes); | ||
var _makeDiff = makeDiff(cssSelectors, allAttributes); | ||
var needless = _makeDiff.needless; | ||
var statistics = _makeDiff.statistics; | ||
printNeedlessSelectorList(needless, statistics); | ||
if (threshold && statistics >= threshold) { | ||
return cb(new PluginError(PLUGIN_NAME, 'too many unused css selectors!')); | ||
} | ||
cb(error, file); | ||
}); | ||
}; | ||
@@ -156,26 +158,26 @@ | ||
if (typeof Object.assign != 'function') { | ||
(function () { | ||
Object.assign = function (target) { | ||
'use strict'; | ||
(function () { | ||
Object.assign = function (target) { | ||
'use strict'; | ||
if (target === undefined || target === null) { | ||
throw new TypeError('Cannot convert undefined or null to object'); | ||
} | ||
if (target === undefined || target === null) { | ||
throw new TypeError('Cannot convert undefined or null to object'); | ||
} | ||
var output = Object(target); | ||
for (var index = 1; index < arguments.length; index++) { | ||
var source = arguments[index]; | ||
if (source !== undefined && source !== null) { | ||
for (var nextKey in source) { | ||
if (source.hasOwnProperty(nextKey)) { | ||
output[nextKey] = source[nextKey]; | ||
} | ||
} | ||
} | ||
} | ||
return output; | ||
}; | ||
})(); | ||
var output = Object(target); | ||
for (var index = 1; index < arguments.length; index++) { | ||
var source = arguments[index]; | ||
if (source !== undefined && source !== null) { | ||
for (var nextKey in source) { | ||
if (source.hasOwnProperty(nextKey)) { | ||
output[nextKey] = source[nextKey]; | ||
} | ||
} | ||
} | ||
} | ||
return output; | ||
}; | ||
})(); | ||
} | ||
exports.default = gulpCssUsage; |
207
index.js
@@ -14,106 +14,107 @@ import through from 'through2'; | ||
let getAllSelectorsFromCSSFile = (cssFile) => { | ||
let contents = cssFile.contents.toString(); | ||
let selectors = {}; | ||
let matches = cssSelectorRegex.exec(contents); | ||
while (matches != null) { | ||
let selector = matches[1]; | ||
selectors[selector] = selector; | ||
matches = cssSelectorRegex.exec(contents); | ||
} | ||
let contents = cssFile.contents.toString(); | ||
let selectors = {}; | ||
let matches = cssSelectorRegex.exec(contents); | ||
while (matches != null) { | ||
let selector = matches[1]; | ||
selectors[selector] = selector; | ||
matches = cssSelectorRegex.exec(contents); | ||
} | ||
return selectors; | ||
return selectors; | ||
}; | ||
let makeDiff = (cssSelectors, jsxAttributes) => { | ||
let needless = []; | ||
Object.keys(cssSelectors).forEach(selector => { | ||
if (!jsxAttributes[selector.substring(1)]) { | ||
needless.push(selector); | ||
} | ||
}); | ||
let needless = []; | ||
return needless; | ||
let cssSelectorKeys = Object.keys(cssSelectors); | ||
cssSelectorKeys.forEach(selector => { | ||
if (!jsxAttributes[selector.substring(1)]) { | ||
needless.push(selector); | ||
} | ||
}); | ||
let statistics = ((needless.length / cssSelectorKeys.length) * 100).toFixed(0); | ||
return {needless, statistics}; | ||
}; | ||
let printNeedlessSelectorList = (list) => { | ||
gutil.log(''); | ||
gutil.log(gutil.colors.yellow(PLUGIN_NAME + ': The following selectors are not in use')); | ||
list.forEach(selector => gutil.log(selector)); | ||
gutil.log(''); | ||
let printNeedlessSelectorList = (list, statistics) => { | ||
gutil.log(''); | ||
gutil.log(gutil.colors.yellow(PLUGIN_NAME + ': ' + statistics + '% of your css selectors are not in use!')); | ||
gutil.log(gutil.colors.yellow(PLUGIN_NAME + ': The selectors are:')); | ||
list.forEach(selector => gutil.log(selector)); | ||
gutil.log(''); | ||
}; | ||
let parseAndExtractJsxAttributes = (jsxFileContents, babylonPlugins = []) => { | ||
let jsxAttributes = {}; | ||
let plugins = ['jsx', 'flow', 'classProperties']; | ||
if(babylonPlugins.length) { | ||
plugins = plugins.concat(babylonPlugins); | ||
} | ||
let jsxAttributes = {}; | ||
let plugins = ['jsx', 'flow', 'classProperties']; | ||
if (babylonPlugins.length) { | ||
plugins = plugins.concat(babylonPlugins); | ||
} | ||
// use babylon.parse and then babel traverse for dynamic class names and other attributes on the jsx code. | ||
// might come up with a bit more strings but the needless stuff are not here anyway. | ||
let ast = parse(jsxFileContents, {sourceType: 'module', plugins: plugins}); | ||
traverse(ast, { | ||
enter: function (path) { | ||
let {type, value} = path.node; | ||
if (type === 'StringLiteral') { | ||
let attributes = value.split(' '); | ||
attributes.forEach(attr => jsxAttributes[attr] = attr); | ||
} | ||
} | ||
}); | ||
// use babylon.parse and then babel traverse for dynamic class names and other attributes on the jsx code. | ||
// might come up with a bit more strings but the needless stuff are not here anyway. | ||
let ast = parse(jsxFileContents, {sourceType: 'module', plugins: plugins}); | ||
traverse(ast, { | ||
enter: function (path) { | ||
let {type, value} = path.node; | ||
if (type === 'StringLiteral') { | ||
let attributes = value.split(' '); | ||
attributes.forEach(attr => jsxAttributes[attr] = attr); | ||
} | ||
} | ||
}); | ||
return jsxAttributes; | ||
return jsxAttributes; | ||
}; | ||
let validateInput = (cssFilePath, threshold) => { | ||
if (!cssFilePath) { | ||
throw new PluginError(PLUGIN_NAME, 'Missing css field!'); | ||
} | ||
if (typeof cssFilePath !== 'string') { | ||
throw new PluginError(PLUGIN_NAME, 'css field must be a string!'); | ||
} | ||
if(threshold && (typeof threshold !== 'number' || threshold > 100 || threshold < 0)){ | ||
throw new PluginError(PLUGIN_NAME, 'threshold value should be a number between 0-100!'); | ||
} | ||
}; | ||
let gulpCssUsage = (options = {}) => { | ||
let {css: cssFilePath, babylon} = options; | ||
if (!cssFilePath) { | ||
throw new PluginError(PLUGIN_NAME, 'Missing css field!'); | ||
} | ||
if(typeof cssFilePath !== 'string'){ | ||
throw new PluginError(PLUGIN_NAME, 'css field must be a string!'); | ||
} | ||
let {css: cssFilePath, babylon, threshold} = options; | ||
let cssFile = new gutil.File({path: cssFilePath, contents: fs.readFileSync(cssFilePath)}); | ||
let cssSelectors = getAllSelectorsFromCSSFile(cssFile); | ||
let fileBuffer; | ||
let allAttributes = {}; | ||
let transformers = (file, enc, cb) => { | ||
let currentJsxAttributes; | ||
if (file.isNull()) { | ||
return cb(error, file); | ||
} | ||
if (file.isBuffer()) { | ||
currentJsxAttributes = parseAndExtractJsxAttributes(file.contents.toString(), babylon); | ||
Object.assign(allAttributes, currentJsxAttributes); | ||
cb(error, file); | ||
} | ||
if (file.isStream()) { | ||
gutil.log(gutil.colors.magenta(PLUGIN_NAME + ': streaming is deprecated and will not be supported soon')); | ||
if (enc !== 'utf8') { | ||
file.contents.setEncoding('utf8'); | ||
} | ||
if (!fileBuffer) { | ||
fileBuffer = new Buffer([]); // utf8 by default | ||
} | ||
validateInput(cssFilePath, threshold); | ||
file.contents.on('data', chunk => fileBuffer = Buffer.concat([new Buffer(chunk), fileBuffer])); | ||
file.contents.on('end', () => { | ||
currentJsxAttributes = parseAndExtractJsxAttributes(fileBuffer.toString(), babylon); | ||
Object.assign(allAttributes, currentJsxAttributes); | ||
fileBuffer = undefined; | ||
cb(error, file); | ||
}); | ||
} | ||
}; | ||
let cssFile = new gutil.File({path: cssFilePath, contents: fs.readFileSync(cssFilePath)}); | ||
let cssSelectors = getAllSelectorsFromCSSFile(cssFile); | ||
let flush = (cb) => { | ||
let needless = makeDiff(cssSelectors, allAttributes); | ||
printNeedlessSelectorList(needless); | ||
cb(); | ||
}; | ||
return through.obj((file, enc, cb) => { | ||
let currentJsxAttributes; | ||
if (file.isNull()) { | ||
return cb(error, file); | ||
} | ||
if (file.isStream()) { | ||
return cb(new PluginError(PLUGIN_NAME, 'Streaming not supported')); | ||
} | ||
return through.obj({}, transformers, flush); | ||
let allAttributes = {}; | ||
// file is buffer | ||
currentJsxAttributes = parseAndExtractJsxAttributes(file.contents.toString(), babylon); | ||
Object.assign(allAttributes, currentJsxAttributes); | ||
let {needless, statistics} = makeDiff(cssSelectors, allAttributes); | ||
printNeedlessSelectorList(needless, statistics); | ||
if(threshold && statistics >= threshold){ | ||
return cb(new PluginError(PLUGIN_NAME, 'too many unused css selectors!')); | ||
} | ||
cb(error, file); | ||
}); | ||
}; | ||
@@ -124,25 +125,25 @@ | ||
if (typeof Object.assign != 'function') { | ||
(function () { | ||
Object.assign = function (target) { | ||
'use strict'; | ||
if (target === undefined || target === null) { | ||
throw new TypeError('Cannot convert undefined or null to object'); | ||
} | ||
(function () { | ||
Object.assign = function (target) { | ||
'use strict'; | ||
if (target === undefined || target === null) { | ||
throw new TypeError('Cannot convert undefined or null to object'); | ||
} | ||
var output = Object(target); | ||
for (var index = 1; index < arguments.length; index++) { | ||
var source = arguments[index]; | ||
if (source !== undefined && source !== null) { | ||
for (var nextKey in source) { | ||
if (source.hasOwnProperty(nextKey)) { | ||
output[nextKey] = source[nextKey]; | ||
} | ||
} | ||
} | ||
} | ||
return output; | ||
}; | ||
})(); | ||
var output = Object(target); | ||
for (var index = 1; index < arguments.length; index++) { | ||
var source = arguments[index]; | ||
if (source !== undefined && source !== null) { | ||
for (var nextKey in source) { | ||
if (source.hasOwnProperty(nextKey)) { | ||
output[nextKey] = source[nextKey]; | ||
} | ||
} | ||
} | ||
} | ||
return output; | ||
}; | ||
})(); | ||
} | ||
export default gulpCssUsage; |
{ | ||
"name": "gulp-css-usage", | ||
"version": "1.0.10", | ||
"version": "2.0.0", | ||
"description": "A Gulp task which scans your JavaScript classes, including React JSX files support (.jsx/.js), your CSS files, and gives you a report of CSS coverage. i.e how many class names are needless and which are those class names.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -41,2 +41,9 @@ # gulp-css-usage | ||
### threshold | ||
Type: 'Number', (between 0-100) | ||
If set, `gulp-css-usage` will check the amount of unused selectors, and if the amount of it is above the threshold then it fails the task. | ||
### babylon | ||
@@ -43,0 +50,0 @@ Type: `Array:String` Default: `['jsx', 'flow', 'classProperties']` |
Sorry, the diff of this file is not supported yet
16219
76
258