UNPKG

8 kBJavaScriptView Raw
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 pluginsFrom = require('./options/plugins');
22var rebaseFrom = require('./options/rebase');
23var rebaseToFrom = require('./options/rebase-to');
24
25var inputSourceMapTracker = require('./reader/input-source-map-tracker');
26var readSources = require('./reader/read-sources');
27
28var serializeStyles = require('./writer/simple');
29var serializeStylesAndSourceMap = require('./writer/source-maps');
30
31var 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// for compatibility with optimize-css-assets-webpack-plugin
55CleanCSS.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
69CleanCSS.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
85function 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
95function 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
125function 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
146function 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
205function runner(localOnly) {
206 // to always execute code asynchronously when a callback is given
207 // more at blog.izs.me/post/59142742143/designing-apis-for-asynchrony
208 return localOnly ?
209 function (callback) { return callback(); } :
210 process.nextTick;
211}
212
213function 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
227function 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
236function 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}