1/**
2 * Clean-css - https://github.com/jakubpawlowicz/clean-css
3 * Released under the terms of MIT license
4 *
5 * Copyright (C) 2017 JakubPawlowicz.com
6 */
7
8var level0Optimize = require('./optimizer/level-0/optimize');
9var level1Optimize = require('./optimizer/level-1/optimize');
10var level2Optimize = require('./optimizer/level-2/optimize');
11var validator = require('./optimizer/validator');
12
13var compatibilityFrom = require('./options/compatibility');
14var fetchFrom = require('./options/fetch');
15var formatFrom = require('./options/format').formatFrom;
16var inlineFrom = require('./options/inline');
17var inlineRequestFrom = require('./options/inline-request');
18var inlineTimeoutFrom = require('./options/inline-timeout');
19var OptimizationLevel = require('./options/optimization-level').OptimizationLevel;
20var optimizationLevelFrom = require('./options/optimization-level').optimizationLevelFrom;
21var rebaseFrom = require('./options/rebase');
22var rebaseToFrom = require('./options/rebase-to');
23
24var inputSourceMapTracker = require('./reader/input-source-map-tracker');
25var readSources = require('./reader/read-sources');
26
27var serializeStyles = require('./writer/simple');
28var serializeStylesAndSourceMap = require('./writer/source-maps');
29
30var CleanCSS = module.exports = function CleanCSS(options) {
31  options = options || {};
32
33  this.options = {
34    compatibility: compatibilityFrom(options.compatibility),
35    fetch: fetchFrom(options.fetch),
36    format: formatFrom(options.format),
37    inline: inlineFrom(options.inline),
38    inlineRequest: inlineRequestFrom(options.inlineRequest),
39    inlineTimeout: inlineTimeoutFrom(options.inlineTimeout),
40    level: optimizationLevelFrom(options.level),
41    rebase: rebaseFrom(options.rebase),
42    rebaseTo: rebaseToFrom(options.rebaseTo),
43    returnPromise: !!options.returnPromise,
44    sourceMap: !!options.sourceMap,
45    sourceMapInlineSources: !!options.sourceMapInlineSources
46  };
47};
48
49
50// for compatibility with optimize-css-assets-webpack-plugin
51CleanCSS.process = function (input, opts) {
52  var cleanCss;
53  var optsTo = opts.to;
54
55  delete opts.to;
56  cleanCss = new CleanCSS(Object.assign({ returnPromise: true, rebaseTo: optsTo }, opts));
57
58  return cleanCss.minify(input)
59    .then(function(output) {
60      return { css: output.styles };
61    });
62};
63
64
65CleanCSS.prototype.minify = function (input, maybeSourceMap, maybeCallback) {
66  var options = this.options;
67
68  if (options.returnPromise) {
69    return new Promise(function (resolve, reject) {
70      minify(input, options, maybeSourceMap, function (errors, output) {
71        return errors ?
72          reject(errors) :
73          resolve(output);
74      });
75    });
76  } else {
77    return minify(input, options, maybeSourceMap, maybeCallback);
78  }
79};
80
81function minify(input, options, maybeSourceMap, maybeCallback) {
82  var sourceMap = typeof maybeSourceMap != 'function' ?
83    maybeSourceMap :
84    null;
85  var callback = typeof maybeCallback == 'function' ?
86    maybeCallback :
87    (typeof maybeSourceMap == 'function' ? maybeSourceMap : null);
88  var context = {
89    stats: {
90      efficiency: 0,
91      minifiedSize: 0,
92      originalSize: 0,
93      startedAt: Date.now(),
94      timeSpent: 0
95    },
96    cache: {
97      specificity: {}
98    },
99    errors: [],
100    inlinedStylesheets: [],
101    inputSourceMapTracker: inputSourceMapTracker(),
102    localOnly: !callback,
103    options: options,
104    source: null,
105    sourcesContent: {},
106    validator: validator(options.compatibility),
107    warnings: []
108  };
109
110  if (sourceMap) {
111    context.inputSourceMapTracker.track(undefined, sourceMap);
112  }
113
114  return runner(context.localOnly)(function () {
115    return readSources(input, context, function (tokens) {
116      var serialize = context.options.sourceMap ?
117        serializeStylesAndSourceMap :
118        serializeStyles;
119
120      var optimizedTokens = optimize(tokens, context);
121      var optimizedStyles = serialize(optimizedTokens, context);
122      var output = withMetadata(optimizedStyles, context);
123
124      return callback ?
125        callback(context.errors.length > 0 ? context.errors : null, output) :
126        output;
127    });
128  });
129}
130
131function runner(localOnly) {
132  // to always execute code asynchronously when a callback is given
133  // more at blog.izs.me/post/59142742143/designing-apis-for-asynchrony
134  return localOnly ?
135    function (callback) { return callback(); } :
136    process.nextTick;
137}
138
139function optimize(tokens, context) {
140  var optimized;
141
142  optimized = level0Optimize(tokens, context);
143  optimized = OptimizationLevel.One in context.options.level ?
144    level1Optimize(tokens, context) :
145    tokens;
146  optimized = OptimizationLevel.Two in context.options.level ?
147    level2Optimize(tokens, context, true) :
148    optimized;
149
150  return optimized;
151}
152
153function withMetadata(output, context) {
154  output.stats = calculateStatsFrom(output.styles, context);
155  output.errors = context.errors;
156  output.inlinedStylesheets = context.inlinedStylesheets;
157  output.warnings = context.warnings;
158
159  return output;
160}
161
162function calculateStatsFrom(styles, context) {
163  var finishedAt = Date.now();
164  var timeSpent = finishedAt - context.stats.startedAt;
165
166  delete context.stats.startedAt;
167  context.stats.timeSpent = timeSpent;
168  context.stats.efficiency = 1 - styles.length / context.stats.originalSize;
169  context.stats.minifiedSize = styles.length;
170
171  return context.stats;
172}
173