232 lines
7.6 KiB
JavaScript
232 lines
7.6 KiB
JavaScript
|
/**
|
||
|
* Clean-css - https://github.com/jakubpawlowicz/clean-css
|
||
|
* Released under the terms of MIT license
|
||
|
*
|
||
|
* Copyright (C) 2015 JakubPawlowicz.com
|
||
|
*/
|
||
|
|
||
|
var ImportInliner = require('./imports/inliner');
|
||
|
var rebaseUrls = require('./urls/rebase');
|
||
|
|
||
|
var tokenize = require('./tokenizer/tokenize');
|
||
|
var simpleOptimize = require('./selectors/simple');
|
||
|
var advancedOptimize = require('./selectors/advanced');
|
||
|
|
||
|
var simpleStringify = require('./stringifier/simple');
|
||
|
var sourceMapStringify = require('./stringifier/source-maps');
|
||
|
|
||
|
var CommentsProcessor = require('./text/comments-processor');
|
||
|
var ExpressionsProcessor = require('./text/expressions-processor');
|
||
|
var FreeTextProcessor = require('./text/free-text-processor');
|
||
|
var UrlsProcessor = require('./text/urls-processor');
|
||
|
|
||
|
var Compatibility = require('./utils/compatibility');
|
||
|
var InputSourceMapTracker = require('./utils/input-source-map-tracker');
|
||
|
var SourceTracker = require('./utils/source-tracker');
|
||
|
var SourceReader = require('./utils/source-reader');
|
||
|
var Validator = require('./properties/validator');
|
||
|
|
||
|
var fs = require('fs');
|
||
|
var path = require('path');
|
||
|
var url = require('url');
|
||
|
|
||
|
var override = require('./utils/object').override;
|
||
|
|
||
|
var DEFAULT_TIMEOUT = 5000;
|
||
|
|
||
|
var CleanCSS = module.exports = function CleanCSS(options) {
|
||
|
options = options || {};
|
||
|
|
||
|
this.options = {
|
||
|
advanced: undefined === options.advanced ? true : !!options.advanced,
|
||
|
aggressiveMerging: undefined === options.aggressiveMerging ? true : !!options.aggressiveMerging,
|
||
|
benchmark: options.benchmark,
|
||
|
compatibility: new Compatibility(options.compatibility).toOptions(),
|
||
|
debug: options.debug,
|
||
|
explicitRoot: !!options.root,
|
||
|
explicitTarget: !!options.target,
|
||
|
inliner: options.inliner || {},
|
||
|
keepBreaks: options.keepBreaks || false,
|
||
|
keepSpecialComments: 'keepSpecialComments' in options ? options.keepSpecialComments : '*',
|
||
|
mediaMerging: undefined === options.mediaMerging ? true : !!options.mediaMerging,
|
||
|
processImport: undefined === options.processImport ? true : !!options.processImport,
|
||
|
processImportFrom: importOptionsFrom(options.processImportFrom),
|
||
|
rebase: undefined === options.rebase ? true : !!options.rebase,
|
||
|
relativeTo: options.relativeTo,
|
||
|
restructuring: undefined === options.restructuring ? true : !!options.restructuring,
|
||
|
root: options.root || process.cwd(),
|
||
|
roundingPrecision: options.roundingPrecision,
|
||
|
semanticMerging: undefined === options.semanticMerging ? false : !!options.semanticMerging,
|
||
|
shorthandCompacting: undefined === options.shorthandCompacting ? true : !!options.shorthandCompacting,
|
||
|
sourceMap: options.sourceMap,
|
||
|
sourceMapInlineSources: !!options.sourceMapInlineSources,
|
||
|
target: !options.target || missingDirectory(options.target) || presentDirectory(options.target) ? options.target : path.dirname(options.target)
|
||
|
};
|
||
|
|
||
|
this.options.inliner.timeout = this.options.inliner.timeout || DEFAULT_TIMEOUT;
|
||
|
this.options.inliner.request = override(
|
||
|
/* jshint camelcase: false */
|
||
|
proxyOptionsFrom(process.env.HTTP_PROXY || process.env.http_proxy),
|
||
|
this.options.inliner.request || {}
|
||
|
);
|
||
|
};
|
||
|
|
||
|
function importOptionsFrom(rules) {
|
||
|
return undefined === rules ? ['all'] : rules;
|
||
|
}
|
||
|
|
||
|
function missingDirectory(filepath) {
|
||
|
return !fs.existsSync(filepath) && !/\.css$/.test(filepath);
|
||
|
}
|
||
|
|
||
|
function presentDirectory(filepath) {
|
||
|
return fs.existsSync(filepath) && fs.statSync(filepath).isDirectory();
|
||
|
}
|
||
|
|
||
|
function proxyOptionsFrom(httpProxy) {
|
||
|
return httpProxy ?
|
||
|
{
|
||
|
hostname: url.parse(httpProxy).hostname,
|
||
|
port: parseInt(url.parse(httpProxy).port)
|
||
|
} :
|
||
|
{};
|
||
|
}
|
||
|
|
||
|
CleanCSS.prototype.minify = function (data, callback) {
|
||
|
var context = {
|
||
|
stats: {},
|
||
|
errors: [],
|
||
|
warnings: [],
|
||
|
options: this.options,
|
||
|
debug: this.options.debug,
|
||
|
localOnly: !callback,
|
||
|
sourceTracker: new SourceTracker(),
|
||
|
validator: new Validator(this.options.compatibility)
|
||
|
};
|
||
|
|
||
|
if (context.options.sourceMap)
|
||
|
context.inputSourceMapTracker = new InputSourceMapTracker(context);
|
||
|
|
||
|
context.sourceReader = new SourceReader(context, data);
|
||
|
data = context.sourceReader.toString();
|
||
|
|
||
|
if (context.options.processImport || data.indexOf('@shallow') > 0) {
|
||
|
// inline all imports
|
||
|
var runner = callback ?
|
||
|
process.nextTick :
|
||
|
function (callback) { return callback(); };
|
||
|
|
||
|
return runner(function () {
|
||
|
return new ImportInliner(context).process(data, {
|
||
|
localOnly: context.localOnly,
|
||
|
imports: context.options.processImportFrom,
|
||
|
whenDone: runMinifier(callback, context)
|
||
|
});
|
||
|
});
|
||
|
} else {
|
||
|
return runMinifier(callback, context)(data);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function runMinifier(callback, context) {
|
||
|
function whenSourceMapReady (data) {
|
||
|
data = context.options.debug ?
|
||
|
minifyWithDebug(context, data) :
|
||
|
minify(context, data);
|
||
|
data = withMetadata(context, data);
|
||
|
|
||
|
return callback ?
|
||
|
callback.call(null, context.errors.length > 0 ? context.errors : null, data) :
|
||
|
data;
|
||
|
}
|
||
|
|
||
|
return function (data) {
|
||
|
if (context.options.sourceMap) {
|
||
|
return context.inputSourceMapTracker.track(data, function () {
|
||
|
if (context.options.sourceMapInlineSources) {
|
||
|
return context.inputSourceMapTracker.resolveSources(function () {
|
||
|
return whenSourceMapReady(data);
|
||
|
});
|
||
|
} else {
|
||
|
return whenSourceMapReady(data);
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
return whenSourceMapReady(data);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function withMetadata(context, data) {
|
||
|
data.stats = context.stats;
|
||
|
data.errors = context.errors;
|
||
|
data.warnings = context.warnings;
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
function minifyWithDebug(context, data) {
|
||
|
var startedAt = process.hrtime();
|
||
|
context.stats.originalSize = context.sourceTracker.removeAll(data).length;
|
||
|
|
||
|
data = minify(context, data);
|
||
|
|
||
|
var elapsed = process.hrtime(startedAt);
|
||
|
context.stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
|
||
|
context.stats.efficiency = 1 - data.styles.length / context.stats.originalSize;
|
||
|
context.stats.minifiedSize = data.styles.length;
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
function benchmark(runner) {
|
||
|
return function (processor, action) {
|
||
|
var name = processor.constructor.name + '#' + action;
|
||
|
var start = process.hrtime();
|
||
|
runner(processor, action);
|
||
|
var itTook = process.hrtime(start);
|
||
|
console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function minify(context, data) {
|
||
|
var options = context.options;
|
||
|
|
||
|
var commentsProcessor = new CommentsProcessor(context, options.keepSpecialComments, options.keepBreaks, options.sourceMap);
|
||
|
var expressionsProcessor = new ExpressionsProcessor(options.sourceMap);
|
||
|
var freeTextProcessor = new FreeTextProcessor(options.sourceMap);
|
||
|
var urlsProcessor = new UrlsProcessor(context, options.sourceMap, options.compatibility.properties.urlQuotes);
|
||
|
|
||
|
var stringify = options.sourceMap ? sourceMapStringify : simpleStringify;
|
||
|
|
||
|
var run = function (processor, action) {
|
||
|
data = typeof processor == 'function' ?
|
||
|
processor(data) :
|
||
|
processor[action](data);
|
||
|
};
|
||
|
|
||
|
if (options.benchmark)
|
||
|
run = benchmark(run);
|
||
|
|
||
|
run(commentsProcessor, 'escape');
|
||
|
run(expressionsProcessor, 'escape');
|
||
|
run(urlsProcessor, 'escape');
|
||
|
run(freeTextProcessor, 'escape');
|
||
|
|
||
|
function restoreEscapes(data, prefixContent) {
|
||
|
data = freeTextProcessor.restore(data, prefixContent);
|
||
|
data = urlsProcessor.restore(data);
|
||
|
data = options.rebase ? rebaseUrls(data, context) : data;
|
||
|
data = expressionsProcessor.restore(data);
|
||
|
return commentsProcessor.restore(data);
|
||
|
}
|
||
|
|
||
|
var tokens = tokenize(data, context);
|
||
|
|
||
|
simpleOptimize(tokens, options, context);
|
||
|
|
||
|
if (options.advanced)
|
||
|
advancedOptimize(tokens, options, context, true);
|
||
|
|
||
|
return stringify(tokens, options, restoreEscapes, context.inputSourceMapTracker);
|
||
|
}
|