├── .gitignore ├── .gitattributes ├── test ├── fixtures │ ├── partials │ │ └── _partial.scss │ ├── precision.scss │ ├── image-path.scss │ ├── imported.scss │ ├── test.scss │ └── include-paths.scss ├── expected │ ├── compile.css │ └── include-paths.css └── test.js ├── .travis.yml ├── .editorconfig ├── .jshintrc ├── package.json ├── license ├── tasks └── sass.js ├── Gruntfile.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /test/fixtures/partials/_partial.scss: -------------------------------------------------------------------------------- 1 | .foo { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/precision.scss: -------------------------------------------------------------------------------- 1 | p { 2 | line-height: 1.3432423534532423; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/image-path.scss: -------------------------------------------------------------------------------- 1 | .background-image { 2 | background-image: image-url('image.png'); 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - npm install -g grunt-cli 6 | -------------------------------------------------------------------------------- /test/fixtures/imported.scss: -------------------------------------------------------------------------------- 1 | li { 2 | font: { 3 | family: serif; 4 | weight: bold; 5 | size: 1.2em; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/expected/compile.css: -------------------------------------------------------------------------------- 1 | li { 2 | font-family: serif; 3 | font-weight: bold; 4 | font-size: 1.2em; } 5 | 6 | .content-navigation { 7 | border-color: #3bbfce; 8 | color: #2ca2af; } 9 | 10 | .border { 11 | padding: 8px; 12 | margin: 8px; 13 | border-color: #3bbfce; } 14 | -------------------------------------------------------------------------------- /test/expected/include-paths.css: -------------------------------------------------------------------------------- 1 | li { 2 | font-family: serif; 3 | font-weight: bold; 4 | font-size: 1.2em; } 5 | 6 | .content-navigation { 7 | border-color: #3bbfce; 8 | color: #2ca2af; } 9 | 10 | .border { 11 | padding: 8px; 12 | margin: 8px; 13 | border-color: #3bbfce; } 14 | -------------------------------------------------------------------------------- /test/fixtures/test.scss: -------------------------------------------------------------------------------- 1 | @import 'imported'; 2 | 3 | $blue: #3bbfce; 4 | $margin: 16px; 5 | 6 | .content-navigation { 7 | border-color: $blue; 8 | color: 9 | darken($blue, 9%); 10 | } 11 | 12 | .border { 13 | padding: $margin / 2; 14 | margin: $margin / 2; 15 | border-color: $blue; 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/include-paths.scss: -------------------------------------------------------------------------------- 1 | @import 'imported'; 2 | 3 | $blue: #3bbfce; 4 | $margin: 16px; 5 | 6 | .content-navigation { 7 | border-color: $blue; 8 | color: 9 | darken($blue, 9%); 10 | } 11 | 12 | .border { 13 | padding: $margin / 2; 14 | margin: $margin / 2; 15 | border-color: $blue; 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [package.json] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "newcap": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "undef": true, 14 | "unused": "vars", 15 | "strict": true 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-sass", 3 | "version": "0.16.1", 4 | "description": "Compile Sass to CSS using node-sass", 5 | "license": "MIT", 6 | "repository": "sindresorhus/grunt-sass", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "http://sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "grunt" 17 | }, 18 | "files": [ 19 | "tasks" 20 | ], 21 | "keywords": [ 22 | "gruntplugin", 23 | "css", 24 | "sass", 25 | "scss", 26 | "style", 27 | "compile", 28 | "preprocess", 29 | "compile", 30 | "libsass" 31 | ], 32 | "dependencies": { 33 | "chalk": "^0.5.1", 34 | "each-async": "^1.0.0", 35 | "node-sass": "1.0.3", 36 | "object-assign": "^1.0.0" 37 | }, 38 | "devDependencies": { 39 | "grunt": "^0.4.5", 40 | "grunt-contrib-clean": "^0.6.0", 41 | "grunt-contrib-nodeunit": "^0.4.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Sindre Sorhus (sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /tasks/sass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var eachAsync = require('each-async'); 4 | var assign = require('object-assign'); 5 | var chalk = require('chalk'); 6 | var sass = require('node-sass'); 7 | 8 | module.exports = function (grunt) { 9 | grunt.registerMultiTask('sass', 'Compile Sass to CSS', function () { 10 | eachAsync(this.files, function (el, i, next) { 11 | var options = this.options({ 12 | precision: 10, 13 | silent: false 14 | }); 15 | 16 | var src = el.src[0]; 17 | 18 | if (!src || path.basename(src)[0] === '_') { 19 | return next(); 20 | } 21 | 22 | if (!grunt.file.exists(el.dest)) { 23 | grunt.file.write(el.dest, ''); 24 | } 25 | 26 | sass.renderFile(assign({}, options, { 27 | // temp workaround for sass/node-sass#425 28 | file: path.resolve(src), 29 | outFile: path.resolve(el.dest), 30 | success: function (css, map) { 31 | if (!options.silent) { 32 | grunt.log.writeln('File ' + chalk.cyan(el.dest) + ' created.'); 33 | } 34 | 35 | if (options.sourceMap) { 36 | var pth = options.sourceMap === true ? (el.dest + '.map') : path.relative(process.cwd(), map); 37 | 38 | if (!options.silent) { 39 | grunt.log.writeln('File ' + chalk.cyan(pth) + ' created.'); 40 | } 41 | } 42 | 43 | next(); 44 | }, 45 | error: function (error) { 46 | grunt.warn(error); 47 | next(error); 48 | } 49 | })); 50 | }.bind(this), this.async()); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function (grunt) { 3 | grunt.initConfig({ 4 | sass: { 5 | compile: { 6 | files: { 7 | 'test/tmp/compile.css': 'test/fixtures/test.scss', 8 | 'test/tmp/compile2.css': 'test/fixtures/test.scss' 9 | } 10 | }, 11 | includePaths: { 12 | options: { 13 | includePaths: ['test/fixtures'] 14 | }, 15 | files: { 16 | 'test/tmp/include-paths.css': 'test/fixtures/include-paths.scss' 17 | } 18 | }, 19 | imagePath: { 20 | options: { 21 | imagePath: '//cdn.test.com' 22 | }, 23 | files: { 24 | 'test/tmp/image-path.css': 'test/fixtures/image-path.scss' 25 | } 26 | }, 27 | ignorePartials: { 28 | cwd: 'test/fixtures/partials', 29 | src: '*.scss', 30 | dest: 'test/tmp', 31 | expand: true, 32 | ext: '.css' 33 | }, 34 | sourceMap: { 35 | options: { 36 | sourceMap: 'source-map.css.map' 37 | }, 38 | files: { 39 | 'test/tmp/source-map.css': 'test/fixtures/test.scss' 40 | } 41 | }, 42 | sourceMapSimple: { 43 | options: { 44 | sourceMap: true 45 | }, 46 | files: { 47 | 'test/tmp/source-map-simple.css': 'test/fixtures/test.scss' 48 | } 49 | }, 50 | precision: { 51 | options: { 52 | precision: 3 53 | }, 54 | files: { 55 | 'test/tmp/precision.css': 'test/fixtures/precision.scss' 56 | } 57 | } 58 | }, 59 | nodeunit: { 60 | tasks: ['test/test.js'] 61 | }, 62 | clean: { 63 | test: ['test/tmp/**'] 64 | } 65 | }); 66 | 67 | grunt.loadTasks('tasks'); 68 | grunt.loadNpmTasks('grunt-contrib-clean'); 69 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 70 | 71 | grunt.registerTask('default', ['clean', 'sass', 'nodeunit', 'clean']); 72 | }; 73 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var grunt = require('grunt'); 3 | 4 | exports.sass = { 5 | compile: function (test) { 6 | test.expect(2); 7 | 8 | var actual = grunt.file.read('test/tmp/compile.css'); 9 | var actual2 = grunt.file.read('test/tmp/compile2.css'); 10 | var expected = grunt.file.read('test/expected/compile.css'); 11 | test.equal(actual, expected, 'should compile SCSS to CSS'); 12 | test.equal(actual2, expected, 'should compile SCSS to CSS'); 13 | 14 | test.done(); 15 | }, 16 | includePaths: function (test) { 17 | test.expect(1); 18 | 19 | var actual = grunt.file.read('test/tmp/include-paths.css'); 20 | var expected = grunt.file.read('test/expected/include-paths.css'); 21 | test.equal(actual, expected, 'should compile SCSS to CSS with options'); 22 | 23 | test.done(); 24 | }, 25 | imagePath: function (test) { 26 | test.expect(1); 27 | 28 | var actual = grunt.file.read('test/tmp/image-path.css'); 29 | test.ok(/\/\/cdn\.test\.com\/image.png/.test(actual), 'should process image-url according to imagePath'); 30 | 31 | test.done(); 32 | }, 33 | ignorePartials: function (test) { 34 | test.expect(1); 35 | 36 | test.ok(!grunt.file.exists('test/tmp/_partial.css'), 'underscore partial files should be ignored'); 37 | 38 | test.done(); 39 | }, 40 | /* 41 | sourceMap: function (test) { 42 | test.expect(2); 43 | 44 | var css = grunt.file.read('test/tmp/source-map.css'); 45 | test.ok(/\/\*\# sourceMappingURL\=source\-map\.css\.map/.test(css), 'should include sourceMapppingUrl'); 46 | 47 | var map = grunt.file.read('test/tmp/source-map.css.map'); 48 | test.ok(/test\.scss/.test(map), 'should include the main file in sourceMap at least'); 49 | test.done(); 50 | }, 51 | */ 52 | sourceMapSimple: function (test) { 53 | test.expect(2); 54 | 55 | var css = grunt.file.read('test/tmp/source-map-simple.css'); 56 | test.ok(/\/\*\# sourceMappingURL\=source\-map-simple\.css\.map/.test(css), 'should include sourceMappingUrl'); 57 | 58 | var map = grunt.file.read('test/tmp/source-map-simple.css.map'); 59 | test.ok(/test\.scss\"/.test(map), 'should include the main file in sourceMap at least'); 60 | test.done(); 61 | }, 62 | precision: function (test) { 63 | test.expect(1); 64 | 65 | var actual = grunt.file.read('test/tmp/precision.css'); 66 | test.ok(/1\.343/.test(actual), 'should support precision option'); 67 | 68 | test.done(); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # grunt-sass [![Build Status](https://travis-ci.org/sindresorhus/grunt-sass.svg?branch=master)](https://travis-ci.org/sindresorhus/grunt-sass) 2 | 3 | [](https://github.com/sass/node-sass) 4 | 5 | > Compile Sass to CSS using [node-sass](https://github.com/sass/node-sass) 6 | 7 | *Issues with the output should be reported on the libsass [issue tracker](https://github.com/hcatlin/libsass/issues).* 8 | 9 | This task uses [libsass](http://libsass.org) which is an experimental Sass compiler in C++. In contrast to the original Ruby compiler, this one is much faster, but is missing some features, though improving quickly. It also doesn't support Compass. Check out [grunt-contrib-sass](https://github.com/gruntjs/grunt-contrib-sass) if you prefer something more stable, but slower. 10 | 11 | 12 | ## Install 13 | 14 | ```sh 15 | $ npm install --save-dev grunt-sass 16 | ``` 17 | 18 | 19 | ## Usage 20 | 21 | ```js 22 | require('load-grunt-tasks')(grunt); // npm install --save-dev load-grunt-tasks 23 | 24 | grunt.initConfig({ 25 | sass: { 26 | options: { 27 | sourceMap: true 28 | }, 29 | dist: { 30 | files: { 31 | 'main.css': 'main.scss' 32 | } 33 | } 34 | } 35 | }); 36 | 37 | grunt.registerTask('default', ['sass']); 38 | ``` 39 | 40 | Files starting with `_` are ignored to match the expected [Sass partial behaviour](http://sass-lang.com/documentation/file.SASS_REFERENCE.html#partials). 41 | 42 | 43 | ## Options 44 | 45 | ### includePaths 46 | 47 | Type: `array` 48 | Default: `[]` 49 | 50 | Additional paths to look for `@import`'ed files. 51 | 52 | ### outputStyle 53 | 54 | Type: `string` 55 | Default: `nested` 56 | Values: `'nested'`, `'compressed'` 57 | 58 | Specify the CSS output style. 59 | 60 | ### imagePath 61 | 62 | Type: `string` 63 | 64 | Represents the public image path. When using the `image-url()` function in a stylesheet, this path will be prepended to the path you supply. Example: Given an `imagePath` of `/path/to/images`, `background-image: image-url('image.png')` will compile to `background-image: url("/path/to/images/image.png")`. 65 | 66 | ### sourceMap 67 | 68 | Type: `boolean`, `string` 69 | Default: `false` 70 | 71 | Set it to `true` to output a Source Map to the same location as the CSS *(output.css.map)*, or specify a path relative to the CSS file to where you want the Source Map. 72 | 73 | 74 | ### precision 75 | 76 | Type: `number` 77 | Default: `10` 78 | 79 | Number of digits to preserve after the dot. With the number 1.23456789 and a precision of 3, the result will be 1.234 in the final CSS. 80 | 81 | ### silent 82 | 83 | Type: `boolean` 84 | Default: `false` 85 | 86 | Set it to `true` if you don't want to output when a file and source map has been created. 87 | 88 | ## License 89 | 90 | MIT © [Sindre Sorhus](http://sindresorhus.com) 91 | --------------------------------------------------------------------------------