clean.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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({ returnPromise: true, rebaseTo: optsTo }, opts));
  50. return cleanCss.minify(input)
  51. .then(function(output) {
  52. return { css: output.styles };
  53. });
  54. };
  55. CleanCSS.prototype.minify = function (input, maybeSourceMap, maybeCallback) {
  56. var options = this.options;
  57. if (options.returnPromise) {
  58. return new Promise(function (resolve, reject) {
  59. minifyAll(input, options, maybeSourceMap, function (errors, output) {
  60. return errors ?
  61. reject(errors) :
  62. resolve(output);
  63. });
  64. });
  65. } else {
  66. return minifyAll(input, options, maybeSourceMap, maybeCallback);
  67. }
  68. };
  69. function minifyAll(input, options, maybeSourceMap, maybeCallback) {
  70. if (options.batch && Array.isArray(input)) {
  71. return minifyInBatchesFromArray(input, options, maybeSourceMap, maybeCallback);
  72. } else if (options.batch && (typeof input == 'object')) {
  73. return minifyInBatchesFromHash(input, options, maybeSourceMap, maybeCallback);
  74. } else {
  75. return minify(input, options, maybeSourceMap, maybeCallback);
  76. }
  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: {
  138. specificity: {}
  139. },
  140. errors: [],
  141. inlinedStylesheets: [],
  142. inputSourceMapTracker: inputSourceMapTracker(),
  143. localOnly: !callback,
  144. options: options,
  145. source: null,
  146. sourcesContent: {},
  147. validator: validator(options.compatibility),
  148. warnings: []
  149. };
  150. var implicitRebaseToWarning;
  151. if (sourceMap) {
  152. context.inputSourceMapTracker.track(undefined, sourceMap);
  153. }
  154. if (options.rebase && !options.explicitRebaseTo) {
  155. implicitRebaseToWarning =
  156. 'You have set `rebase: true` without giving `rebaseTo` option, which, in this case, defaults to the current working directory. ' +
  157. 'You are then warned this can lead to unexpected URL rebasing (aka here be dragons)! ' +
  158. '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.';
  159. context.warnings.push(implicitRebaseToWarning);
  160. }
  161. return runner(context.localOnly)(function () {
  162. return readSources(input, context, function (tokens) {
  163. var serialize = context.options.sourceMap ?
  164. serializeStylesAndSourceMap :
  165. serializeStyles;
  166. var optimizedTokens = optimize(tokens, context);
  167. var optimizedStyles = serialize(optimizedTokens, context);
  168. var output = withMetadata(optimizedStyles, context);
  169. return callback ?
  170. callback(context.errors.length > 0 ? context.errors : null, output) :
  171. output;
  172. });
  173. });
  174. }
  175. function runner(localOnly) {
  176. // to always execute code asynchronously when a callback is given
  177. // more at blog.izs.me/post/59142742143/designing-apis-for-asynchrony
  178. return localOnly ?
  179. function (callback) { return callback(); } :
  180. process.nextTick;
  181. }
  182. function optimize(tokens, context) {
  183. var optimized = level0Optimize(tokens, context);
  184. optimized = OptimizationLevel.One in context.options.level ?
  185. level1Optimize(tokens, context) :
  186. tokens;
  187. optimized = OptimizationLevel.Two in context.options.level ?
  188. level2Optimize(tokens, context, true) :
  189. optimized;
  190. return optimized;
  191. }
  192. function withMetadata(output, context) {
  193. output.stats = calculateStatsFrom(output.styles, context);
  194. output.errors = context.errors;
  195. output.inlinedStylesheets = context.inlinedStylesheets;
  196. output.warnings = context.warnings;
  197. return output;
  198. }
  199. function calculateStatsFrom(styles, context) {
  200. var finishedAt = Date.now();
  201. var timeSpent = finishedAt - context.stats.startedAt;
  202. delete context.stats.startedAt;
  203. context.stats.timeSpent = timeSpent;
  204. context.stats.efficiency = 1 - styles.length / context.stats.originalSize;
  205. context.stats.minifiedSize = styles.length;
  206. return context.stats;
  207. }