44

In learning how to use grunt, I am trying to make a simple coffee-script watcher/compiler. The problem is, if I tell the watch task to watch several files, and one changes, it is going to pass all of the files to the coffee command. This means when you change 1 file, it's going to recompile all of the files matching the src pattern. Instead, I want to only recompile the single file that changed that matches the src pattern.

Here is the grunt.js:

module.exports = function(grunt) {
  grunt.initConfig({
    coffee: {
      app: {
        src: ['test/cases/controller/*.coffee'],
        dest: 'tmp',
        options: {
          bare: true,
          preserve_dirs: true
        }
      }
    },
    watch: {
      files: ['<config:coffee.app.src>'],
      tasks: ['coffee:app']
    }
  });

  grunt.loadNpmTasks('grunt-coffee');
  grunt.registerTask('default', 'coffee');
};

This is using grunt-coffee, which is basically this: https://gist.github.com/2373159.

When I run grunt watch, and I save a file in test/cases/controller/*.coffee, it compiles all of the matching files (putting them in tmp/*).

How do you instead only compile the changed file using grunt?

Lance
  • 75,200
  • 93
  • 289
  • 503

7 Answers7

15

The upcoming (and currently in-development) v0.4.0a grunt has the grunt.file.watchFiles object, which was designed expressly for this purpose. The grunt-coffee plugin may have added support for this feature already, I'm not sure.

Either way, if you're interested in trying an in-development version of grunt in your project, check out the When will I be able to use in-development feature 'X'? FAQ entry.

Cowboy Ben Alman
  • 1,941
  • 1
  • 13
  • 10
  • 6
    Yeah, so this doesn't exist anymore, and this is still being discussed. Watch https://github.com/gruntjs/grunt-contrib-watch/issues/14 – Ethan J. Brown Jan 03 '13 at 18:17
  • 1
    This is a problem with node.js in general. Gaze has wrapped this and it is getting close to a fix. see if you can help. https://github.com/shama/gaze/issues/14 – Lance Mar 15 '13 at 16:46
  • Anyone new to this feature should look here for more info on "watch" -> https://github.com/gruntjs/grunt-contrib-watch – blong Jul 09 '13 at 16:13
8

I got this working when compiling my less files. You should be able to mess with this configuration a little bit to git it working with the coffeescript plugin. The portion of interest is the grunt.event.on('watch', ...). In this event handler I'm updating the files property in the less command to only contain the changed file.

path = require('path');

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    less: {
      development: {
        options: {
          paths: ["./library/less"],
        },
        files: [
          { src: "./library/less/bootstrap.less", dest: "./library/css/bootstrap.css"},
          { src: "./library/less/app.less", dest: "./library/css/app.css"}
        ]
      }
    },

    watch: {
      styles: {
        files: "./library/less/*",
        tasks: ["less"],
        options: {
          nospawn: true,
        },
      },
    },
  });

  // Event handling
  grunt.event.on('watch', function(action, filepath){
    // Update the config to only build the changed less file.
    grunt.config(['less', 'development', 'files'], [
      {src: filepath, dest: './library/css/' + path.basename(filepath, '.less') + '.css'}
    ]);
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Tasks
  grunt.registerTask('default', ['watch']);
};
blachniet
  • 4,323
  • 5
  • 33
  • 34
  • This method doesn't work with [building files dynamically](http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically). Any workarounds? – laggingreflex Jun 24 '14 at 09:49
  • @blachniet: This is great! But I still got a problem - maybe you know the answer to it. Is it possible to overwrite a single property of the options? So for example within the watch event handler I use "grunt.config(['sftp', 'widgets', 'options.srcBasePath'], file_path);". I was hoping to use the options I already set in the sftp config but add a single key value pair to it. Is that possible? With my code above nothing happens. With "grunt.config(['sftp', 'widgets', 'options'], {"srcBasePath:":file_path});" all other options are overwritten... – user2718671 Jan 10 '18 at 15:52
  • @blachniet: Nevermind^^ - grunt.config(['sftp', 'widgets', 'options', 'srcBasePath'], file_path); worked for setting a single key value pair. ;) – user2718671 Jan 10 '18 at 15:58
4

None of these answers worked very well for me. Here is my solution if anyone is interested (I know I'm a little late in replying to this question).

https://gist.github.com/ryansmith94/8569178

Ryan Smith
  • 1,255
  • 2
  • 13
  • 16
3

In this issue, Kyle Robinson suggests using the watchevent. It's very important to set watch task nospawn property to true to make it work. I modified his solution to selectively run the tasks:

grunt.event.on('watch', function(action, filepath) {
    if (minimatch(filepath, grunt.config('watch.stylesheets.files'))) {
        grunt.config('compass.dist.options.specify', [filepath]);
    }

    if (minimatch(filepath, grunt.config('watch.scripts.files'))) {
        var uglifySrc = filepath.replace(grunt.config('uglify.dist.cwd'), '');
        grunt.config('jshint.dist.src', [filepath]);
        grunt.config('uglify.dist.src', [uglifySrc]);
    }
});

Here is the complete solution: https://gist.github.com/luissquall/5408257

luissquall
  • 1,740
  • 19
  • 14
3

https://github.com/tschaub/grunt-newer looks like exactly for similar tasks:

Configure Grunt tasks to run with newer files only.

Synopsis: The newer task will configure another task to run with src files that are a) newer than the dest files or b) newer than the last successful run (if there are no dest files). See below for examples and more detail.

You can prepend easily to any task. In your case:

  grunt.loadNpmTasks('grunt-newer');
  grunt.registerTask('default', 'newer:coffee');
Lajos Veres
  • 13,595
  • 7
  • 43
  • 56
2

So new to Grunt 0.4 is more named tasks

Let us give you an example!

watch: {
    package1: {
        files: [
            './modules/package1/**/*.coffee'
        ],
        tasks: ['coffee:package3']
    },
    package2: {
        files: [
            './test_packages/package2/**/*.coffee'
        ],
        tasks: ['coffee:package3']
    },
    package3: {
        files: [
            './test_packages/package3/**/*.coffee'
        ],
        tasks: ['coffee:package3']
    },
}

To run all those watch tasks, simply do grunt.registerTask('default', ['myInitialBuild', 'watch']);

Where myInitialBuild is whatever initial build (all of the files) then follow it up with a watch on each package. In reality you could do this for every file but that sounds sucky.

ThePrimeagen
  • 4,462
  • 4
  • 31
  • 44
2

The task grunt-contrib-watch now supports this.

https://npmjs.org/package/grunt-contrib-watch -> look for "Compiling Files As Needed"

grunt.initConfig({
  watch: {
    scripts: {
      files: ['lib/*.js'],
      tasks: ['jshint'],
      options: {
        nospawn: true,
      },
    },
  },
  jshint: {
    all: ['lib/*.js'],
  },
});

// on watch events configure jshint:all to only run on changed file
grunt.event.on('watch', function(action, filepath) {
  grunt.config(['jshint', 'all'], filepath);
});

This should prevent the tasks from compiling all files every time when something changes.

smets.kevin
  • 1,570
  • 14
  • 17
  • 1
    For how long does the new value set with `grunt.config(['jshint', 'all'], filepath);` take effect? Does it revert back to the previous setting after the task is complete? – zakdances Aug 17 '13 at 23:17