├── .babelrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE-MIT ├── README.md ├── package.json └── src ├── __tests__ └── index.js ├── index.js └── lib └── formatter.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-loose", "stage-0"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "development": { 6 | "sourceMaps": "inline" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{json,yml}] 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | npm-debug.log 5 | dist 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | matrix: 5 | include: 6 | - node_js: '5' 7 | - node_js: '4' 8 | - node_js: '0.12' 9 | env: NO_ESLINT=true 10 | 11 | script: "[[ $NO_ESLINT == true ]] && npm run test-012 || npm test" 12 | 13 | after_success: 14 | - './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.1.1 2 | 3 | * Fixes an issue where the module would crash when trying to format messages 4 | from sources other than itself. Now, any messages that were not created by 5 | this module are ignored. 6 | 7 | # 1.1.0 8 | 9 | * Added an option to show a summary of execution time across many different 10 | files (thanks to @export-mike). 11 | 12 | # 1.0.0 13 | 14 | * Initial release. 15 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Ben Briggs (http://beneb.info) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [postcss][postcss]-devtools [![Build Status](https://travis-ci.org/postcss/postcss-devtools.svg?branch=master)][ci] [![NPM version](https://badge.fury.io/js/postcss-devtools.svg)][npm] 2 | 3 | > Log execution time for each plugin in a PostCSS instance. 4 | 5 | 6 | ## Install 7 | 8 | With [npm](https://npmjs.org/package/postcss-devtools) do: 9 | 10 | ``` 11 | npm install postcss-devtools --save-dev 12 | ``` 13 | 14 | 15 | ## Example 16 | 17 | Load postcss-devtools into a PostCSS instance and it will wrap each plugin 18 | with a function that logs the time taken for the plugin to perform its 19 | required task. Note that this plugin must be used with the asynchronous API: 20 | 21 | ```js 22 | var postcss = require('postcss'); 23 | var devtools = require('postcss-devtools'); 24 | var autoprefixer = require('autoprefixer'); 25 | 26 | var css = 'h1 { color: red }'; 27 | 28 | postcss([devtools(), autoprefixer()]).process(css).then(function (result) { 29 | console.log('Done.'); 30 | }); 31 | 32 | //=> autoprefixer 37 ms 33 | //=> Done. 34 | ``` 35 | 36 | 37 | ## API 38 | 39 | ### devtools([options]) 40 | 41 | #### options 42 | 43 | ##### precise 44 | 45 | Type: `boolean` 46 | Default: `false` 47 | 48 | This adds extra precision to the times that are reported. 49 | 50 | ##### silent 51 | 52 | Type: `boolean` 53 | Default: `false` 54 | 55 | Set this to `true` to use your own logger for the output of this module. 56 | 57 | ### devtools.printSummary() 58 | 59 | Print a summary spanning across all files. Note that you should set 60 | `opts.silent` to `true` to avoid outputting more than is necessary when calling 61 | this function. 62 | 63 | ```js 64 | var postcss = require('postcss'); 65 | var devtools = require('postcss-devtools')({silent: true}); // disable summary for each css file 66 | var autoprefixer = require('autoprefixer'); 67 | 68 | var cssOne = 'h1 { color: red }'; 69 | var cssTwo = 'h1 { color: blue }'; 70 | // View a summary for all plugins across all css files 71 | Promise.all( 72 | [ 73 | postcss([devtools, autoprefixer()]).process(cssOne), 74 | postcss([devtools, autoprefixer()]).process(cssTwo) 75 | ] 76 | ).then(() => { 77 | console.log('Done.'); 78 | devtools.printSummary(); 79 | }); 80 | //=> Done. 81 | //=> Summary 82 | //=> autoprefixer 73 ms 83 | ``` 84 | 85 | 86 | ## Usage 87 | 88 | See the [PostCSS documentation](https://github.com/postcss/postcss#usage) for 89 | examples for your environment. 90 | 91 | 92 | ## Contributing 93 | 94 | Pull requests are welcome. If you add functionality, then please add unit tests 95 | to cover it. 96 | 97 | 98 | ## License 99 | 100 | MIT © [Ben Briggs](http://beneb.info) 101 | 102 | 103 | [ci]: https://travis-ci.org/postcss/postcss-devtools 104 | [npm]: http://badge.fury.io/js/postcss-devtools 105 | [postcss]: https://github.com/postcss/postcss 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-devtools", 3 | "version": "1.1.1", 4 | "description": "Log execution time for each plugin in a PostCSS instance.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "pretest": "eslint src", 8 | "prepublish": "del-cli dist && BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/", 9 | "report": "nyc report --reporter=html", 10 | "test": "nyc ava src/__tests__/", 11 | "test-012": "nyc ava src/__tests__/" 12 | }, 13 | "files": [ 14 | "LICENSE-MIT", 15 | "dist" 16 | ], 17 | "keywords": [ 18 | "css", 19 | "postcss", 20 | "postcss-plugin", 21 | "measure", 22 | "tool", 23 | "development" 24 | ], 25 | "license": "MIT", 26 | "devDependencies": { 27 | "ava": "^0.17.0", 28 | "babel-cli": "^6.4.5", 29 | "babel-core": "^6.4.5", 30 | "babel-plugin-add-module-exports": "^0.2.0", 31 | "babel-preset-es2015": "^6.3.13", 32 | "babel-preset-es2015-loose": "^7.0.0", 33 | "babel-preset-stage-0": "^6.3.13", 34 | "babel-register": "^6.9.0", 35 | "coveralls": "^2.11.6", 36 | "del-cli": "^0.2.0", 37 | "eslint": "^3.0.0", 38 | "eslint-config-cssnano": "^3.0.0", 39 | "eslint-plugin-babel": "^3.3.0", 40 | "eslint-plugin-import": "^1.10.2", 41 | "nyc": "^10.0.0" 42 | }, 43 | "homepage": "https://github.com/postcss/postcss-devtools", 44 | "author": { 45 | "name": "Ben Briggs", 46 | "email": "beneb.info@gmail.com", 47 | "url": "http://beneb.info" 48 | }, 49 | "repository": "postcss/postcss-devtools", 50 | "dependencies": { 51 | "chalk": "^1.1.1", 52 | "convert-hrtime": "^1.0.0", 53 | "is-promise": "^2.1.0", 54 | "jsprim": "^1.2.2", 55 | "postcss": "^5.0.14", 56 | "postcss-reporter": "^1.3.0", 57 | "pretty-hrtime": "^1.0.1", 58 | "text-table": "^0.2.0" 59 | }, 60 | "ava": { 61 | "require": "babel-register" 62 | }, 63 | "eslintConfig": { 64 | "extends": "cssnano" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import ava from 'ava'; 2 | import postcss from 'postcss'; 3 | import plugin from '../'; 4 | import {name} from '../../package.json'; 5 | 6 | const fixture = 'h1{color:blue}'; 7 | 8 | let backgroundify = postcss.plugin('backgroundify', () => { 9 | return css => { 10 | css.walkDecls(decl => (decl.prop = 'background')); 11 | }; 12 | }); 13 | 14 | let redify = postcss.plugin('redify', () => { 15 | return css => { 16 | return new Promise(resolve => { 17 | setTimeout(() => { 18 | css.walkDecls(decl => (decl.value = 'red')); 19 | return resolve(); 20 | }, 50); 21 | }); 22 | }; 23 | }); 24 | 25 | ava('should not print anything if no plugins', t => { 26 | return postcss([plugin()]).process(fixture).then(result => { 27 | t.deepEqual(result.messages.length, 0); 28 | }); 29 | }); 30 | 31 | ava('should work with sync plugins', t => { 32 | let processors = [ 33 | plugin({silent: true}), 34 | backgroundify, 35 | ]; 36 | 37 | return postcss(processors).process(fixture).then(result => { 38 | t.deepEqual(result.messages.length, 1); 39 | }); 40 | }); 41 | 42 | ava('should work with async plugins', t => { 43 | let processors = [ 44 | plugin({silent: true}), 45 | redify(), 46 | ]; 47 | 48 | return postcss(processors).process(fixture).then(result => { 49 | t.deepEqual(result.messages.length, 1); 50 | }); 51 | }); 52 | 53 | ava('should have printSummary method', t => { 54 | const devTool = plugin({silent: true}); 55 | let processors = [ 56 | devTool, 57 | redify(), 58 | backgroundify(), 59 | ]; 60 | 61 | return postcss(processors).process(fixture).then(() => { 62 | t.is(typeof devTool.printSummary, 'function'); 63 | devTool.printSummary(); 64 | }); 65 | }); 66 | 67 | ava('should have printSummary method with multiple css', t => { 68 | const devTool = plugin({silent: true}); 69 | let processors = [ 70 | devTool, 71 | redify(), 72 | backgroundify(), 73 | ]; 74 | 75 | // emulate a Runners callback function 76 | return Promise.all([ 77 | postcss(processors).process(fixture), 78 | postcss(processors).process(fixture), 79 | ]) 80 | .then(() => { 81 | t.is(typeof devTool.printSummary, 'function'); 82 | devTool.printSummary(); 83 | }); 84 | }); 85 | 86 | ava('should print different colours for the slower plugins', t => { 87 | let processors = [ 88 | plugin(), 89 | redify(), 90 | redify(), 91 | redify(), 92 | redify(), 93 | backgroundify(), 94 | backgroundify(), 95 | backgroundify(), 96 | backgroundify(), 97 | backgroundify(), 98 | backgroundify(), 99 | ]; 100 | 101 | return postcss(processors).process(fixture).then(result => { 102 | t.deepEqual(result.messages.length, 10); 103 | }); 104 | }); 105 | 106 | ava('should work with filenames', t => { 107 | let processors = [ 108 | plugin(), 109 | backgroundify, 110 | ]; 111 | 112 | return postcss(processors).process(fixture, {from: 'app.css'}).then(result => { 113 | t.deepEqual(result.messages.length, 1); 114 | }); 115 | }); 116 | 117 | ava('should ignore unrelated messages', t => { 118 | let processors = [ 119 | plugin(), 120 | postcss.plugin('unrelated', () => (css, result) => result.warn('Unrelated message!', {node: result.root})), 121 | ]; 122 | 123 | return postcss(processors).process(fixture).then(result => { 124 | t.deepEqual(result.messages.length, 2); 125 | }); 126 | }); 127 | 128 | ava('should use the postcss plugin api', t => { 129 | t.truthy(plugin().postcssVersion, 'should be able to access version'); 130 | t.deepEqual(plugin().postcssPlugin, name, 'should be able to access name'); 131 | }); 132 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import pretty from 'pretty-hrtime'; 3 | import reporter from 'postcss-reporter'; 4 | import convert from 'convert-hrtime'; 5 | import isPromise from 'is-promise'; 6 | import {hrtimeAdd} from 'jsprim'; 7 | import formatter, {formatSummaryResults} from './lib/formatter'; 8 | 9 | const updateSummary = (summary, {plugin, rawTime}, {precise}) => { 10 | const summaryForPlugin = summary[plugin] || {rawTime: [0, 0]}; 11 | const newRawTime = hrtimeAdd(summaryForPlugin.rawTime, rawTime); 12 | const formatted = pretty(newRawTime, {precise}); 13 | const text = 'Completed in ' + pretty(newRawTime, {precise}); 14 | const time = convert(newRawTime); 15 | return { 16 | ...summary, 17 | [plugin]: {rawTime: newRawTime, formatted, text, time}, 18 | }; 19 | }; 20 | 21 | function getMessage (plugin, completed, precise) { 22 | const formatted = pretty(completed, {precise}); 23 | return { 24 | plugin, 25 | formatted, 26 | text: `Completed in ${formatted}`, 27 | time: convert(completed), 28 | rawTime: completed, 29 | _devtools: true, 30 | }; 31 | } 32 | 33 | export default postcss.plugin('postcss-devtools', (opts) => { 34 | const {precise, silent} = { 35 | precise: false, 36 | silent: false, 37 | ...opts, 38 | }; 39 | 40 | let summaryResults = {}; 41 | 42 | const devtools = (css, result) => { 43 | result.processor.plugins = result.processor.plugins.map(proc => { 44 | if (proc.postcssPlugin === 'postcss-devtools') { 45 | return proc; 46 | } 47 | let wrappedPlugin = (styles, res) => { 48 | let completed; 49 | return new Promise(resolve => { 50 | const timer = process.hrtime(); 51 | const p = proc(styles, res); 52 | if (isPromise(p)) { 53 | p.then(() => { 54 | completed = process.hrtime(timer); 55 | const message = getMessage(proc.postcssPlugin, completed, precise); 56 | res.messages.push(message); 57 | summaryResults = updateSummary(summaryResults, message, {precise, silent}); 58 | resolve(); 59 | }); 60 | } else { 61 | completed = process.hrtime(timer); 62 | const message = getMessage(proc.postcssPlugin, completed, precise); 63 | res.messages.push(message); 64 | summaryResults = updateSummary(summaryResults, message, {precise, silent}); 65 | resolve(); 66 | } 67 | }); 68 | }; 69 | 70 | wrappedPlugin.postcssPlugin = proc.postcssPlugin; 71 | wrappedPlugin.postcssVersion = proc.postcssVersion; 72 | 73 | return wrappedPlugin; 74 | }); 75 | 76 | if (!silent) { 77 | result.processor.use(reporter({formatter: formatter})); 78 | } 79 | }; 80 | 81 | devtools.printSummary = () => { 82 | console.log(formatSummaryResults(summaryResults)); 83 | }; 84 | 85 | return devtools; 86 | }); 87 | -------------------------------------------------------------------------------- /src/lib/formatter.js: -------------------------------------------------------------------------------- 1 | import {relative} from 'path'; 2 | import {red, yellow, green, underline} from 'chalk'; 3 | import table from 'text-table'; 4 | 5 | let logFrom = fromValue => { 6 | if (!fromValue.indexOf('<')) { 7 | return fromValue; 8 | } 9 | return relative(process.cwd(), fromValue); 10 | }; 11 | 12 | function createResultsTable (messages) { 13 | const sorted = messages.sort((a, b) => b.time.s - a.time.s); 14 | const ten = Math.floor(sorted.length * 0.10); 15 | const twenty = Math.floor(sorted.length * 0.20); 16 | const output = table(sorted.map((message, index) => { 17 | if (index < ten) { 18 | return [message.plugin, red(message.formatted)]; 19 | } 20 | if (index < twenty) { 21 | return [message.plugin, yellow(message.formatted)]; 22 | } 23 | return [message.plugin, green(message.formatted)]; 24 | })); 25 | 26 | return output; 27 | } 28 | 29 | export default function formatter (input) { 30 | const output = createResultsTable(input.messages.filter(m => m._devtools)); 31 | return `${underline(logFrom(input.source))}\n${output}`; 32 | }; 33 | 34 | export function formatSummaryResults (resultsMap) { 35 | const results = Object.keys(resultsMap).map(k => ({plugin: k, ...resultsMap[k]})); 36 | const output = createResultsTable(results); 37 | return `${underline(`Summary`)}\n${output}`; 38 | }; 39 | --------------------------------------------------------------------------------