Creating customised LESS/SASS with your projects in Yeoman

When Grunt, Bower & Yeoman first came into prominence within the developer community I, along with most others, revelled at its potential. It held the possibility of simplifying repetitive processes, and packaging and distributing code without the worry of dependencies.

Yeoman interested me the most with its ability to create new projects catered to your needs from scratch via the command line. The problem came in being able to make your LESS or SASS files follow this idea of customising based on what you need. It took a bit of digging but I think I’ve found a pretty decent solution…

First a bit of context:

Tangent Snowball, like most agencies, has a set of house styles that are created as the basis for most prototypes and projects, and contains our best practices and tried & tested features. However, its fair to say that almost all of the projects it is used for won’t need every one of these; the result being a tedious task involving picking out which features aren’t needed…oh and the JavaScript and styles it requires…and oh wait the responsive styles relating to this as well…wait, did I need that?

I was excited at the prospect of streamlining this process once and for all with Yeoman.

But I ran in to a problem.

That problems name was LESS.

My initial Yeoman generator was pretty standard (and I assume by the fact you’re reading this, that you have created one too. If not, I recommend checking out this articleLINK 1 first to make the rest of this post a bit more relevant)

With the command ‘yo housestyles’ the user would be prompted with a checkbox where they could choose which features they would like, for example:

Screen Shot 2014-08-26 at 12.12.56

The choices would then be fed back to the generator, and would be handled through the prompts function

this.prompt(prompts, function (answers) {
    var features = answers.features;
    function hasFeature(feat) { return features.indexOf(feat) !== -1; }

self.prompt() method.
    this.modernizrUse = hasFeature('modernizrUse');
    this.icomoonUse = hasFeature('icomoonUse');
    this.responsiveUse = hasFeature('responsiveUse');
    cb();
  }.bind(this));

The result is that, in your HTML file, you could specify whether something is included or not like so:

<% if(icomoonUse) { %>
    //Bring in CSS plus anything else you might to what to do here
<% } %>

Now unfortunately where LESS is concerned, the template tags don’t work, and applying any conditional style inclusion or omission into the CSS itself would be overwritten the minute the user recompiled the styles. This is because the CSS is compiled from a main LESS file that uses @import to bring in other smaller LESS files. What’s more, if we were to just remove the LESS file of the feature we didn’t want (alerts.less for example), the user would receive an error when trying to compile, as the main less file would be looking to import a file that no longer existed.

So to make this plausible, we need the main less file to be customisable as well. The solution I think is a fairly neat one, and involves the use of two functions:


1) readFileAsString()

This allows us to get access to the contents of a file and manipulate it; exactly what we’re hoping to achieve through the custom checkboxes in the command line.

var path = 'static/css/less/styles.less',
    file = this.readFileAsString(path);

Now that we have the contents of the file, we need to alter it:


2) replace()

Fairly obvious but we use it in such a way as to avoid having to re-write the whole main LESS file. We start by removing all the imports for LESS files from the main file that could be added through the checkboxes. Then, we add a ‘hook’ into the file that we can identify as the place that we want to add the LESS files for whichever features the user has decided.

Here is an example if the user chose to add tabs

// whatever you write here should be at the bottom of your main LESS file
var hook = '// Yeoman additions',
    insertTab = ‘@import tabs.less’;

// if the user chose tabs in the checkbox
if(this.tabUse == true) {
// replace the hook with the import string, and then add the hook after in case you want to do something similar again
    file = file.replace(hook, insertTab+'\n'+hook);
}

//write the altered file
this.write(path, file)

And that’s that! You can use the same approach for as many custom features as you want, just make sure to change the file all you want before you call this.write.

I have the code for my current implementation on Github here and if you have any questions just drop me a message.