cleancss 6.7 KB

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