#!/usr/bin/env node var fs = require('fs'); var path = require('path'); var CleanCSS = require('../index'); var commands = require('commander'); var packageConfig = fs.readFileSync(path.join(path.dirname(fs.realpathSync(process.argv[1])), '../package.json')); var buildVersion = JSON.parse(packageConfig).version; var isWindows = process.platform == 'win32'; var lineBreak = require('os').EOL; // Specify commander options to parse command line params correctly commands .version(buildVersion, '-v, --version') .usage('[options] source-file, [source-file, ...]') .option('-b, --keep-line-breaks', 'Keep line breaks') .option('-c, --compatibility [ie7|ie8]', 'Force compatibility mode (see Readme for advanced examples)') .option('-d, --debug', 'Shows debug information (minification time & compression efficiency)') .option('-o, --output [output-file]', 'Use [output-file] as output instead of STDOUT') .option('-r, --root [root-path]', 'Set a root path to which resolve absolute @import rules') .option('-s, --skip-import', 'Disable @import processing') .option('-t, --timeout [seconds]', 'Per connection timeout when fetching remote @imports (defaults to 5 seconds)') .option('--rounding-precision [n]', 'Rounds to `N` decimal places. Defaults to 2. -1 disables rounding', parseInt) .option('--s0', 'Remove all special comments, i.e. /*! comment */') .option('--s1', 'Remove all special comments but the first one') .option('--semantic-merging', 'Enables unsafe mode by assuming BEM-like semantic stylesheets (warning, this may break your styling!)') .option('--skip-advanced', 'Disable advanced optimizations - ruleset reordering & merging') .option('--skip-aggressive-merging', 'Disable properties merging based on their order') .option('--skip-import-from [rules]', 'Disable @import processing for specified rules', function (val) { return val.split(','); }, []) .option('--skip-media-merging', 'Disable @media merging') .option('--skip-rebase', 'Disable URLs rebasing') .option('--skip-restructuring', 'Disable restructuring optimizations') .option('--skip-shorthand-compacting', 'Disable shorthand compacting') .option('--source-map', 'Enables building input\'s source map') .option('--source-map-inline-sources', 'Enables inlining sources inside source maps'); commands.on('--help', function () { console.log(' Examples:\n'); console.log(' %> cleancss one.css'); console.log(' %> cleancss -o one-min.css one.css'); if (isWindows) { console.log(' %> type one.css two.css three.css | cleancss -o merged-and-minified.css'); } else { console.log(' %> cat one.css two.css three.css | cleancss -o merged-and-minified.css'); console.log(' %> cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz'); } console.log(''); process.exit(); }); commands.parse(process.argv); // If no sensible data passed in just print help and exit var fromStdin = !process.env.__DIRECT__ && !process.stdin.isTTY; if (!fromStdin && commands.args.length === 0) { commands.outputHelp(); return 0; } // Now coerce commands into CleanCSS configuration... var options = { advanced: commands.skipAdvanced ? false : true, aggressiveMerging: commands.skipAggressiveMerging ? false : true, compatibility: commands.compatibility, debug: commands.debug, inliner: commands.timeout ? { timeout: parseFloat(commands.timeout) * 1000 } : undefined, keepBreaks: !!commands.keepLineBreaks, keepSpecialComments: commands.s0 ? 0 : (commands.s1 ? 1 : '*'), mediaMerging: commands.skipMediaMerging ? false : true, processImport: commands.skipImport ? false : true, processImportFrom: processImportFrom(commands.skipImportFrom), rebase: commands.skipRebase ? false : true, restructuring: commands.skipRestructuring ? false : true, root: commands.root, roundingPrecision: commands.roundingPrecision, semanticMerging: commands.semanticMerging ? true : false, shorthandCompacting: commands.skipShorthandCompacting ? false : true, sourceMap: commands.sourceMap, sourceMapInlineSources: commands.sourceMapInlineSources, target: commands.output }; if (options.root || commands.args.length > 0) { var relativeTo = options.root || commands.args[0]; if (isRemote(relativeTo)) { options.relativeTo = relativeTo; } else { var resolvedRelativeTo = path.resolve(relativeTo); options.relativeTo = fs.statSync(resolvedRelativeTo).isFile() ? path.dirname(resolvedRelativeTo) : resolvedRelativeTo; } } if (options.sourceMap && !options.target) { outputFeedback(['Source maps will not be built because you have not specified an output file.'], true); options.sourceMap = false; } // ... and do the magic! if (commands.args.length > 0) { minify(commands.args); } else { var stdin = process.openStdin(); stdin.setEncoding('utf-8'); var data = ''; stdin.on('data', function (chunk) { data += chunk; }); stdin.on('end', function () { minify(data); }); } function isRemote(path) { return /^https?:\/\//.test(path) || /^\/\//.test(path); } function processImportFrom(rules) { if (rules.length === 0) { return ['all']; } else if (rules.length == 1 && rules[0] == 'all') { return []; } else { return rules.map(function (rule) { if (rule == 'local') return 'remote'; else if (rule == 'remote') return 'local'; else return '!' + rule; }); } } function minify(data) { new CleanCSS(options).minify(data, function (errors, minified) { if (options.debug) { console.error('Original: %d bytes', minified.stats.originalSize); console.error('Minified: %d bytes', minified.stats.minifiedSize); console.error('Efficiency: %d%', ~~(minified.stats.efficiency * 10000) / 100.0); console.error('Time spent: %dms', minified.stats.timeSpent); } outputFeedback(minified.errors, true); outputFeedback(minified.warnings); if (minified.errors.length > 0) process.exit(1); if (minified.sourceMap) { var mapFilename = path.basename(options.target) + '.map'; output(minified.styles + lineBreak + '/*# sourceMappingURL=' + mapFilename + ' */'); outputMap(minified.sourceMap, mapFilename); } else { output(minified.styles); } }); } function output(minified) { if (options.target) fs.writeFileSync(options.target, minified, 'utf8'); else process.stdout.write(minified); } function outputMap(sourceMap, mapFilename) { var mapPath = path.join(path.dirname(options.target), mapFilename); fs.writeFileSync(mapPath, sourceMap.toString(), 'utf-8'); } function outputFeedback(messages, isError) { var prefix = isError ? '\x1B[31mERROR\x1B[39m:' : 'WARNING:'; messages.forEach(function (message) { console.error('%s %s', prefix, message); }); }