Skip to content

Commit

Permalink
Fixes #856 - allows minify to return a promise.
Browse files Browse the repository at this point in the history
Why:

* Useful as an alternative to callback API;
* future Node.js versions will support `await` mechanism
  which requires supporting promises.
  • Loading branch information
jakubpawlowicz committed Jan 19, 2017
1 parent bbaed3f commit 23637f6
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .jshintrc
Expand Up @@ -6,6 +6,9 @@
"indent": 2,
"noarg": true,
"node": true,
"predef": [
"Promise"
],
"plusplus": false,
"quotmark": "single",
"strict": false,
Expand Down
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -209,6 +209,7 @@ CleanCSS constructor accepts a hash as a parameter, i.e.,
* `level` - an integer denoting optimization level applied or a hash with a fine-grained configuration; see examples below; defaults to `1`
* `rebase` - set to false to skip URL rebasing
* `rebaseTo` - a directory to which all URLs are rebased (most likely the directory under which the output file will live), defaults to the current directory
* `returnPromise` - set to true to make `minify` method return a Promise object (see example below); defaults to `false`
* `sourceMap` - set to true to build output source map; defaults to `false`
* `sourceMapInlineSources` - set to true to inline sources inside a source map's `sourcesContent` field (defaults to false)
It is also required to process inlined sources from input source maps.
Expand Down Expand Up @@ -345,6 +346,19 @@ new CleanCSS().minify(source, function (error, minified) {
This is due to a fact, that, while local files can be read synchronously, remote resources can only be processed asynchronously.
If you don't provide a callback, then remote `@import`s will be left intact.

#### How to work with clean-css Promise API

If you prefer clean-css to return a Promise object then you need to explicitely ask for it:

```js
var CleanCSS = require('clean-css');
var source = 'a{font-weight:bold;}';
new CleanCSS({ returnPromise: true })
.minify(source)
.then(function (minified) { // console.log(minified); })
.catch(function (error) { // deal with errors });
```
### How to use clean-css with build tools?
* [Broccoli](https://github.com/broccolijs/broccoli#broccoli): [broccoli-clean-css](https://github.com/shinnn/broccoli-clean-css)
Expand Down
27 changes: 22 additions & 5 deletions lib/clean.js
Expand Up @@ -39,6 +39,7 @@ var CleanCSS = module.exports = function CleanCSS(options) {
level: optimizationLevelFrom(options.level),
rebase: undefined === options.rebase ? true : !!options.rebase,
rebaseTo: ('rebaseTo' in options) ? path.resolve(options.rebaseTo) : process.cwd(),
returnPromise: !!options.returnPromise,
sourceMap: !!options.sourceMap,
sourceMapInlineSources: !!options.sourceMapInlineSources
};
Expand Down Expand Up @@ -70,12 +71,28 @@ function proxyOptionsFrom(httpProxy) {
}

CleanCSS.prototype.minify = function (input, maybeSourceMap, maybeCallback) {
var options = this.options;

if (options.returnPromise) {
return new Promise(function (resolve, reject) {
minify(input, options, maybeSourceMap, function (errors, output) {
return errors ?
reject(errors) :
resolve(output);
});
});
} else {
return minify(input, options, maybeSourceMap, maybeCallback);
}
};

function minify(input, options, maybeSourceMap, maybeCallback) {
var sourceMap = typeof maybeSourceMap != 'function' ?
maybeSourceMap :
null;
var callback = sourceMap ?
var callback = typeof maybeCallback == 'function' ?
maybeCallback :
maybeSourceMap;
(typeof maybeSourceMap == 'function' ? maybeSourceMap : null);
var context = {
stats: {
efficiency: 0,
Expand All @@ -91,10 +108,10 @@ CleanCSS.prototype.minify = function (input, maybeSourceMap, maybeCallback) {
inlinedStylesheets: [],
inputSourceMapTracker: inputSourceMapTracker(),
localOnly: !callback,
options: this.options,
options: options,
source: null,
sourcesContent: {},
validator: validator(this.options.compatibility),
validator: validator(options.compatibility),
warnings: []
};

Expand All @@ -117,7 +134,7 @@ CleanCSS.prototype.minify = function (input, maybeSourceMap, maybeCallback) {
output;
});
});
};
}

function runner(localOnly) {
// to always execute code asynchronously when a callback is given
Expand Down
35 changes: 35 additions & 0 deletions test/module-test.js
Expand Up @@ -75,6 +75,41 @@ vows.describe('module tests').addBatch({
assert.equal(minified.styles, '@import url(https://fonts.googleapis.com/css?family=Open+Sans);');
}
},
'with promise returned and no callback': {
'topic': function () {
new CleanCSS({ returnPromise: true })
.minify('.block{color:#f00}')
.then(this.callback.bind(null, null));
},
'should yield output': function (errors, output) {
assert.equal(output.styles, '.block{color:red}');
}
},
'with promise returned and callback': {
'topic': function () {
new CleanCSS({ returnPromise: true })
.minify('.block{color:#f00}', function () { throw new Error('should not get here!'); })
.then(this.callback.bind(null, null));
},
'should yield output': function (errors, output) {
assert.equal(output.styles, '.block{color:red}');
}
},
'with promise and error': {
'topic': function () {
var vow = this;

new CleanCSS({ returnPromise: true })
.minify('@import "missing.css";')
.then(function (output) { vow.callback(null, output); })
.catch(function (errors) { vow.callback(errors, null); });
},
'should catch error': function (errors, result) {
/* jshint unused: false */
assert.lengthOf(errors, 1);
assert.equal(errors[0], 'Ignoring local @import of "missing.css" as resource is missing.');
}
},
'debug info': {
'topic': function () {
return new CleanCSS().minify('a{ color: #f00 }');
Expand Down

0 comments on commit 23637f6

Please sign in to comment.