Converting an Omega 4 Subtheme from Compass to Grunt and Libsass

Display Date
Omega 4
Drupal 7
Sass
Howto

Omega 4 has been great to us when building Drupal 7 websites and applications.  It provides us with a clean starting point to build upon with a powerful and easy to use Grid system (Susy) as well as an efficient layout for using Sass.  Most of our themes were built using the Ruby-based Compass compiler.  While we love Sass, we have known Compass to be slow, buggy, and crash frequently.  There is nothing more annoying than refreshing and not seeing your changes only to find that Compass has crashed again.

For our Drupal 8 implementations we have been using the Bootstrap base theme with the Sass starterkit, and compiling our Sass files using Libsass and the node.js Grunt task manager.  We found it to be significantly faster and more stable so we decided to convert our existing Compass Omega 4 themes so we would have a consistent development environment for our profjects.  If you are building a new subtheme of Omega 4 the default starterkit now runs with Libsass and Gulp (an alternative to Grunt).

As a prerequisite for this task, ensure that you have Node.js installed on your server and than npm is working.  If you do not yet have Grunt and Bower installed yet, install them globally:

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

To begin, we removed all of our existing compiled CSS so that we would know nothing we saw was left behind from Compass.  From your theme directory run:

find css -type f -name '*.css' -delete -o -name '*.map' -delete

If you check in on your theme now you should see that all of your CSS is gone.

EAC Website without CSS

Now that our theme is properly broken, it is time to get set up with Grunt and Libsass.  Your theme most likely already has a package.json, bower.json and Gruntfile.js from the Omega starterkit.  This is set up to use a Grunt to manage Ruby Compass, which is not our plan, so lets delete them and init a new node package.  Follow the instructions on screen.  The only setting which matters for our needs is the name, set it the same as your theme.

$ rm package.json bower.json Gruntfile.js
$ npm init
$ bower init

Now lets add Grunt, Grunt Watch, Libsass, Sass Globbing, Compass Importer, Normalize, Singularity and Susy to our project:

$ npm install grunt --save-dev
$ npm install grunt-contrib-watch --save-dev
$ npm install grunt-sass --save-dev
$ npm install grunt-sass-globbing --save-dev
$ npm install compass-importer --save-dev
$ bower install normalize-libsass --save-dev
$ bower install sass-toolkit --save-dev
$ bower install susy --save-dev
$ bower install breakpoint-sass --save-dev
$ bower install singularity --save-dev

Now create a new Gruntfile.js with the following contents:

'use strict';
var compass = require('compass-importer');

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({

    // Grunt-sass-globbing
    sass_globbing: {
      app: {
        files: {
          'sass/_variablesMap.scss': ['sass/variables/**/*.scss'],
          'sass/_abstractionsMap.scss': ['sass/abstractions/**/*.scss'],
          'sass/_baseMap.scss': ['sass/base/**/*.scss'],
          'sass/_componentsMap.scss': ['sass/components/**/*.scss']
        },
        options: {
          useSingleQuotes: false,
          signature: '// Hello, World!'
        }
      }
    },

    // Grunt-sass
    sass: {
      app: {
        // Takes every file that ends with .scss from the scss
        // directory and compile them into the css directory.
        // Also changes the extension from .scss into .css.
        // Note: file name that begins with _ are ignored automatically
        files: [{
          expand: true,
          cwd: 'sass',
          src: ['**/*.scss'],
          dest: 'css',
          rename  : function (dest, src) {
            var folder    = src.substring(0, src.lastIndexOf('/'));
            var filename  = src.substring(src.lastIndexOf('/'), src.length);
            filename  = filename.substring(0, filename.lastIndexOf('.'));
            return dest + '/' + folder + filename + '.css';
          }
        }]
      },
      options: {
        sourceMap: true,
        outputStyle: 'expanded',
        imagePath: "../",
        importer: compass
      }
    },

    // Grunt-contrib-watch
    watch: {
      sass: {
        files: ['sass/**/*.scss'],
        tasks: ['sass:app']
      },
      sass_globbing: {
        files: ['sass/**/*.scss'],
        tasks: ['sass_globbing']
      },
      options: {
        // Sets livereload to true for livereload to work
        // (livereload is not covered in this article)
        livereload: true,
        spawn: false
      }
    },
  });

  // Loads Grunt Tasks
  // ...
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-sass-globbing');

  // Default task(s).
  // ...
  grunt.registerTask('default', ['sass_globbing', 'sass', 'watch']);
}

This is a very basic Gruntfile to compile the CSS and does not cover the many other possible Grunt tasks you may wish to set up, but it will get us up and running.

Now it is time to run grunt and attempt to compile our theme.  If you are using susy and breakpoints in your theme be prepared for errors.  You will need to update your import statements to point to the bower versions of Susy and Breakpoint-Sass that we installed earlier.  For instance in our theme we had errors from our layout:

>> Error: File to import not found or unreadable: breakpoint.
>> Error: File to import not found or unreadable: susy.

Our layout file is three levels deep in sass/layouts/eac so we needed to update our imports to be as follows:

@import "../../../libraries/susy/sass/susy";
@import "../../../libraries/breakpoint-sass/stylesheets/breakpoint";

We also need to replace Normalize, Singularity and Toolkit with the versions installed with Bower.  Find references to:

@import "normalize";
@import "toolkit/kickstart";
@import "singularitygs";

and replace with:

@import "../libraries/normalize-libsass/normalize";
@import "../libraries/sass-toolkit/stylesheets/toolkit/kickstart";
@import "../libraries/singularity/stylesheets/singularitygs";

The next error we contend with is our globbing lines.  Wildcard imports are not supported in Libsass, and from the discussion on GitHub it doesn't sound like they ever will be.  Rather than reorganize out Omega 4 theme, we used the grunt-sass-globbing library in our Gruntfile above to emulate the functionality.  We will need to replace:

@import "variables/**/*";
@import "abstractions/**/*";
@import "base/**/*";
@import "components/**/*";

with:

@import "variablesMap";
@import "abstractionsMap";
@import "baseMap";
@import "componentsMap";

Run `grunt` again and unless you are using other external libraries you should see no errors and your css directory should be populated once again.  Congratulations!  Enjoy the speed and stability of Libsass.

EAC Website with CSS