سؤال

I want to have a gruntfile with 2 tasks: less (compiles all less files) and watch (listens to changes and re-compiles the changed file).

I have the following Gruntfile.js:

module.exports = function(grunt) {
    var files = [
        {
            expand: true,
            cwd: 'media/less',
            src: ['*.less'],
            dest: 'media/css/',
            ext: '.css'
        },
        {
            expand: true,
            cwd: 'media/less/vendor',
            src: ['*.less'],
            dest: 'media/css/vendor/',
            ext: '.css'
        },
        {
            expand: true,
            cwd: 'media/admin/less',
            src: ['*.less'],
            dest: 'media/admin/css/',
            ext: '.css'
        }
    ];

    grunt.initConfig({
        less: {
            development: {
                options: {
                    compress: false,
                    yuicompress: true,
                    optimization: 2
                },
                files: files
            },
            production: {
                options: {
                    compress: true,
                    yuicompress: true,
                    optimization: 2
                },
                files: files
            }
        },
        watch: {
            styles: {
                files: ['media/**/*.less'],
                tasks: ['less:development'],
                options: {
                    nospawn: true
                }
            }
        }
    });

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

    grunt.registerTask('default', ['less:development']);
};

The less task runs correctly without any problems. The watch task however listens to changes, but re-compiles all files when one is changed. I suspect it's something to do with how I set up my less task, because I want my less file list to be dynamic and not to add each file manually.

As per this answer grunt should already support this, but I'm unsure how.

هل كانت مفيدة؟

المحلول

Ended up using the watch event and overriding the files property of the less task. Here's my final code:

module.exports = function(grunt) {
    var files = [
        {
            expand: true,
            cwd: 'media/less',
            src: ['*.less'],
            dest: 'media/css/',
            ext: '.css',
            extDot: 'last'
        },
        {
            expand: true,
            cwd: 'media/less/vendor',
            src: ['*.less'],
            dest: 'media/css/vendor/',
            ext: '.css',
            extDot: 'last'
        },
        {
            expand: true,
            cwd: 'media/admin/less',
            src: ['*.less'],
            dest: 'media/admin/css/',
            ext: '.css',
            extDot: 'last'
        }
    ];

    grunt.initConfig({
        less: {
            development: {
                options: {
                    compress: false,
                    yuicompress: true,
                    optimization: 2
                },
                files: files
            },
            production: {
                options: {
                    compress: true,
                    yuicompress: true,
                    optimization: 2
                },
                files: files
            }
        },
        watch: {
            styles: {
                files: ['media/**/*.less'],
                tasks: ['less:development'],
                options: {
                    nospawn: true
                }
            }
        }
    });

    grunt.event.on('watch', function(action, filepath){
        // ignore include files, TODO: have naming convention
        // if an include file has been changed, all files will be re-compiled
        if(filepath.indexOf('.inc.') > -1)
            return true;

        // might not be the most efficient way to do this
        var srcDir = filepath.split('/');
        var filename = srcDir[srcDir.length - 1];
        delete srcDir[srcDir.length - 1];
        srcDir = srcDir.join('/');
        var destDir = srcDir.replace(/less/g, 'css');

        grunt.config('less.development.files', [{
            src: filename,
            dest: destDir,
            expand: true,
            cwd: srcDir,
            ext: '.css',
            extDot: 'last'
        }]);
    });

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

    grunt.registerTask('default', ['less:development']);
};

نصائح أخرى

I'm unsure what you mean with the current title of your question Grunt watch less on changed file only. Do you mean that is a problem? That's the expected behavior of a watch task, it will watch the files specified for changes and run the tasks you specified - in this case the LESS compilation.

I did some changes to your file. Some of it was simplified, some was changed keeping flexibility and expandability of the script in mind.

First install underscore as a dependency, by running:

npm install underscore --save-dev

Then do the following changes to your Gruntfile.js:

module.exports = function(grunt) {

    var _ = require('underscore');

    var files = {
        app : {
            '<%= path.styles.css %>/styles.css' : '<%= path.styles.less %>/*.less'
        },
        vendor : {
            '<%= path.styles.css %>/styles-vendor.css' : '<%= path.styles.vendor %>/*.less'
        },
        admin : {
            '<%= path.styles.css %>/styles-admin.css' : '<%= path.styles.admin %>/*.less'
        }
    }

    function all() {
        'use strict';
        var allfiles = {},
            i = {};

        for (i in files) {
            _.extend(allfiles, files[i]);
        }
        return allfiles;
    }

    grunt.initConfig({
        path : {
            media : 'media',
            styles : {
                css: 'media/css',
                less: 'media/less',
                admin: 'media/admin/less',
                vendor: '<%= path.styles.less %>/vendor'                
            }
        },
        less: {
            development: {
                options: {
                    compress: false,
                    yuicompress: true,
                    optimization: 2
                },
                files: (all())
            },
            production: {
                options: {
                    compress: true,
                    yuicompress: true,
                    optimization: 2
                },
                files: (all())
            }
        },
        watch: {
            styles: {
                files: ['<%= path.media %>/**/*.less'],
                tasks: ['less:development'],
                options: {
                    nospawn: true
                }
            }
        }
    });

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

    // run several tasks as default (handy for complex projects)
    grunt.registerTask('dist', [ // run with 'grunt dist'
        'less:production'
    ]);
    grunt.registerTask('dev', [ // default, will run with 'grunt' only
        'less:development'
    ]);

    grunt.registerTask('default', 'dev');
};

If what you want is to actually compile the sets of files separately (files.app, files.vendor & files.admin), you might need to split the task some more, like so:

        less: {
            app: {
                options: {
                    compress: false,
                    yuicompress: true,
                    optimization: 2
                },
                files: files.app
            },
            vendor: {
                options: {
                    compress: true,
                    yuicompress: true,
                    optimization: 2
                },
                files: files.vendor
            },
            admin: {
                options: {
                    compress: true,
                    yuicompress: true,
                    optimization: 2
                },
                files: files.admin
            },
            development: {
                options: {
                    compress: false,
                    yuicompress: true,
                    optimization: 2
                },
                files: (all())
            },
            production: {
                options: {
                    compress: true,
                    yuicompress: true,
                    optimization: 2
                },
                files: (all())
            }
        },
        watch: {
            all: {
                files: ['<%= path.media %>/**/*.less'],
                tasks: ['less:development'],
                options: {
                    nospawn: true
                }
            },
            app : {
                files: ['<%= path.styles.less %>/*.less'],
                tasks: ['less:app'],
                options: {
                    nospawn: true
                }
            },
            vendor : {
                files: ['<%= path.styles.vendor %>/*.less'],
                tasks: ['less:vendor'],
                options: {
                    nospawn: true
                }
            },
            admin : {
                files: ['<%= path.styles.admin %>/*.less'],
                tasks: ['less:admin'],
                options: {
                    nospawn: true
                }
            }
        }

Then you could then run either of these:

grunt watch:app
grunt watch:vendor
grunt watch:admin

You can always to choose to run the tasks directly, once:

grunt less:app
grunt less:vendor
grunt less:admin

Hope this helps! Please note that I haven't tested this.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top