diff --git a/CHANGELOG.md b/CHANGELOG.md index 99dffd1..0fc5da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Gulp Sass Changelog ## v2.0.0-alpha.1 +### March 26, 2015 +* **New** Added `renderSync` option that can be used through `sass.sync()` + ### March 24, 2015 * **Change** Updated to `node-sass` 3.0.0-alpha.1 * **New** Added support for `gulp-sourcemaps` including tests diff --git a/README.md b/README.md index 5867f90..8c4c374 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ npm install gulp-sass --save-dev # Basic Usage -Something like this: +Something like this will compile your Sass files: ```javascript var gulp = require('gulp'); -var gutil = require('gulp-util'); var sass = require('gulp-sass'); gulp.task('sass', function () { @@ -24,6 +23,19 @@ gulp.task('sass', function () { }); ``` +You can also compile synchronously, doing something like this: + +```javascript +var gulp = require('gulp'); +var sass = require('gulp-sass'); + +gulp.task('sass', function () { + gulp.src('./scss/*.scss') + .pipe(sass.sync().on('error', sass.logError)) + .pipe(gulp.dest('./css')); +}); +``` + ## Options Pass in options just like you would for [`node-sass`](https://github.com/sass/node-sass#options); they will be passed along just as if you were using `node-sass`. @@ -42,7 +54,7 @@ gulp.src('./scss/*.scss') .pipe(gulp.dest('./css')); ``` -By default, [gulp-sourcemaps](https://github.com/floridoo/gulp-sourcemaps) writes the source maps inline in the compiled CSS files. To write them to a separate file, specify a relative file path in the `sourcemaps.write()` function. +By default, [gulp-sourcemaps](https://github.com/floridoo/gulp-sourcemaps) writes the source maps inline in the compiled CSS files. To write them to a separate file, specify a path relative to the `gulp.dest()` destination in the `sourcemaps.write()` function. ```javascript var sourcemaps = require('gulp-sourcemaps'); diff --git a/index.js b/index.js index ee4eabf..fd9cf8a 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +'use strict'; + var gutil = require('gulp-util'); var through = require('through2'); var assign = require('object-assign'); @@ -10,12 +12,13 @@ var PLUGIN_NAME = 'gulp-sass'; ////////////////////////////// // Main Gulp Sass function ////////////////////////////// -var gulpSass = function gulpSass(options) { - 'use strict'; - +var gulpSass = function gulpSass(options, sync) { return through.obj(function(file, enc, cb) { var opts, - callback; + filePush, + errorM, + callback, + result; if (file.isNull()) { return cb(null, file); @@ -36,33 +39,77 @@ var gulpSass = function gulpSass(options) { opts.omitSourceMapUrl = true; } - callback = function(error, obj) { - if (error) { - return cb(new gutil.PluginError( - PLUGIN_NAME, error.message + ' ' + gutil.colors.cyan('line ' + error.line) + ' in ' + gutil.colors.magenta(error.file) - )); - } + ////////////////////////////// + // Handles returning the file to the stream + ////////////////////////////// + filePush = function filePush(sassObj) { // Build Source Maps! - if (obj.map) { - applySourceMap(file, JSON.parse(obj.map.toString())); + if (sassObj.map) { + applySourceMap(file, JSON.parse(sassObj.map.toString())); } - file.contents = obj.css; + file.contents = sassObj.css; file.path = gutil.replaceExtension(file.path, '.css'); cb(null, file); }; - sass.render(opts, callback); + ////////////////////////////// + // Handles error message + ////////////////////////////// + errorM = function errorM(error) { + var relativePath = path.relative(process.cwd(), error.file), + message = ''; + + message += gutil.colors.underline(relativePath) + '\n'; + message += gutil.colors.gray(' ' + error.line + ':' + error.column) + ' '; + message += error.message; + + return cb(new gutil.PluginError( + PLUGIN_NAME, message + )); + }; + + if (sync !== true) { + ////////////////////////////// + // Async Sass render + ////////////////////////////// + callback = function(error, obj) { + if (error) { + return errorM(error); + } + filePush(obj); + }; + + sass.render(opts, callback); + } + else { + ////////////////////////////// + // Sync Sass render + ////////////////////////////// + try { + result = sass.renderSync(opts); + + filePush(result); + } + catch(error) { + return errorM(error); + } + } }); }; +////////////////////////////// +// Sync Sass render +////////////////////////////// +gulpSass.sync = function sync(options) { + return gulpSass(options, true); +}; + ////////////////////////////// // Log errors nicely ////////////////////////////// gulpSass.logError = function logError(error) { - 'use strict'; - gutil.log(gutil.colors.red('[' + PLUGIN_NAME + '] ') + error.message); }; diff --git a/test/lint.js b/test/lint.js index a744f18..ba58856 100644 --- a/test/lint.js +++ b/test/lint.js @@ -10,11 +10,6 @@ var report; describe('code style guide', function() { it('index.js should follow our lint style guide', function(done) { - cli = new eslint.CLIEngine({ - 'rules': { - 'strict': [ 2, 'function' ] - } - }); report = cli.executeOnFiles(['index.js']); if (report.errorCount > 0 || report.warningCount > 0) { console.log(formatter(report.results)); @@ -26,11 +21,6 @@ describe('code style guide', function() { }); it('test/main.js should follow our lint style guide', function(done) { - cli = new eslint.CLIEngine({ - 'rules': { - 'strict': [ 2, 'global' ] - } - }); report = cli.executeOnFiles(['test/main.js']); if (report.errorCount > 0 || report.warningCount > 0) { console.log(formatter(report.results)); @@ -44,8 +34,7 @@ describe('code style guide', function() { it('test/lint.js should follow our lint style guide', function(done) { cli = new eslint.CLIEngine({ 'rules': { - 'no-console': 0, - 'strict': [ 2, 'global' ] + 'no-console': 0 } }); report = cli.executeOnFiles(['test/lint.js']); diff --git a/test/main.js b/test/main.js index df4f8c6..ebec4e1 100644 --- a/test/main.js +++ b/test/main.js @@ -19,7 +19,7 @@ var createVinyl = function createVinyl(filename, contents) { }); }; -describe('gulp-sass', function() { +describe('gulp-sass -- async compile', function() { it('should pass file when it isNull()', function(done) { var stream = sass(); var emptyFile = { @@ -119,7 +119,7 @@ describe('gulp-sass', function() { var stream = sass(); stream.on('error', function(err) { - err.message.indexOf('property "font" must be followed by a \':\'').should.equal(0); + err.message.indexOf('property "font" must be followed by a \':\'').should.not.equal(-1); done(); }); stream.write(errorFile); @@ -154,3 +154,139 @@ describe('gulp-sass', function() { stream.write(sassFile); }); }); + +describe('gulp-sass -- sync compile', function() { + it('should pass file when it isNull()', function(done) { + var stream = sass.sync(); + var emptyFile = { + 'isNull': function () { + return true; + } + }; + stream.on('data', function(data) { + data.should.equal(emptyFile); + done(); + }); + stream.write(emptyFile); + }); + + it('should emit error when file isStream()', function (done) { + var stream = sass.sync(); + var streamFile = { + 'isNull': function () { + return false; + }, + 'isStream': function () { + return true; + } + }; + stream.on('error', function(err) { + err.message.should.equal('Streaming not supported'); + done(); + }); + stream.write(streamFile); + }); + + it('should compile a single sass file', function(done) { + var sassFile = createVinyl('mixins.scss'); + var stream = sass.sync(); + stream.on('data', function(cssFile) { + should.exist(cssFile); + should.exist(cssFile.path); + should.exist(cssFile.relative); + should.exist(cssFile.contents); + String(cssFile.contents).should.equal( + fs.readFileSync(path.join(__dirname, 'expected/mixins.css'), 'utf8') + ); + done(); + }); + stream.write(sassFile); + }); + + it('should compile multiple sass files', function(done) { + var files = [ + createVinyl('mixins.scss'), + createVinyl('variables.scss') + ]; + var stream = sass.sync(); + var mustSee = files.length; + var expectedPath = 'expected/mixins.css'; + + stream.on('data', function(cssFile) { + should.exist(cssFile); + should.exist(cssFile.path); + should.exist(cssFile.relative); + should.exist(cssFile.contents); + if (cssFile.path.indexOf('variables') !== -1) { + expectedPath = 'expected/variables.css'; + } + String(cssFile.contents).should.equal( + fs.readFileSync(path.join(__dirname, expectedPath), 'utf8') + ); + mustSee--; + if (mustSee <= 0) { + done(); + } + }); + + files.forEach(function (file) { + stream.write(file); + }); + }); + + it('should compile files with partials in another folder', function(done) { + var sassFile = createVinyl('inheritance.scss'); + var stream = sass.sync(); + stream.on('data', function(cssFile) { + should.exist(cssFile); + should.exist(cssFile.path); + should.exist(cssFile.relative); + should.exist(cssFile.contents); + String(cssFile.contents).should.equal( + fs.readFileSync(path.join(__dirname, 'expected/inheritance.css'), 'utf8') + ); + done(); + }); + stream.write(sassFile); + }); + + it('should handle sass errors', function(done) { + var errorFile = createVinyl('error.scss'); + var stream = sass.sync(); + + stream.on('error', function(err) { + err.message.indexOf('property "font" must be followed by a \':\'').should.not.equal(-1); + done(); + }); + stream.write(errorFile); + }); + + it('should work with gulp-sourcemaps', function(done) { + var sassFile = createVinyl('inheritance.scss'); + + // Expected sources are relative to file.base + var expectedSources = [ + 'includes/_cats.scss', + 'inheritance.scss' + ]; + + var stream; + + sassFile.sourceMap = '{' + + '"version": 3,' + + '"file": "scss/subdir/multilevelimport.scss",' + + '"names": [],' + + '"mappings": "",' + + '"sources": [ "scss/subdir/multilevelimport.scss" ],' + + '"sourcesContent": [ "@import ../inheritance;" ]' + + '}'; + + stream = sass.sync(); + stream.on('data', function(cssFile) { + should.exist(cssFile.sourceMap); + assert.deepEqual(cssFile.sourceMap.sources, expectedSources); + done(); + }); + stream.write(sassFile); + }); +});