Skip to content

Instantly share code, notes, and snippets.

@axoplasm
Last active January 17, 2018 12:41
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save axoplasm/bdda3922fa43b2ef25d8 to your computer and use it in GitHub Desktop.
Save axoplasm/bdda3922fa43b2ef25d8 to your computer and use it in GitHub Desktop.
Ditching Compass and Sass for LibSass

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:

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:

1. Install Grunt and Bower

More complete instructions at Gruntjs.com and bower.io.

$ npm install -g grunt-cli    
$ npm install -g bower

2. Edit package.json

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"    
   }    
 }    

3. Edit Gruntfile.js

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    
    
};    

4. Edit bower.json

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 <paul@axoplasm.com>"    
  ],    
  "license": "MIT",    
  "ignore": [    
    "**/.*",    
    "node_modules",    
    "bower_components",    
    "test",    
    "tests"    
  ],    
  "dependencies": {    
    "susy": "~2.2.2",    
    "breakpoint-sass": "~2.5.0",    
    "compass-mixins": "~1.0.2"    
  }    
}    

5. Install your node modules and bower components

$ npm install    
$ bower install

The magic Bower component here is compass-mixins. This is a repository of libsass-compatible Sass libraries.

6. Link to local Sass libraries in bower_components

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.

7. Debug library failures in compass-mixins

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;    
  }    
}    

7: Remove Ruby files

Finally, all the old Ruby files, including Bundle files, aren’t necessary, so I just removed them altogether.

$ rm Gemfile*    
$ rm config.rb

Adendum: so is it really faster?

OMG yes. About 10x faster.

@jc00ke
Copy link

jc00ke commented Apr 6, 2015

Nice! I went the UNIX route and did something like this on a project where I needed the compiled stylesheets to be in specific directories:

#!/bin/bash

for app in apps/*; do
  sass  --no-cache \
        --scss \
        --watch \
        "$app/public/stylesheets:$app/public/assets/"
done

I won't have that many apps so I don't really care how many sass processes are running.

@windgazer
Copy link

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:

            options: {
                loadPath: [
                    "bower_components/compass-mixins/lib/",
                    "bower_components/susy/sass/",
                    "bower_components/breakpoint-sass/stylesheets/",
                ],
            }

This would allow you to simply import:

@import "compass";    
@import "susy";    
@import "breakpoint";    

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 :)

@menocomp
Copy link

menocomp commented Aug 11, 2016

@windgazer
there is nothing called "loadPath" in Node Sass, it is "includePaths"
https://github.com/sass/node-sass#includepaths

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment