cleancss 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. #!/usr/bin/env node
  2. var fs = require('fs');
  3. var path = require('path');
  4. var CleanCSS = require('../index');
  5. var commands = require('commander');
  6. var packageConfig = fs.readFileSync(path.join(path.dirname(fs.realpathSync(process.argv[1])), '../package.json'));
  7. var buildVersion = JSON.parse(packageConfig).version;
  8. var isWindows = process.platform == 'win32';
  9. var lineBreak = require('os').EOL;
  10. // Specify commander options to parse command line params correctly
  11. commands
  12. .version(buildVersion, '-v, --version')
  13. .usage('[options] source-file, [source-file, ...]')
  14. .option('-b, --keep-line-breaks', 'Keep line breaks')
  15. .option('-c, --compatibility [ie7|ie8]', 'Force compatibility mode (see Readme for advanced examples)')
  16. .option('-d, --debug', 'Shows debug information (minification time & compression efficiency)')
  17. .option('-o, --output [output-file]', 'Use [output-file] as output instead of STDOUT')
  18. .option('-r, --root [root-path]', 'Set a root path to which resolve absolute @import rules')
  19. .option('-s, --skip-import', 'Disable @import processing')
  20. .option('-t, --timeout [seconds]', 'Per connection timeout when fetching remote @imports (defaults to 5 seconds)')
  21. .option('--rounding-precision [n]', 'Rounds to `N` decimal places. Defaults to 2. -1 disables rounding', parseInt)
  22. .option('--s0', 'Remove all special comments, i.e. /*! comment */')
  23. .option('--s1', 'Remove all special comments but the first one')
  24. .option('--semantic-merging', 'Enables unsafe mode by assuming BEM-like semantic stylesheets (warning, this may break your styling!)')
  25. .option('--skip-advanced', 'Disable advanced optimizations - ruleset reordering & merging')
  26. .option('--skip-aggressive-merging', 'Disable properties merging based on their order')
  27. .option('--skip-import-from [rules]', 'Disable @import processing for specified rules', function (val) { return val.split(','); }, [])
  28. .option('--skip-media-merging', 'Disable @media merging')
  29. .option('--skip-rebase', 'Disable URLs rebasing')
  30. .option('--skip-restructuring', 'Disable restructuring optimizations')
  31. .option('--skip-shorthand-compacting', 'Disable shorthand compacting')
  32. .option('--source-map', 'Enables building input\'s source map')
  33. .option('--source-map-inline-sources', 'Enables inlining sources inside source maps');
  34. commands.on('--help', function () {
  35. console.log(' Examples:\n');
  36. console.log(' %> cleancss one.css');
  37. console.log(' %> cleancss -o one-min.css one.css');
  38. if (isWindows) {
  39. console.log(' %> type one.css two.css three.css | cleancss -o merged-and-minified.css');
  40. } else {
  41. console.log(' %> cat one.css two.css three.css | cleancss -o merged-and-minified.css');
  42. console.log(' %> cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz');
  43. }
  44. console.log('');
  45. process.exit();
  46. });
  47. commands.parse(process.argv);
  48. // If no sensible data passed in just print help and exit
  49. var fromStdin = !process.env.__DIRECT__ && !process.stdin.isTTY;
  50. if (!fromStdin && commands.args.length === 0) {
  51. commands.outputHelp();
  52. return 0;
  53. }
  54. // Now coerce commands into CleanCSS configuration...
  55. var options = {
  56. advanced: commands.skipAdvanced ? false : true,
  57. aggressiveMerging: commands.skipAggressiveMerging ? false : true,
  58. compatibility: commands.compatibility,
  59. debug: commands.debug,
  60. inliner: commands.timeout ? { timeout: parseFloat(commands.timeout) * 1000 } : undefined,
  61. keepBreaks: !!commands.keepLineBreaks,
  62. keepSpecialComments: commands.s0 ? 0 : (commands.s1 ? 1 : '*'),
  63. mediaMerging: commands.skipMediaMerging ? false : true,
  64. processImport: commands.skipImport ? false : true,
  65. processImportFrom: processImportFrom(commands.skipImportFrom),
  66. rebase: commands.skipRebase ? false : true,
  67. restructuring: commands.skipRestructuring ? false : true,
  68. root: commands.root,
  69. roundingPrecision: commands.roundingPrecision,
  70. semanticMerging: commands.semanticMerging ? true : false,
  71. shorthandCompacting: commands.skipShorthandCompacting ? false : true,
  72. sourceMap: commands.sourceMap,
  73. sourceMapInlineSources: commands.sourceMapInlineSources,
  74. target: commands.output
  75. };
  76. if (options.root || commands.args.length > 0) {
  77. var relativeTo = options.root || commands.args[0];
  78. if (isRemote(relativeTo)) {
  79. options.relativeTo = relativeTo;
  80. } else {
  81. var resolvedRelativeTo = path.resolve(relativeTo);
  82. options.relativeTo = fs.statSync(resolvedRelativeTo).isFile() ?
  83. path.dirname(resolvedRelativeTo) :
  84. resolvedRelativeTo;
  85. }
  86. }
  87. if (options.sourceMap && !options.target) {
  88. outputFeedback(['Source maps will not be built because you have not specified an output file.'], true);
  89. options.sourceMap = false;
  90. }
  91. // ... and do the magic!
  92. if (commands.args.length > 0) {
  93. minify(commands.args);
  94. } else {
  95. var stdin = process.openStdin();
  96. stdin.setEncoding('utf-8');
  97. var data = '';
  98. stdin.on('data', function (chunk) {
  99. data += chunk;
  100. });
  101. stdin.on('end', function () {
  102. minify(data);
  103. });
  104. }
  105. function isRemote(path) {
  106. return /^https?:\/\//.test(path) || /^\/\//.test(path);
  107. }
  108. function processImportFrom(rules) {
  109. if (rules.length === 0) {
  110. return ['all'];
  111. } else if (rules.length == 1 && rules[0] == 'all') {
  112. return [];
  113. } else {
  114. return rules.map(function (rule) {
  115. if (rule == 'local')
  116. return 'remote';
  117. else if (rule == 'remote')
  118. return 'local';
  119. else
  120. return '!' + rule;
  121. });
  122. }
  123. }
  124. function minify(data) {
  125. new CleanCSS(options).minify(data, function (errors, minified) {
  126. if (options.debug) {
  127. console.error('Original: %d bytes', minified.stats.originalSize);
  128. console.error('Minified: %d bytes', minified.stats.minifiedSize);
  129. console.error('Efficiency: %d%', ~~(minified.stats.efficiency * 10000) / 100.0);
  130. console.error('Time spent: %dms', minified.stats.timeSpent);
  131. }
  132. outputFeedback(minified.errors, true);
  133. outputFeedback(minified.warnings);
  134. if (minified.errors.length > 0)
  135. process.exit(1);
  136. if (minified.sourceMap) {
  137. var mapFilename = path.basename(options.target) + '.map';
  138. output(minified.styles + lineBreak + '/*# sourceMappingURL=' + mapFilename + ' */');
  139. outputMap(minified.sourceMap, mapFilename);
  140. } else {
  141. output(minified.styles);
  142. }
  143. });
  144. }
  145. function output(minified) {
  146. if (options.target)
  147. fs.writeFileSync(options.target, minified, 'utf8');
  148. else
  149. process.stdout.write(minified);
  150. }
  151. function outputMap(sourceMap, mapFilename) {
  152. var mapPath = path.join(path.dirname(options.target), mapFilename);
  153. fs.writeFileSync(mapPath, sourceMap.toString(), 'utf-8');
  154. }
  155. function outputFeedback(messages, isError) {
  156. var prefix = isError ? '\x1B[31mERROR\x1B[39m:' : 'WARNING:';
  157. messages.forEach(function (message) {
  158. console.error('%s %s', prefix, message);
  159. });
  160. }