I had a large client framework extending my personal boilerplate that was taking upwards of 10seconds to compile with standard Ruby Sass. This framework had minimal dependencies:
- Breakpoint for media query handling
- Susy for grids
- and of course Compass
I used Bundler to manage Ruby dependencies and ran tasks with Grunt — mainly compiling Sass via grunt-contrib-compass, and previewing with live-reload. Simple stuff.
But 10seconds was an unacceptable performance hit for me. I typically keep my monitor split in half (using Spectacle ), with a browser on one half and MacVim on the other. With Live Reload running I get a nearly realtime preview of my work … except for that one client framework, where I was getting that 10 second delay. I knew that my saviour would be a non-Ruby version of Sass, namely LibSass, probably run through a Node implementation. (I already had libsass-python for a larger Django project for another client, but this was a mirror dependency for production. I don’t think I ever used libsass-python on my dev box.)
I had a lot of conceptual trouble ditching Compass. For four(?) years now Compass Watch has been my main compiling task. If I didn’t use Compass, how would I configure and load Sass libraries? (Answer: directly as local libraries in your master Sass file(s) [e.g. screen.scss], not as Ruby gems.) And would I need Bundler any more? (No, you’ll use Bower to create local versions of your Sass libs in the project, analogous to managing JS libs through NPM.)
So here’s what I did:
More complete instructions at Gruntjs.com and bower.io.
$ npm install -g grunt-cli
$ npm install -g bower
I removed grunt-contrib-compass
and grunt-contrib-watch
, and added grunt-sass
.
{
"name": "myproject",
"version": "0.1.0",
"devDependencies": {
"grunt": "^0.4.5",
- "grunt-concurrent": "^1.0.0",
- "grunt-contrib-compass": "^1.0.1",
"grunt-contrib-watch": "^0.6.1",
+ "grunt-sass": "^0.18.1"
}
}
Because I wasn’t running concurrent tasks, I removed all my task targets. I’m sure this is bad practice but I didn’t need them. (I’m omitting livereload for clarity.) All we’re watching are Sass files.
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
sass: {
files: ['sass/**/*.{scss,sass}','sass/_partials/**/*.{scss,sass}'],
tasks: ['sass:dist']
},
},
sass: {
options: {
sourceMap: true,
outputStyle: 'expanded'
},
dist: {
files: {
'css/screen.css': 'sass/screen.scss'
}
}
}
});
// plugins
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-contrib-watch');
// Default task(s).
grunt.registerTask('default', ['sass:dist', 'watch']); // functionally the same as running $ grunt watch or $ grunt default
};
We’ll use Bower to manage Sass dependencies locally, much the way I used Bundle to manage gems previously. These are stored locally in bower_components and are vanilla .scss files. This is important later. Instead of using Compass and Ruby to load Sass libraries from your Gem path, I linked directly to them in screen.scss. You’ll probably also want to add bower_components to your .gitignore just like node_modules.
{
"name": "crs-framework",
"version": "0.0.0",
"authors": [
"Paul Souders <[email protected]>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"susy": "~2.2.2",
"breakpoint-sass": "~2.5.0",
"compass-mixins": "~1.0.2"
}
}
$ npm install
$ bower install
The magic Bower component here is compass-mixins. This is a repository of libsass-compatible Sass libraries.
In the top of your master Sass file (screen.scss or similar), instead of importing from the Compass/Gem path (i.e. @import "compass"
) you’ll need to import from the local Bower component:
-@import "compass
-@import "susy";
-@import "breakpoint";
+@import "../bower_components/compass-mixins/lib/compass";
+@import "../bower_components/susy/sass/susy";
+@import "../bower_components/breakpoint-sass/stylesheets/breakpoint";
I’m sure there are better ways of doing this.
LibSass and compass-mixins are not featurewise replacements for Ruby Sass and Compass. Fire up Grunt from your project root and see what breaks:
$ grunt
Running "sass:dist" (sass) task
>> no mixin named input-placeholder
>> Backtrace:
>> sass/_base.scss:283
>> Line 283 Column 12 sass/_base.scss
Warning: Use --force to continue.
Aborted due to warnings.
$
In my case, @mixin input-placeholder
was my sole breakage. So I wrote my own mixin in screen.scss (or wherever) that accomplished most of the same thing:
// ## Temporary mixins to overload mixins missing from compass-mixins ## //
@mixin input-placeholder {
// used in _base.scss
// replaces compass/css/user-interface/input-placeholder()
&::-webkit-input-placeholder {
@content;
}
&:-moz-placeholder {
@content;
opacity: 1;
}
&::-moz-placeholder {
@content;
opacity: 1;
}
&:-ms-input-placeholder {
@content;
}
}
Finally, all the old Ruby files, including Bundle files, aren’t necessary, so I just removed them altogether.
$ rm Gemfile*
$ rm config.rb
OMG yes. About 10x faster.
Nice writeup, I'll probably give it a try soon-ish. However, I noticed your "I'm sure there's better..." remark and in case you're interrested, it starts with configuring your sass-grunt task with an extra option:
This would allow you to simply import:
Although I don't quite have your setup yet, I didn't test this specifically, however I already make use of bower-managed dependencies for my own SASS setup and the loadPath definitely works :)