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