clean.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * Clean-css - https://github.com/clean-css/clean-css
  3. * Released under the terms of MIT license
  4. */
  5. var level0Optimize = require('./optimizer/level-0/optimize');
  6. var level1Optimize = require('./optimizer/level-1/optimize');
  7. var level2Optimize = require('./optimizer/level-2/optimize');
  8. var validator = require('./optimizer/validator');
  9. var compatibilityFrom = require('./options/compatibility');
  10. var fetchFrom = require('./options/fetch');
  11. var formatFrom = require('./options/format').formatFrom;
  12. var inlineFrom = require('./options/inline');
  13. var inlineRequestFrom = require('./options/inline-request');
  14. var inlineTimeoutFrom = require('./options/inline-timeout');
  15. var OptimizationLevel = require('./options/optimization-level').OptimizationLevel;
  16. var optimizationLevelFrom = require('./options/optimization-level').optimizationLevelFrom;
  17. var pluginsFrom = require('./options/plugins');
  18. var rebaseFrom = require('./options/rebase');
  19. var rebaseToFrom = require('./options/rebase-to');
  20. var inputSourceMapTracker = require('./reader/input-source-map-tracker');
  21. var readSources = require('./reader/read-sources');
  22. var serializeStyles = require('./writer/simple');
  23. var serializeStylesAndSourceMap = require('./writer/source-maps');
  24. var CleanCSS = module.exports = function CleanCSS(options) {
  25. options = options || {};
  26. this.options = {
  27. batch: !!options.batch,
  28. compatibility: compatibilityFrom(options.compatibility),
  29. explicitRebaseTo: 'rebaseTo' in options,
  30. fetch: fetchFrom(options.fetch),
  31. format: formatFrom(options.format),
  32. inline: inlineFrom(options.inline),
  33. inlineRequest: inlineRequestFrom(options.inlineRequest),
  34. inlineTimeout: inlineTimeoutFrom(options.inlineTimeout),
  35. level: optimizationLevelFrom(options.level),
  36. plugins: pluginsFrom(options.plugins),
  37. rebase: rebaseFrom(options.rebase, options.rebaseTo),
  38. rebaseTo: rebaseToFrom(options.rebaseTo),
  39. returnPromise: !!options.returnPromise,
  40. sourceMap: !!options.sourceMap,
  41. sourceMapInlineSources: !!options.sourceMapInlineSources
  42. };
  43. };
  44. // for compatibility with optimize-css-assets-webpack-plugin
  45. CleanCSS.process = function(input, opts) {
  46. var cleanCss;
  47. var optsTo = opts.to;
  48. delete opts.to;
  49. cleanCss = new CleanCSS(Object.assign({
  50. returnPromise: true, rebaseTo: optsTo
  51. }, opts));
  52. return cleanCss.minify(input)
  53. .then(function(output) {
  54. return { css: output.styles };
  55. });
  56. };
  57. CleanCSS.prototype.minify = function(input, maybeSourceMap, maybeCallback) {
  58. var options = this.options;
  59. if (options.returnPromise) {
  60. return new Promise(function(resolve, reject) {
  61. minifyAll(input, options, maybeSourceMap, function(errors, output) {
  62. return errors
  63. ? reject(errors)
  64. : resolve(output);
  65. });
  66. });
  67. }
  68. return minifyAll(input, options, maybeSourceMap, maybeCallback);
  69. };
  70. function minifyAll(input, options, maybeSourceMap, maybeCallback) {
  71. if (options.batch && Array.isArray(input)) {
  72. return minifyInBatchesFromArray(input, options, maybeSourceMap, maybeCallback);
  73. } if (options.batch && (typeof input == 'object')) {
  74. return minifyInBatchesFromHash(input, options, maybeSourceMap, maybeCallback);
  75. }
  76. return minify(input, options, maybeSourceMap, maybeCallback);
  77. }
  78. function minifyInBatchesFromArray(input, options, maybeSourceMap, maybeCallback) {
  79. var callback = typeof maybeCallback == 'function'
  80. ? maybeCallback
  81. : (typeof maybeSourceMap == 'function' ? maybeSourceMap : null);
  82. var errors = [];
  83. var outputAsHash = {};
  84. var inputValue;
  85. var i, l;
  86. function whenHashBatchDone(innerErrors, output) {
  87. outputAsHash = Object.assign(outputAsHash, output);
  88. if (innerErrors !== null) {
  89. errors = errors.concat(innerErrors);
  90. }
  91. }
  92. for (i = 0, l = input.length; i < l; i++) {
  93. if (typeof input[i] == 'object') {
  94. minifyInBatchesFromHash(input[i], options, whenHashBatchDone);
  95. } else {
  96. inputValue = input[i];
  97. outputAsHash[inputValue] = minify([inputValue], options);
  98. errors = errors.concat(outputAsHash[inputValue].errors);
  99. }
  100. }
  101. return callback
  102. ? callback(errors.length > 0 ? errors : null, outputAsHash)
  103. : outputAsHash;
  104. }
  105. function minifyInBatchesFromHash(input, options, maybeSourceMap, maybeCallback) {
  106. var callback = typeof maybeCallback == 'function'
  107. ? maybeCallback
  108. : (typeof maybeSourceMap == 'function' ? maybeSourceMap : null);
  109. var errors = [];
  110. var outputAsHash = {};
  111. var inputKey;
  112. var inputValue;
  113. for (inputKey in input) {
  114. inputValue = input[inputKey];
  115. outputAsHash[inputKey] = minify(inputValue.styles, options, inputValue.sourceMap);
  116. errors = errors.concat(outputAsHash[inputKey].errors);
  117. }
  118. return callback
  119. ? callback(errors.length > 0 ? errors : null, outputAsHash)
  120. : outputAsHash;
  121. }
  122. function minify(input, options, maybeSourceMap, maybeCallback) {
  123. var sourceMap = typeof maybeSourceMap != 'function'
  124. ? maybeSourceMap
  125. : null;
  126. var callback = typeof maybeCallback == 'function'
  127. ? maybeCallback
  128. : (typeof maybeSourceMap == 'function' ? maybeSourceMap : null);
  129. var context = {
  130. stats: {
  131. efficiency: 0,
  132. minifiedSize: 0,
  133. originalSize: 0,
  134. startedAt: Date.now(),
  135. timeSpent: 0
  136. },
  137. cache: { specificity: {} },
  138. errors: [],
  139. inlinedStylesheets: [],
  140. inputSourceMapTracker: inputSourceMapTracker(),
  141. localOnly: !callback,
  142. options: options,
  143. source: null,
  144. sourcesContent: {},
  145. validator: validator(options.compatibility),
  146. warnings: []
  147. };
  148. var implicitRebaseToWarning;
  149. if (sourceMap) {
  150. context.inputSourceMapTracker.track(undefined, sourceMap);
  151. }
  152. if (options.rebase && !options.explicitRebaseTo) {
  153. implicitRebaseToWarning = 'You have set `rebase: true` without giving `rebaseTo` option, which, in this case, defaults to the current working directory. '
  154. + 'You are then warned this can lead to unexpected URL rebasing (aka here be dragons)! '
  155. + 'If you are OK with the clean-css output, then you can get rid of this warning by giving clean-css a `rebaseTo: process.cwd()` option.';
  156. context.warnings.push(implicitRebaseToWarning);
  157. }
  158. return runner(context.localOnly)(function() {
  159. return readSources(input, context, function(tokens) {
  160. var serialize = context.options.sourceMap
  161. ? serializeStylesAndSourceMap
  162. : serializeStyles;
  163. var optimizedTokens = optimize(tokens, context);
  164. var optimizedStyles = serialize(optimizedTokens, context);
  165. var output = withMetadata(optimizedStyles, context);
  166. return callback
  167. ? callback(context.errors.length > 0 ? context.errors : null, output)
  168. : output;
  169. });
  170. });
  171. }
  172. function runner(localOnly) {
  173. // to always execute code asynchronously when a callback is given
  174. // more at blog.izs.me/post/59142742143/designing-apis-for-asynchrony
  175. return localOnly
  176. ? function(callback) { return callback(); }
  177. : process.nextTick;
  178. }
  179. function optimize(tokens, context) {
  180. var optimized = level0Optimize(tokens, context);
  181. optimized = OptimizationLevel.One in context.options.level
  182. ? level1Optimize(tokens, context)
  183. : tokens;
  184. optimized = OptimizationLevel.Two in context.options.level
  185. ? level2Optimize(tokens, context, true)
  186. : optimized;
  187. return optimized;
  188. }
  189. function withMetadata(output, context) {
  190. output.stats = calculateStatsFrom(output.styles, context);
  191. output.errors = context.errors;
  192. output.inlinedStylesheets = context.inlinedStylesheets;
  193. output.warnings = context.warnings;
  194. return output;
  195. }
  196. function calculateStatsFrom(styles, context) {
  197. var finishedAt = Date.now();
  198. var timeSpent = finishedAt - context.stats.startedAt;
  199. delete context.stats.startedAt;
  200. context.stats.timeSpent = timeSpent;
  201. context.stats.efficiency = 1 - styles.length / context.stats.originalSize;
  202. context.stats.minifiedSize = styles.length;
  203. return context.stats;
  204. }