Skip to content

Instantly share code, notes, and snippets.

@stephenharris
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephenharris/2b5a178296b4cd46e778 to your computer and use it in GitHub Desktop.
Save stephenharris/2b5a178296b4cd46e778 to your computer and use it in GitHub Desktop.
Automated generation of 'colour schemed' stylesheets from less files.

Description of problem

You have a directory (colour-scheme/) containing .less (each defining a different colour scheme). For each of those files (call it scheme-x.less), compile it with the "core" .less file(s) (stored elsewhere) to create style-x.css.

Preferably, by adding a .less file to colour-scheme/ directory and running a grunt task, the appropriate style-?.css should be created. That is, no editing of Gruntfile.js is required.

Directory layout is below.

assets/css
|
| — less/
|
| — core.less
|
| — colour-schemes/
| — scheme-red.less
| — scheme-blue.less
| — scheme-green.less
|
| — style-red.css (generated)
| — style-blue.css (generated)
| — style-green.css (generated)
@franz-josef-kaiser
Copy link

None of that is tested.

You will need a step in between: Cache.


~/assets/css
├── less
│     └── core.less
├── colour-schemes
│     ├── scheme-red.less
│     └── scheme-green.less
└── .less-cache
│     ├── core-red.less
│     └── core-green.less
├ core-red.css
└ core-green.css

The tasks you need are grunt-contrib-concat and grunt-contrib-less


grunt.loadNpmTasks( 'grunt-contrib-concat' );
grunt.loadNpmTasks( 'grunt-contrib-less' );

grunt.registerTask(
    'colours',
    [
        'concat:red',
        'concat:blue',
        'less'
    ]
);

And the tasks should be configured like this:

grunt.initConfig( {

    config : {
        root    : 'assets/css',
        main    : 'core',
        colours : 'colour-schemes',
        cache   : '.less-cache'
    },

    concat : {
        red : {
            src  : [
                '<%= config.root %>/less/<%= config.colours %>/scheme-red.less',
                '<%= config.root %>/<%= config.main %>.less'
            ],
            dest : '<%= config.root %>/<%= config.cache %>/<%= config.main %>-red.less'
        },
        green : {
            src  : [
                '<%= config.root %>/less/<%= config.colours %>/scheme-green.less',
                '<%= config.root %>/<%= config.main %>.less'
            ],
            dest : '<%= config.root %>/<%= config.cache %>/<%= config.main %>-green.less'
        }
    },

    less : {
        options : {
            compress : false,
            report   : 'gzip'
        },
        dev : {
            options : {
                path : [
                    '<%= config.root %>/less/**/*.less'
                ]
            },
            files : {
                '<%= config.root %>/<%= config.main %>-red.css' :
                    '<%= config.root %>/<%= config.cache %>/<%= config.main %>-red.less'
            }
        }
    }
} );

Note: I'd recommend using a distinct distribution/deploy target directory for the concatenated and processed .css file.

@bosconian-dynamics
Copy link

Building off of kaiser's contrib-less/contrib-concat implementation, here is one way in which you can resolve the problem dynamically (turns out I was wrong about using a multitask - doing so causes the task to fail as it requires explicit configuration properties for each individual target. Regular custom tasks don't test the Grunt environment before executing, and thus don't fail as a result of missing target configs):

var path = require( 'path' );
var fs = require( 'fs' );

module.exports = function( grunt ) {
    // Initialize Grunt configuration
    grunt.initConfig({
        colourScheme: {     // Default configuration for the colourScheme task
            paths: {                // Relative path parts. All paths but 'root' are relative to 'root'
                root: 'assets/css',                 // Relative to Grunt's process.cwd()
                lessCore: 'less',                   // Directory containing the core LESS file
                lessSchemes: 'colour-schemes',      // Directory contianing individual colour schemes
                lessCache: '.less-cache',           // Directory to which concatenated LESS files will be written
                dest: '.'                           // Directory to which the final compiled CSS file will be written
            },
            filenames: {
                lessCore: 'core'                    // Name of the primary LESS file that schemes will be compiled with
            }
        },
        concat: {
            colourScheme: {         // Important bits established at runtime in compileColourScheme()
            }
        },
        less: {
            colourScheme: {         // Important bits established at runtime in compileColourScheme()
            }
        }
    });

    // Load external tasks

    grunt.loadNpmTasks( 'grunt-contrib-concat' );
    grunt.loadNpmTasks( 'grunt-contrib-less' );

    // Register custom tasks

    grunt.registerTask( 'colourScheme', function( colour ) {
        var config = grunt.config.get( this.name );
        var colourSchemesDir = path.join(
            config.paths.root,
            config.paths.lessSchemes
        );
        var files;

        if( 'undefined' === typeof colour || ! colour )
            files = fs.readdirSync( colourSchemesDir );
        else
            files = [ colourSchemesDir + '/scheme-' + colour + '.less' ];

        for( var i = 0; i < files.length; i++ ) {
            compileColourScheme( files[ i ], config );
        }
    });

    // Define utility functions

    /**
     * Compiles the specified LESS file with the LESS core file to produce a colour scheme stylesheet.
     *
     * @param {string} lessScheme The path to a colour scheme's LESS file relative to the Gruntfile
     * @param {object} config
     */
    function compileColourScheme( lessScheme, config ) {
        if( 'undefined' === typeof config || ! config )
            config = grunt.config.get( 'colourScheme' );

        if( fs.existsSync( lessScheme ) ) {
            var colour = lessScheme.slice(
                lessScheme.lastIndexOf( 'scheme-' ) + new String('scheme-').length,
                lessScheme.lastIndexOf( '.less' )
            );
            var lessCoreDir = path.join(
                config.paths.root,
                config.paths.lessCore
            );
            var lessCoreFile = path.join(
                lessCoreDir,
                config.filenames.lessCore + '.less'
            );
            var lessCacheFile = path.join(
                config.paths.root,
                config.paths.lessCache,
                config.filenames.lessCore + '-' + colour + '.less'
            );
            var destCssFile = path.join(
                config.paths.root,
                config.paths.dest,
                config.filenames.lessCore + '-' + colour + '.css'
            );
            var compilationFileMap = {};

            // Can't use an expression as an inline obj def property key - establish less task's "files" option here
            compilationFileMap[ destCssFile ] = lessCacheFile;

            // Establish run-time options for the concat:colourScheme task-target
            grunt.config.set( 'concat.colourScheme.src', [ lessCoreFile, lessScheme ] );
            grunt.config.set( 'concat.colourScheme.dest', lessCacheFile );

            // Establish run-time options for the less:colourScheme task-target
            grunt.config.set( 'less.colourScheme.files', compilationFileMap );
            grunt.config.set( 'less.colourScheme.options.path', lessCoreDir );

            // Execute the concat:colourScheme and less:colourScheme tasks sequentially
            grunt.task.run( [ 'concat:colourScheme', 'less:colourScheme' ] );
        }
        else {
            console.log('Scheme source file "' + lessScheme + '" does not exist.');
        }
    }
};

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