2

I have a basic application using LESS and grunt watch to compile changes on the fly.

Since a very long time I was complaining about the fact that when I change any LESS file, all get re-compiled. It was such a waste of my time not to be able to compile only what was needed. But I wanted it smart, so if there were any import it would also compile the impacted files, not just the one that gets modified.

Today I decided to fix this. It's not easy at all. Grunt watch don't take care of this, even using grunt-newer didn't work out, in both case it is required to write custom rules.

Finally, I didn't use https://github.com/tschaub/grunt-newer because it doesn't play well with grunt-sync but more importantly, because it doesn't fit my needs with LESS, since it can't deal with the LESS import stuff and so it will not compile files related but only the files that have been changed, it's not "smart".

(It is possible to customize it to take into account that kind of stuff, and people provide some scripts on gist, but there isn't any official repo so it's kinda hard to find what you need, but definitely worth a look.)


It's been a question asked multiple time with no real or easy answer to. See:

How do you watch multiple files, but only run task on changed file, in Grunt.js?

Grunt watch: compile only one file not all

Grunt watch less on changed file only

Grunt: Watch multiple files, Compile only Changed

https://github.com/tschaub/grunt-newer/issues/29

https://gist.github.com/cgmartin/10328349

Community
  • 1
  • 1
Vadorequest
  • 16,593
  • 24
  • 118
  • 215

1 Answers1

0

My solution is a custom solution, specific to my app, but I hope it can be used for yours too! It's pretty simple.

Assuming you have a styles folder containing several files and directories (less), then my solution was to define a white list of folders for which I would not compile all less files, but only those that have been changed.

Grunt-less config: https://gist.github.com/Vadorequest/bd46bb4d6c326e837710

Grunt-watch config: https://gist.github.com/Vadorequest/b48bcfda2d0205ba3f95

Folders architecture: Folders architecture

The "easiest" way to deal with this so far is to override the grunt.watch event and so, to check whether or not the changed file may impact other files. There are several ways depending on your own architecture. Mine is simple enough because files that impact all others are either at the root of the styles folder or inside sub folders. So I "just" had to check if the filePath of the file belongs to the white list or not. If so, then I update the grunt config on the fly, changing the property src to match only the changed file name.

grunt.event.on('watch', function(action, filePath, watchedTargetName) {
        switch(watchedTargetName){
            /*
            Compile only what is needed.
            Based on a white list of sub folders within the "styles" directory.
            White listed folders will not require to compile all LESS file, but only the changed ones.
            Others will require to compile everything.
             */
            case 'styles':
                // Root path from where the files are located.
                var rootPath = 'assets/linker/styles/';
                // Path of the file
                var filePathRelativeToRootPath = path.relative(rootPath, filePath);
                // Grunt task name (see less.js)
                var gruntTask = 'less';
                // Sub task to use.
                var subTaskName = 'dev';
                // List of folders that don't need to recompile everything.
                var whiteListFolders = [
                    'common',
                    'devices',
                    'layous',
                    'themes',
                    'views',
                ];

                if(action === 'changed'){
                    var isDir = path.dirname(filePath) !== '';
                    var dirName = filePathRelativeToRootPath.split(path.sep)[0];

                    // If the file is a directory and is belongs to the white list then we will override the grunt config on the fly to compile only that file.
                    if(isDir && _.contains(whiteListFolders, dirName)){
                        // We load the less config located at tasks/config/less.js
                        var config = grunt.config(gruntTask);

                        // Checks for any misconfiguration.
                        if(!config){
                            log.error('There is no config for the grunt task named ' + gruntTask);
                        }

                        if(!config[subTaskName]){
                            log.error('There is no sub task named ' + subTaskName + " for the the grunt task named " + gruntTask);
                        }

                        // Update the files.src to be the path to the modified file (relative to srcDir).
                        // Instead of updating all files, it will only update the one that has been changed.
                        config[subTaskName].files[0].src = filePathRelativeToRootPath;
                        grunt.config("less", config);
                        console.info('watcher LESS - The file ' + filePath + ' is in the white list and will be updated alone.');
                    }else{
                        console.info('watcher LESS - The file ' + filePath + ' is not is the white list, all LESS files will be updated.');
                    }
                }
                break;
        }
    } );

Basically, if I change a file named ayolan.less within the themes directory then here is what will be set in the memory when I'll update it. (See https://gist.github.com/Vadorequest/b48bcfda2d0205ba3f95#file-watch-js-L80-L81)

{
    dev: {
        files: [{
            expand: true,
            cwd: 'assets/linker/styles/',
            src: 'themes/ayolan.less',// This has been changed in memory, for this specific watch event.
            dest: '.tmp/public/linker/styles/',
            ext: '.css'
        }]
    }
}

This allows me now to use http://www.browsersync.io/ that will update the browser about 1 second after I make a simple change in a LESS file, that was about 5 seconds before, because it had to compile all LESS files and copy them. (I made other performance changes as well, but it definitely helped to reach that goal!)

Vadorequest
  • 16,593
  • 24
  • 118
  • 215