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?
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.
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']);
};
In this issue, Kyle Robinson suggests using the watch
event. 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
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.
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).
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.
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');