/** * 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); }