Skip to content

Commit b64750e

Browse files
committed
feat: initial functionality
0 parents  commit b64750e

File tree

13 files changed

+3143
-0
lines changed

13 files changed

+3143
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
coverage/
3+
npm-debug.log

.travis.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
language: node_js
2+
cache:
3+
directories:
4+
- node_modules
5+
notifications:
6+
email: false
7+
node_js:
8+
- v7
9+
- v6
10+
before_install:
11+
- npm install -g coveralls
12+
before_script:
13+
- npm prune
14+
script:
15+
- npm run test:lint
16+
- npm run test:coverage
17+
after_success:
18+
- cat ./coverage/lcov.info | coveralls || echo 'Failed to upload to coveralls...'
19+
# - npm run semantic-release

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Patrick Hulce <patrick.hulce@gmail.com> (https://patrickhulce.com/)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# prunedcss
2+
[![NPM Package](https://badge.fury.io/js/prunedcss.svg)](https://www.npmjs.com/package/prunedcss)
3+
[![Build Status](https://travis-ci.org/patrickhulce/prunedcss.svg?branch=master)](https://travis-ci.org/patrickhulce/prunedcss)
4+
[![Coverage Status](https://coveralls.io/repos/github/patrickhulce/prunedcss/badge.svg?branch=master)](https://coveralls.io/github/patrickhulce/prunedcss?branch=master)
5+
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
6+
[![Dependencies](https://david-dm.org/patrickhulce/prunedcss.svg)](https://david-dm.org/patrickhulce/prunedcss)
7+
8+
Eliminates unused CSS rules.

bin/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs')
4+
const prune = require('../lib/prune')
5+
const html = '<div class="foo-bar" id="something"></div>'
6+
const css = fs.readFileSync(__dirname + '/../test/fixtures/content.css', 'utf8')
7+
const pruned = prune(html, css)
8+
console.log(pruned)

lib/css-parser.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const _ = require('lodash')
2+
const gonzales = require('gonzales-pe')
3+
4+
const IGNORE_COMMENT_REGEX = /\bprunedcss:ignore\b/
5+
6+
class CssParser {
7+
constructor(content, corpra) {
8+
this._content = content
9+
this._corpra = corpra
10+
this._parsed = gonzales.parse(content)
11+
this._usedTokens = new Set()
12+
}
13+
14+
pruned() {
15+
let ignoreRules = false
16+
this._parsed.traverse((rule, index, parent) => {
17+
Object.defineProperty(rule, 'parent', {get: _.constant(parent)})
18+
19+
if (rule.type === 'multilineComment' && IGNORE_COMMENT_REGEX.test(rule.content)) {
20+
ignoreRules = !ignoreRules;
21+
} else if (rule.type === 'ruleset') {
22+
const selectors = []
23+
rule.traverseByType('selector', node => selectors.push(this.getSelectorTokens(node)))
24+
const usedSelectors = selectors.filter(s => this.findUsedSelector(s))
25+
if (usedSelectors.length === 0) {
26+
parent.removeChild(index)
27+
}
28+
}
29+
})
30+
31+
return this._parsed.toString()
32+
}
33+
34+
getSelectorTokens(parsed) {
35+
const tokens = []
36+
parsed.traverseByTypes(['id', 'class', 'typeSelector'], node => {
37+
const content = _.get(node, 'content.0.content', '')
38+
if (node.type === 'typeSelector' && ['html', 'body'].includes(content)) {
39+
return
40+
}
41+
42+
tokens.push(content)
43+
})
44+
45+
return tokens;
46+
}
47+
48+
findUsedSelector(tokens) {
49+
return _.every(tokens, token => {
50+
if (this._usedTokens.has(token)) {
51+
return true;
52+
}
53+
54+
const found = _.find(this._corpra, corpus => corpus.contains(token))
55+
if (found) {
56+
this._usedTokens.add(token)
57+
}
58+
59+
return found
60+
})
61+
}
62+
}
63+
64+
module.exports = CssParser

lib/prune.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const Source = require('./sources/source')
2+
const CssParser = require('./css-parser')
3+
4+
module.exports = function (content, css, options) {
5+
const source = new Source(content)
6+
return new CssParser(css, [source]).pruned()
7+
}

lib/sources/source.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Source {
2+
constructor(text) {
3+
this._text = text
4+
}
5+
6+
contains(selector) {
7+
return new RegExp(`\\b${selector}\\b`, 'i').test(this._text)
8+
}
9+
}
10+
11+
module.exports = Source

package.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "prunedcss",
3+
"version": "0.0.0",
4+
"description": "Eliminates unused CSS rules.",
5+
"main": "./lib/prune.js",
6+
"bin": {
7+
"bin/index.js": "prunedcss"
8+
},
9+
"scripts": {
10+
"test": "npm run test:lint && npm run test:unit",
11+
"test:unit": "mocha --reporter spec --require test/bootstrap test/*.test.js test/**/*.test.js",
12+
"test:lint": "xo ./lib/**/*.js ./test/**/*.js",
13+
"test:coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --require test/bootstrap test/*.test.js test/**/*.test.js",
14+
"test:watch": "mocha --watch --reporter dot --require test/bootstrap test/*.test.js test/**/*.test.js",
15+
"test:debug:watch": "node --inspect --debug-brk ./node_modules/.bin/mocha --watch --reporter dot --require test/bootstrap test/*.test.js test/**/*.test.js",
16+
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
17+
},
18+
"repository": {
19+
"type": "git",
20+
"url": "https://github.com/patrickhulce/prunedcss.git"
21+
},
22+
"author": "Patrick Hulce <patrick.hulce@gmail.com>",
23+
"license": "MIT",
24+
"bugs": {
25+
"url": "https://github.com/patrickhulce/prunedcss/issues"
26+
},
27+
"homepage": "https://github.com/patrickhulce/prunedcss#readme",
28+
"xo": {
29+
"env": [
30+
"node",
31+
"mocha"
32+
],
33+
"global": [
34+
"expect"
35+
]
36+
},
37+
"config": {
38+
"commitizen": {
39+
"path": "./node_modules/cz-conventional-changelog"
40+
}
41+
},
42+
"dependencies": {
43+
"gonzales-pe": "^4.0.3",
44+
"lodash": "^4.17.4"
45+
},
46+
"devDependencies": {
47+
"chai": "^3.5.0",
48+
"istanbul": "^0.4.5",
49+
"mocha": "^3.2.0",
50+
"semantic-release": "^6.3.2",
51+
"sinon": "^1.17.7",
52+
"sinon-chai": "^2.8.0",
53+
"xo": "patrickhulce/xo#master"
54+
}
55+
}

test/bootstrap.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const chai = require('chai')
2+
chai.use(require('sinon-chai'))
3+
4+
global.expect = chai.expect

0 commit comments

Comments
 (0)