-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1bb4a2e
Showing
11 changed files
with
476 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
|
||
# Created by https://www.gitignore.io/api/node | ||
|
||
### Node ### | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules | ||
jspm_packages | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
language: node_js | ||
node_js: | ||
- "node" | ||
- "iojs" | ||
- "4.3" | ||
- "0.12" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Copyright (c) 2016, Anton Nesterov | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# usedcss | ||
|
||
>PostCSS plugin which helps you extract only used styles. Unlike [uncss](https://github.com/giakki/uncss) and others does not render your pages to find used classes, but instead parse it statically, which can be beneficial in some cases. Also support simple Angular's ng-class parsing. And also, due to awesomeness of PostCSS, it works with LESS and SCSS via PostCSS syntaxes. | ||
## Installation | ||
|
||
``` | ||
npm i usedcss --save | ||
``` | ||
|
||
## Options | ||
|
||
### html | ||
|
||
Type: `array` of [globs](https://github.com/isaacs/node-glob) | ||
*Required option* | ||
|
||
You must specify html files to check css selector usage against them. | ||
|
||
### ignore | ||
|
||
Type: `array` of `strings` | ||
|
||
Saves ignored selectors even if they are not presented in DOM. | ||
|
||
## ignoreRegexp | ||
|
||
Type: `array` of `regexps` | ||
|
||
Use it to save selectors based on regexp. | ||
|
||
## ngclass | ||
|
||
Type: `boolean` | ||
|
||
Default: `false` | ||
|
||
Parse or not to parse `ng-class` statements. | ||
|
||
## ignoreNesting | ||
|
||
Type: `boolean` | ||
|
||
Default: `false` | ||
|
||
Ignore nesting so `.class1 .class2` will be saved even if there is element with `class2`, but it's not nested with `class1`. Useful if you use templates. | ||
|
||
## Usage | ||
|
||
Check out [PostCSS documentation](https://github.com/postcss/postcss#usage) on how to use PostCSS plugins. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
'use strict'; | ||
const Promise = require('bluebird'); | ||
const fs = Promise.promisifyAll(require('fs')); | ||
const glob = Promise.promisify(require('multi-glob').glob); | ||
const postcss = require('postcss'); | ||
const noop = require('node-noop').noop; | ||
const cheerio = require('cheerio'); | ||
const expressions = require('angular-expressions'); | ||
const isRegex = require('is-regex'); | ||
|
||
module.exports = postcss.plugin('usedcss', (options) => { | ||
var htmls = []; | ||
return function(css) { | ||
return new Promise((resolve, reject) => { | ||
if (!options.html) { | ||
reject('No html files specified.'); | ||
return; | ||
} | ||
if (options.ignore && !Array.isArray(options.ignore)) { | ||
reject('ignore option should be an array.'); | ||
return; | ||
} | ||
if (options.ignoreRegexp && !Array.isArray(options.ignoreRegexp)) { | ||
reject('ignoreRegexp option should be an array.'); | ||
return; | ||
} | ||
if (options.ignoreRegexp && !options.ignoreRegexp.every(isRegex)) { | ||
reject('ignoreRegexp option should contain regular expressions.'); | ||
return; | ||
} | ||
if (options.ngclass && typeof options.ngclass != 'boolean') { | ||
reject('ngclass option should be boolean.'); | ||
return; | ||
} | ||
if (options.ignoreNesting && typeof options.ignoreNesting != 'boolean') { | ||
reject('ignoreNesting option should be boolean.'); | ||
return; | ||
} | ||
var promise; | ||
if (options.ignoreNesting && options.ignore) { | ||
promise = Promise.map(options.ignore, (item, i) => { | ||
options.ignore[i] = item.replace(/^.*( |>|<)/g, ''); | ||
}); | ||
} else { | ||
promise = Promise.resolve(); | ||
} | ||
promise.then(() => { | ||
return glob(options.html) | ||
.then((files) => { | ||
return Promise.map(files, (file) => { | ||
return fs.readFileAsync(file).then((content) => { | ||
htmls.push(cheerio.load(content.toString())); | ||
return Promise.resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(() => { | ||
if (options.ngclass) { | ||
return Promise.map(htmls, (html) => { | ||
html('[ng-class], [data-ng-class]').each((i, el) => { | ||
var cls = []; | ||
var ngcl = html(el).attr('ng-class'); | ||
if (ngcl) { | ||
cls = cls.concat( | ||
Object.keys(expressions.compile(ngcl)()) | ||
); | ||
} | ||
var datang = html(el).attr('data-ng-class'); | ||
if (datang) { | ||
cls = cls.concat( | ||
Object.keys(expressions.compile(datang)()) | ||
); | ||
} | ||
cls.forEach((cl) => { | ||
html(el).addClass(cl); | ||
}); | ||
}); | ||
return Promise.resolve(); | ||
}); | ||
} | ||
return Promise.resolve(); | ||
}) | ||
.then(() => { | ||
var promises = []; | ||
css.walkRules((rule) => { | ||
// ignore keyframes | ||
if ( | ||
rule.parent.type === 'atrule' && | ||
/keyframes/.test(rule.parent.name) | ||
) { | ||
return; | ||
} | ||
|
||
// if we found an element, we reject the promise and do nothing | ||
// promise is resolved if we found nothing after iteration | ||
// in this case, we remove a rule | ||
// sounds hacky, but it works | ||
promises.push( | ||
Promise.map(rule.selectors, (selector) => { | ||
var promise; | ||
if (options.ignoreRegexp) { | ||
promise = Promise.map(options.ignoreRegexp, (item) => { | ||
if (item.test(selector)) { | ||
return Promise.reject(); | ||
} | ||
}); | ||
} else { | ||
promise = Promise.resolve(); | ||
} | ||
return promise.then(() => { | ||
// remove pseudo-classes from selectors | ||
selector = selector.replace(/::?[a-zA-Z-]*$/g, ''); | ||
if (options.ignoreNesting) { | ||
selector = selector.replace(/^.*( |>|<)/g, '') | ||
} | ||
return Promise.map(htmls, (html) => { | ||
if ( | ||
(html(selector).length > 0 || | ||
( | ||
options.ignore && | ||
options.ignore.indexOf(selector) > -1) | ||
) | ||
) { | ||
return Promise.reject(); | ||
} | ||
return Promise.resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(() => { | ||
rule.remove(); | ||
return Promise.resolve(); | ||
}) | ||
.catch(noop) | ||
); | ||
}); | ||
return Promise.all(promises); | ||
}); | ||
}) | ||
.then(resolve) | ||
.catch(reject); | ||
}); | ||
}; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
{ | ||
"name": "usedcss", | ||
"version": "1.0.0", | ||
"description": "Extract only styles presented in your html files.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "./node_modules/.bin/eslint *.js tests/*.js && ./node_modules/jscs/bin/jscs *.js tests/*.js && ./node_modules/mocha/bin/mocha tests/*.tests.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/komachi/usedcss.git" | ||
}, | ||
"keywords": [ | ||
"css", | ||
"postcss", | ||
"less", | ||
"scss", | ||
"sass", | ||
"postcss-plugin", | ||
"optimization" | ||
], | ||
"author": "Anton Nesterov", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/komachi/usedcss/issues" | ||
}, | ||
"homepage": "https://nesterov.pw/usedcss", | ||
"eslintConfig": { | ||
"extends": "defaults", | ||
"env": { | ||
"node": true, | ||
"es6": true, | ||
"mocha": true | ||
} | ||
}, | ||
"jscsConfig": { | ||
"preset": "node-style-guide", | ||
"esnext": true, | ||
"verbose": true, | ||
"requireTrailingComma": null, | ||
"requireCapitalizedComments": null | ||
}, | ||
"dependencies": { | ||
"angular-expressions": "^0.3.0", | ||
"bluebird": "^3.3.4", | ||
"cheerio": "^0.20.0", | ||
"is-regex": "^1.0.3", | ||
"multi-glob": "^1.0.1", | ||
"node-noop": "^1.0.0", | ||
"postcss": "^5.0.19" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^2.7.0", | ||
"eslint-config-defaults": "^9.0.0", | ||
"expect": "^1.16.0", | ||
"jscs": "^2.11.0", | ||
"mocha": "^2.4.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
const expect = require('expect'); | ||
const runTest = require('./runTest.helper.js'); | ||
|
||
describe('errors', () => { | ||
it('Should retun error if there is no html files specified.', (done) => { | ||
runTest({html: null}).catch((err) => { | ||
expect(err).toBe('No html files specified.'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('Should retun error if ignore is not an array', (done) => { | ||
runTest({ignore: 'test'}).catch((err) => { | ||
expect(err).toBe('ignore option should be an array.'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('Should retun error if ignoreRegexp is not an array', (done) => { | ||
runTest({ignoreRegexp: 'test'}).catch((err) => { | ||
expect(err).toBe('ignoreRegexp option should be an array.'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('Should retun error if ignoreRegexp contains not a regexp', (done) => { | ||
runTest({ignoreRegexp: ['test']}).catch((err) => { | ||
expect(err).toBe( | ||
'ignoreRegexp option should contain regular expressions.' | ||
); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('Should retun error if ngclass is not boolean', (done) => { | ||
runTest({ngclass: 'test'}).catch((err) => { | ||
expect(err).toBe('ngclass option should be boolean.'); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('Should retun error if ignoreNesting is not boolean', (done) => { | ||
runTest({ignoreNesting: 'test'}).catch((err) => { | ||
expect(err).toBe('ignoreNesting option should be boolean.'); | ||
done(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.