├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── public └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Springload 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSS Reporter 2 | 3 | Based on [The Specificity Graph](http://csswizardry.com/2014/10/the-specificity-graph/) by Harry Roberts and Katie Fenn's [parker](https://github.com/katiefenn/parker) 4 | 5 |  6 | 7 | 8 | ## Usage 9 | 10 | ```bash 11 | > sudo npm install -g css-reporter 12 | > css-reporter "glob/to/css/files/**/*.css" 13 | ``` 14 | 15 | And visit `localhost:9000` in your browser 16 | 17 | 18 | ## Known issues 19 | 20 | * Selectors inside media queries don't behave properly 21 | * !importants are ignored (they're a rule, not a selector) 22 | * Some issues with ID selectors not getting picked up. 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require("fs"); 4 | var css = require('css'); 5 | var Parker = require('parker'); 6 | var metrics = require("parker/metrics/All.js"); 7 | var glob = require("glob"); 8 | var SpecificityPerSelector; 9 | 10 | // Filename may very well change, so let's just look for the id 11 | metrics.map(function(item) { 12 | if (item.id === "specificity-per-selector") { 13 | SpecificityPerSelector = item; 14 | } 15 | }); 16 | 17 | // Some sort of globby default 18 | var globString = "public/assets/**/*.css"; 19 | pattern = process.argv[2] || globString; 20 | 21 | 22 | 23 | function generateStylesheetReport(filePaths) { 24 | 25 | var stylesheetResults = []; 26 | 27 | function parseStyleSheet(item) { 28 | var stylesheet; 29 | var message; 30 | 31 | try { 32 | stylesheet = fs.readFileSync(item, "utf-8"); 33 | console.log("Inspecting ", item); 34 | } catch(e) { 35 | message = "Couldn't read", item; 36 | stylesheetResults.push({ errors: message, path: item }); 37 | console.log(message); 38 | return; 39 | } 40 | 41 | var obj; 42 | 43 | try { 44 | obj = css.parse(stylesheet); 45 | } catch(e) { 46 | message = "Couldn't parse " + item; 47 | stylesheetResults.push({ errors: message, path: item }); 48 | console.log(message); 49 | return; 50 | } 51 | 52 | var rules = obj.stylesheet.rules; 53 | var specificityMap = []; 54 | 55 | var _lines = []; 56 | var _specificity = []; 57 | var _selectors = []; 58 | 59 | function iterator(rule) { 60 | if (rule.selectors) { 61 | var specs = []; 62 | 63 | if (rule.selectors.length) { 64 | rule.selectors.forEach(function iterateSelectors(selector) { 65 | 66 | var res; 67 | // console.log(selector); 68 | try { 69 | res = SpecificityPerSelector.measure(selector); 70 | } catch (e) { 71 | console.log("couldn't decode selector ", selector); 72 | res = 0; 73 | // return; 74 | } 75 | 76 | _specificity.push(res); 77 | _lines.push(rule.position.start.line); 78 | _selectors.push(selector); 79 | 80 | specificityMap.push({ 81 | specificity: res, 82 | selector: selector, 83 | position: rule.position.start 84 | }); 85 | }); 86 | } 87 | } 88 | } 89 | 90 | rules.forEach(iterator); 91 | 92 | var chartData = { 93 | lines: _lines, 94 | specificity: _specificity, 95 | selectors: _selectors 96 | }; 97 | 98 | parker = new Parker(metrics); 99 | var report = parker.run(stylesheet); 100 | 101 | stylesheetResults.push({ 102 | path: item, 103 | data: specificityMap, 104 | chart: chartData, 105 | report: report 106 | }) 107 | } 108 | 109 | filePaths.forEach(parseStyleSheet); 110 | 111 | return stylesheetResults; 112 | }; 113 | 114 | 115 | 116 | 117 | var nunjucks = require("nunjucks"); 118 | var express = require("express"); 119 | var port = 9000; 120 | var app = express(); 121 | var webRoot = __dirname + "/public"; 122 | 123 | var viewEngine = new nunjucks.Environment(new nunjucks.FileSystemLoader(webRoot)); 124 | viewEngine.express(app); 125 | 126 | viewEngine.addFilter("array", function(data) { 127 | if (data instanceof Array) { 128 | return true; 129 | } else { 130 | return false; 131 | } 132 | }); 133 | 134 | viewEngine.addFilter("json", function(data) { 135 | try { 136 | return JSON.stringify(data); 137 | } catch(e) { 138 | 139 | } 140 | return ""; 141 | }); 142 | 143 | app.set('view engine', 'html'); 144 | app.use("/assets", express.static(__dirname + "static")); 145 | 146 | app.get("/", function index(req, res, next) { 147 | 148 | function onGetFiles(error, files) { 149 | var results = []; 150 | 151 | if (files.length) { 152 | results = generateStylesheetReport(files); 153 | } 154 | 155 | res.format({ 156 | html: function() { 157 | res.render('index', { 158 | stylesheets: results, 159 | pattern: pattern 160 | }); 161 | }, 162 | json: function() { 163 | res.json(results); 164 | } 165 | }); 166 | } 167 | 168 | glob(pattern, {}, onGetFiles); 169 | }); 170 | 171 | 172 | 173 | var server = app.listen(port, function () { 174 | console.log("CSS Report on http://localhost:" +port+""); 175 | }); 176 | 177 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-reporter", 3 | "version": "0.0.5", 4 | "description": "CSS Reporter", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "css-reporter": "./index.js" 11 | }, 12 | "scripts": { 13 | "start": "node ./index" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:springload/css-reporter.git" 18 | }, 19 | "keywords": [ 20 | "CSS", 21 | "Harry", 22 | "Roberts", 23 | "Specificity" 24 | ], 25 | "author": "Springload", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/springload/css-reporter/issues" 29 | }, 30 | "homepage": "https://github.com/springload/css-reporter", 31 | "dependencies": { 32 | "css": "^2.1.0", 33 | "glob": "^4.0.6", 34 | "express": "^4.10.1", 35 | "parker": "0.0.8", 36 | "nunjucks": "^1.1.0" 37 | }, 38 | "devDependencies": {} 39 | } 40 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |105 | 106 | Harry's article 107 | 108 |
109 |113 | 114 |
115 | 116 | {% if not stylesheets.length %} 117 |119 | Looking in {{ pattern }} 120 |
121 | {% endif %} 122 | 125 | {% for stylesheet in stylesheets %} 126 |129 | File {{ stylesheet.path }} 130 |
131 |157 | {{ v }} 158 |
159 | {% endfor %} 160 | {% endif %} 161 | {% if key == "unique-colours" %} 162 | 163 | 164 | {% for v in val %} 165 | 166 | 167 | 168 | {{ v }} 169 | 170 | 171 | {% endfor %} 172 | 173 | {% endif %} 174 | {% else %} 175 | {{ val }} 176 | {% endif %} 177 |