diff --git a/gulpfile.js b/gulpfile.js index 185499d5..635d56cb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,6 +1,14 @@ var gulp = require('gulp'); var path = require('path'); +var BUILD_CONFIGS = [ + './test/webpack-default.config', + './test/webpack-explicit-plugins.config', + './test/webpack-with-packs.config', + './test/webpack-incorrect-using-packs.config', + './test/webpack-custom-parser.config' +]; + gulp.task('clean', function (done) { var fs = require('fs-extra'); fs.remove(path.join(__dirname, 'build'), done); @@ -17,12 +25,17 @@ gulp.task('lint', function () { .pipe(eslint.failAfterError()); }); -gulp.task('build', ['clean'], function () { - var webpack = require('webpack-stream'); - return gulp.src('') - .pipe(webpack(require('./test/webpack.config'))) - .pipe(gulp.dest('build/')); -}); +BUILD_CONFIGS + .forEach(function (configFile) { + gulp.task(configFile, ['clean'], function () { + var webpack = require('webpack-stream'); + return gulp.src('') + .pipe(webpack(require(configFile))) + .pipe(gulp.dest('build/')); + }); + }); + +gulp.task('build', BUILD_CONFIGS); gulp.task('test', ['build'], function () { var mocha = require('gulp-mocha'); diff --git a/index.js b/index.js index f8ca2481..ff37b7c5 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,7 @@ var loaderUtils = require('loader-utils'); var postcss = require('postcss'); +var assign = require('./lib/assign'); +var getConfiguration = require('./lib/configuration'); function PostCSSLoaderError(error) { Error.call(this); @@ -25,90 +27,79 @@ module.exports = function (source, map) { var file = this.resourcePath; var params = loaderUtils.parseQuery(this.query); - var opts = { - from: file, - to: file, - map: { - inline: params.sourceMap === 'inline', - annotation: false + var options = this.options.postcss; + var pack = params.pack; + var loader = this; + var callback = this.async(); + + getConfiguration(options, pack, function (err, config) { + if (err) { + callback(err); + return; } - }; - if ( typeof map === 'string' ) map = JSON.parse(map); - if ( map && map.mappings ) opts.map.prev = map; + var plugins = config.plugins; + var exec = config.exec; - var options = this.options.postcss; - if ( typeof options === 'function' ) { - options = options.call(this, this); - } + var opts = assign({}, config.options, { + from: file, + to : file, + map: { + inline: params.sourceMap === 'inline', + annotation: false + } + }); - var plugins; - var exec; - if ( typeof options === 'undefined' ) { - plugins = []; - } else if ( Array.isArray(options) ) { - plugins = options; - } else { - plugins = options.plugins || options.defaults; - opts.stringifier = options.stringifier; - opts.parser = options.parser; - opts.syntax = options.syntax; - exec = options.exec; - } - if ( params.pack ) { - plugins = options[params.pack]; - if ( !plugins ) { - throw new Error('PostCSS plugin pack is not defined in options'); - } - } + if ( typeof map === 'string' ) map = JSON.parse(map); + if ( map && map.mappings ) opts.map.prev = map; - if ( params.syntax ) { - opts.syntax = require(params.syntax); - } - if ( params.parser ) { - opts.parser = require(params.parser); - } - if ( params.stringifier ) { - opts.stringifier = require(params.stringifier); - } - if ( params.exec ) { - exec = params.exec; - } + if ( params.syntax ) { + opts.syntax = require(params.syntax); + } + if ( params.parser ) { + opts.parser = require(params.parser); + } + if ( params.stringifier ) { + opts.stringifier = require(params.stringifier); + } + if ( params.exec ) { + exec = params.exec; + } - var loader = this; - var callback = this.async(); + if ( params.parser === 'postcss-js' || exec ) { + source = loader.exec(source, loader.resource); + } - if ( params.parser === 'postcss-js' || exec ) { - source = this.exec(source, this.resource); - } + // Allow plugins to add or remove postcss plugins + if ( loader._compilation ) { + plugins = loader._compilation.applyPluginsWaterfall( + 'postcss-loader-before-processing', + [].concat(plugins), + params + ); + } else { + loader.emitWarning( + 'this._compilation is not available thus ' + + '`postcss-loader-before-processing` is not supported' + ); + } - // Allow plugins to add or remove postcss plugins - if ( this._compilation ) { - plugins = this._compilation.applyPluginsWaterfall( - 'postcss-loader-before-processing', - [].concat(plugins), - params - ); - } else { - loader.emitWarning( - 'this._compilation is not available thus ' + - '`postcss-loader-before-processing` is not supported' - ); - } + postcss(plugins).process(source, opts) + .then(function (result) { + result.warnings().forEach(function (msg) { + loader.emitWarning(msg.toString()); + }); - postcss(plugins).process(source, opts) - .then(function (result) { - result.warnings().forEach(function (msg) { - loader.emitWarning(msg.toString()); - }); - callback(null, result.css, result.map ? result.map.toJSON() : null); - return null; - }) - .catch(function (error) { - if ( error.name === 'CssSyntaxError' ) { - callback(new PostCSSLoaderError(error)); - } else { - callback(error); - } - }); + var resultMap = result.map ? result.map.toJSON() : null; + callback(null, result.css, resultMap); + return null; + }) + .catch(function (error) { + if ( error.name === 'CssSyntaxError' ) { + callback(new PostCSSLoaderError(error)); + } else { + callback(error); + } + }); + }); }; diff --git a/lib/assign.js b/lib/assign.js new file mode 100644 index 00000000..dd98eb1b --- /dev/null +++ b/lib/assign.js @@ -0,0 +1,20 @@ +module.exports = Object.assign || function (target) { + 'use strict'; + // We must check against these specific cases. + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; +}; diff --git a/lib/configuration/index.js b/lib/configuration/index.js new file mode 100644 index 00000000..bdc5ebeb --- /dev/null +++ b/lib/configuration/index.js @@ -0,0 +1,27 @@ +var parseOptions = require('./parse_options'); +var loadConfig = require('postcss-load-config'); + +function loadConfigurationFromRc(pack, callback) { + if ( pack ) { + callback(new Error('PostCSS plugin pack is supported ' + + 'only when config is passed explicitly')); + return; + } + + loadConfig() + .then(function (config) { + callback(null, { options: config.options, plugins: config.plugins }); + }) + .catch(function (err) { + callback(err); + }); +} + +module.exports = function getConfiguration(options, pack, callback) { + if ( typeof options === 'undefined' ) { + loadConfigurationFromRc(pack, callback); + return; + } + + parseOptions(options, pack, callback); +}; diff --git a/lib/configuration/parse_options.js b/lib/configuration/parse_options.js new file mode 100644 index 00000000..861b0edc --- /dev/null +++ b/lib/configuration/parse_options.js @@ -0,0 +1,41 @@ +function getPluginsFromOptions(options, pack) { + var plugins; + + if ( typeof options === 'undefined') { + plugins = []; + } + else if ( Array.isArray(options) ) { + plugins = options; + } else { + plugins = options.plugins || options.defaults; + } + + if ( pack ) { + plugins = options[pack]; + if ( !plugins ) { + throw new Error('PostCSS plugin pack is not defined in options'); + } + } + + return plugins; +} + +module.exports = function parseOptions(options, pack, callback) { + var exec = options && options.exec; + + if ( typeof options === 'function' ) { + options = options.call(this, this); + } + + var plugins = getPluginsFromOptions(options, pack); + var opts = {}; + + if ( typeof options !== 'undefined' ) { + opts.stringifier = options.stringifier; + opts.parser = options.parser; + opts.syntax = options.syntax; + } + + callback(null, { options: opts, plugins: plugins, exec: exec }); +} + diff --git a/package.json b/package.json index 95c0f7ef..a92bc163 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "repository": "postcss/postcss-loader", "dependencies": { "loader-utils": "^0.2.16", - "postcss": "^5.2.0" + "postcss": "^5.2.0", + "postcss-load-config": "^1.0.0-alpha4" }, "devDependencies": { "eslint-config-postcss": "2.0.2", @@ -21,7 +22,8 @@ "gulp-mocha": "3.0.1", "fs-extra": "0.30.0", "chai": "3.5.0", - "gulp": "3.9.1" + "gulp": "3.9.1", + "sugarss": "^0.1.6" }, "scripts": { "test": "gulp" diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..a432898a --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,7 @@ +var config = { + plugins: {} +} + +config.plugins[require.resolve(__dirname + '/test/support/plugins/blue')] = false; + +module.exports = config diff --git a/test/plugins/red.js b/test/plugins/red.js deleted file mode 100644 index 5b79b260..00000000 --- a/test/plugins/red.js +++ /dev/null @@ -1,14 +0,0 @@ -var warning = false; - -module.exports = { - postcss: function (css, result) { - if ( !warning ) { - result.warn('Test red warning'); - warning = true; - } - - css.walkDecls(function (decl) { - if ( decl.value === 'blue' ) decl.value = 'red'; - }); - } -}; diff --git a/test/cases/broken.css b/test/support/cases/broken.css similarity index 100% rename from test/cases/broken.css rename to test/support/cases/broken.css diff --git a/test/cases/exec.js b/test/support/cases/exec.js similarity index 100% rename from test/cases/exec.js rename to test/support/cases/exec.js diff --git a/test/cases/style.css b/test/support/cases/style.css similarity index 100% rename from test/cases/style.css rename to test/support/cases/style.css diff --git a/test/cases/style.js b/test/support/cases/style.js similarity index 100% rename from test/cases/style.js rename to test/support/cases/style.js diff --git a/test/support/cases/sugar.css b/test/support/cases/sugar.css new file mode 100644 index 00000000..b1ec6a6e --- /dev/null +++ b/test/support/cases/sugar.css @@ -0,0 +1,2 @@ +a + color: black diff --git a/test/plugins/blue.js b/test/support/plugins/blue.js similarity index 100% rename from test/plugins/blue.js rename to test/support/plugins/blue.js diff --git a/test/support/plugins/red.js b/test/support/plugins/red.js new file mode 100644 index 00000000..e1396f7d --- /dev/null +++ b/test/support/plugins/red.js @@ -0,0 +1,20 @@ +var warning = false; + +module.exports = function (options) { + var alpha = options.alpha || '1.0'; + + return { + postcss: function (css, result) { + if (!warning) { + result.warn('Test red warning'); + warning = true; + } + + css.walkDecls(function (decl) { + if (decl.value === 'blue') { + decl.value = 'rgba(255, 0, 0, ' + alpha + ')'; + } + }); + } + }; +}; diff --git a/test/webpack-plugins/rewrite.js b/test/support/webpack-plugins/rewrite.js similarity index 100% rename from test/webpack-plugins/rewrite.js rename to test/support/webpack-plugins/rewrite.js diff --git a/test/test-custom-parser.js b/test/test-custom-parser.js new file mode 100644 index 00000000..5d056788 --- /dev/null +++ b/test/test-custom-parser.js @@ -0,0 +1,12 @@ +var expect = require('chai').expect; + +describe('postcss-loader', function () { + + context('when config defines syntax function', function () { + it('processes sugarss', function () { + var css = require('!raw-loader!../!' + + './support/cases/sugar.css'); + expect(css).to.eql('a\n color: rgba(255, 0, 0, 0.1)\n'); + }); + }); +}); diff --git a/test/test.js b/test/test-default.js similarity index 61% rename from test/test.js rename to test/test-default.js index 9f83a268..0a80b2b9 100644 --- a/test/test.js +++ b/test/test-default.js @@ -3,43 +3,40 @@ var expect = require('chai').expect; describe('postcss-loader', function () { it('processes CSS with default plugins', function () { - var css = require('!raw-loader!../!./cases/style.css'); - expect(css).to.eql('a { color: red }\n'); - }); - - it('processes CSS with custom plugins', function () { - var css = require('!raw-loader!../?pack=blues!./cases/style.css'); + var css = require('!raw-loader!../!./support/cases/style.css'); expect(css).to.eql('a { color: blue }\n'); }); it('processes CSS in safe mode', function () { var css = require('!raw-loader!' + '../?parser=postcss-safe-parser!' + - './cases/broken.css'); + './support/cases/broken.css'); expect(css).to.eql('a { color:\n}'); }); it('lets other plugins alter the used plugins', function () { - var css = require('!raw-loader!../?rewrite=true!./cases/style.css'); + var css = require('!raw-loader!../?rewrite=true!' + + './support/cases/style.css'); expect(css).to.eql('a { color: black }\n'); }); it('processes CSS-in-JS', function () { var css = require('!raw-loader!' + '../?parser=postcss-js!' + - './cases/style.js'); - expect(css).to.eql('a {\n color: red\n}'); + './support/cases/style.js'); + expect(css).to.eql('a {\n color: blue\n}'); }); it('processes CSS with exec', function () { var css = require('!raw-loader!' + '../?exec!' + - './cases/exec.js'); + './support/cases/exec.js'); expect(css).to.eql('a {\n color: green\n}'); }); it('inlines map', function () { - var css = require('!raw-loader!../?sourceMap=inline!./cases/style.css'); + var css = require('!raw-loader!../?sourceMap=inline!' + + './support/cases/style.css'); expect(css).to.include('/*# sourceMappingURL='); }); diff --git a/test/test-explicit-plugins.js b/test/test-explicit-plugins.js new file mode 100644 index 00000000..63b9cc62 --- /dev/null +++ b/test/test-explicit-plugins.js @@ -0,0 +1,12 @@ +var expect = require('chai').expect; + +describe('postcss-loader', function () { + + context('when config adds plugins', function () { + it('processes CSS with custom plugins', function () { + var css = require('!raw-loader!../!' + + './support/cases/style.css'); + expect(css).to.eql('a { color: rgba(255, 0, 0, 0.1) }\n'); + }); + }); +}); diff --git a/test/test-incorrect-using-packs.js b/test/test-incorrect-using-packs.js new file mode 100644 index 00000000..5fcbfcf0 --- /dev/null +++ b/test/test-incorrect-using-packs.js @@ -0,0 +1,21 @@ +var expect = require('chai').expect; + +describe('postcss-loader', function () { + + context('when config does not define packs', function () { + it('fails to load specific pack', function () { + var compile = function () { + // Try-catch to change webpack error into warning + try { + require('!raw-loader!../../?pack=blues!' + + '../support/cases/style.css'); + } catch (ex) { + // and then propagate runtime error + throw ex; + } + }; + + expect(compile).to.throw('Cannot find module'); + }); + }); +}); diff --git a/test/test-with-packs.js b/test/test-with-packs.js new file mode 100644 index 00000000..49a07f75 --- /dev/null +++ b/test/test-with-packs.js @@ -0,0 +1,18 @@ +var expect = require('chai').expect; + +describe('postcss-loader', function () { + + context('when config defines packs', function () { + it('processes CSS with default plugins', function () { + var css = require('!raw-loader!../!' + + './support/cases/style.css'); + expect(css).to.eql('a { color: rgba(255, 0, 0, 0.1) }\n'); + }); + + it('processes CSS with custom plugins', function () { + var css = require('!raw-loader!../?pack=blues!' + + './support/cases/style.css'); + expect(css).to.eql('a { color: blue }\n'); + }); + }); +}); diff --git a/test/webpack-custom-parser.config.js b/test/webpack-custom-parser.config.js new file mode 100644 index 00000000..daf50b25 --- /dev/null +++ b/test/webpack-custom-parser.config.js @@ -0,0 +1,25 @@ +var path = require('path'); +var sugarss = require('sugarss'); + +var blue = require('./support/plugins/blue'); +var red = require('./support/plugins/red'); + +var RewritePlugin = require('./support/webpack-plugins/rewrite.js'); + +module.exports = { + target : 'node', + context: __dirname, + entry : './test-custom-parser.js', + output : { + path: path.join(__dirname, '..', 'build') + }, + postcss: function () { + return { + syntax: sugarss, + plugins: [blue, red({ alpha: 0.1 })] + }; + }, + plugins: [ + new RewritePlugin() + ] +}; diff --git a/test/webpack-default.config.js b/test/webpack-default.config.js new file mode 100644 index 00000000..913e3c3f --- /dev/null +++ b/test/webpack-default.config.js @@ -0,0 +1,15 @@ +var path = require('path'); + +var RewritePlugin = require('./support/webpack-plugins/rewrite.js'); + +module.exports = { + target : 'node', + context: __dirname, + entry : './test-default.js', + output : { + path: path.join(__dirname, '..', 'build') + }, + plugins: [ + new RewritePlugin() + ] +}; diff --git a/test/webpack-explicit-plugins.config.js b/test/webpack-explicit-plugins.config.js new file mode 100644 index 00000000..89389426 --- /dev/null +++ b/test/webpack-explicit-plugins.config.js @@ -0,0 +1,20 @@ +var path = require('path'); + +var blue = require('./support/plugins/blue'); +var red = require('./support/plugins/red'); +var RewritePlugin = require('./support/webpack-plugins/rewrite.js'); + +module.exports = { + target : 'node', + context: __dirname, + entry : './test-explicit-plugins.js', + output : { + path: path.join(__dirname, '..', 'build') + }, + postcss: function () { + return [blue, red({ alpha: 0.1 })]; + }, + plugins: [ + new RewritePlugin() + ] +}; diff --git a/test/webpack-incorrect-using-packs.config.js b/test/webpack-incorrect-using-packs.config.js new file mode 100644 index 00000000..78250f19 --- /dev/null +++ b/test/webpack-incorrect-using-packs.config.js @@ -0,0 +1,15 @@ +var path = require('path'); + +var RewritePlugin = require('./support/webpack-plugins/rewrite.js'); + +module.exports = { + target : 'node', + context: __dirname, + entry : './test-incorrect-using-packs.js', + output : { + path: path.join(__dirname, '..', 'build') + }, + plugins: [ + new RewritePlugin() + ] +}; diff --git a/test/webpack-with-packs.config.js b/test/webpack-with-packs.config.js new file mode 100644 index 00000000..d021480f --- /dev/null +++ b/test/webpack-with-packs.config.js @@ -0,0 +1,23 @@ +var path = require('path'); + +var blue = require('./support/plugins/blue'); +var red = require('./support/plugins/red'); +var RewritePlugin = require('./support/webpack-plugins/rewrite.js'); + +module.exports = { + target : 'node', + context: __dirname, + entry : './test-with-packs.js', + output : { + path: path.join(__dirname, '..', 'build') + }, + postcss: function () { + return { + defaults: [blue, red({ alpha: 0.1 })], + blues: [blue] + }; + }, + plugins: [ + new RewritePlugin() + ] +}; diff --git a/test/webpack.config.js b/test/webpack.config.js deleted file mode 100644 index b4223b05..00000000 --- a/test/webpack.config.js +++ /dev/null @@ -1,24 +0,0 @@ -var path = require('path'); - -var blue = require('./plugins/blue'); -var red = require('./plugins/red'); -var RewritePlugin = require('./webpack-plugins/rewrite.js'); - -module.exports = { - target: 'node', - context: __dirname, - entry: './test.js', - output: { - path: path.join(__dirname, '..', 'build'), - filename: 'test.js' - }, - postcss: function () { - return { - defaults: [blue, red], - blues: [blue] - }; - }, - plugins: [ - new RewritePlugin() - ] -};