Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grunt Watch Event with Grunt Copy for Only Changed Files

Okay I've been stuck on this for 2 weeks, so hopefully someone else here has run across this problem. I'm trying to use Grunt to copy only files that have changed. I've seen numerous examples of how to do this with JSLINT and UGLIFY but no specific examples on how to do this with grunt-contrib-copy.

When you register a watch event and pass the filename to the copy subtask, the file name is accessible (i'm logging it out), but the file never copies over properly.

I'm hoping its a simple thing that i'm overlooking. Could someone please have a look at my code and see what I'm doing wrong?

//Gruntfile.js:

module.exports = function(grunt) {
grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    options: {
        base: 'app',
        dist: 'dist',
    },

    copy: {
        changedFiles: {
            expand: true,
            dot: true,
            cwd: '<%= options.base %>',
            src: ['**/*.*'],
            dest: '<%= options.dist %>/'

        }
     },
    watch: {
        options: {
            nospawn: true,
            //debounceDelay: 1000,
        },
        css: {
            files: ['app/css/*.css',
                   'app/js/*.js'
                   ],
            tasks: ['copy:changedFiles'],

        }
    }    

});

grunt.event.on('watch', function(action, filepath, target){
    grunt.log.writeln('target: ', target + '\n filepath: ' + filepath + '\n action: has ' + action);
    grunt.config('copy.changedFiles.src', new Array(filepath) );
});

//load our copy task
grunt.loadNpmTasks('grunt-contrib-copy');

//load our watch task
grunt.loadNpmTasks('grunt-contrib-watch');

grunt.registerTask('copyChangedFiles', [
        'watch:css'
]);

};

Basically my folder setup is as such:

-app
| - css
| - js
-dist

I'm watching the app folder and trying to copy files that change in the app directory and copy them to the dist directory. Dynamically modifying the copy src doesn't seem to be working.

The copy task when run by itself with watch and not on the watch event works just fine and copies every file over, but I'm interested in copying only files that change.

I've also tried a variation of this within my watch event, to no avail:

var copyDest = filepath.replace(grunt.config('copy.changedFiles.dest'), '');
var copyCwd = filepath.replace(grunt.config('copy.changedFiles.cwd'), '');
grunt.config('copy.changedFiles.cwd' , copyCwd);
grunt.config(['copy', 'changedFiles', 'src'] , [filepath]);

Has anyone ever successfully done this before using grunt copy? Or is there another task I should be using? I've tried the same with grunt-sync and that didn't seem to work either. I'm stuck.

Thanks for the help.

like image 375
jjsquared Avatar asked Oct 30 '14 22:10

jjsquared


2 Answers

You should be able to use the grunt-newer package. The only thing I have noticed about this is that it doesn't do the delete action if the files are removed from the source and are currently in the destination of the copy.

However, for most purposes this should perform the task that you are looking for. Watch will be triggered on a file change, newer will only run if the files in destination are older than the src.

Note: nospawn is deprecated, and is now spawn. It was left for backwards compatibility.

I'm not sure it makes sense for files: [<pattern>] to not match the src pattern described in the copy task.

module.exports = function(grunt) {
grunt.initConfig({
    options: {
        base: 'app',
        dist: 'dist',
    },
    copy: {
        changedFiles: {
            expand: true,
            dot: true,
            cwd: '<%= options.base %>',
            src: ['**/*.*'],
            dest: '<%= options.dist %>/'
        }
     },
    watch: {
        options: {
            //nospawn is depricated but kept for compatibility.  use spawn false instead
            spawn: false,
            cwd: '<%= options.base %>'
            //debounceDelay: 1000,
        },
        css: {
            //should match above
            files: ['**/*.*'],
            //On new file detection run copy:changedFiles
            tasks: ['newer:copy:changedFiles']
        }
    }     
});

grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-newer');

grunt.registerTask('copyChangedFiles', ['watch:css']);

};
like image 110
Jarrett R Avatar answered Oct 14 '22 02:10

Jarrett R


I was able to use this SO answer: How to modify grunt watch tasks based on the file changed? and modify it to suit my needs. I'm now able to copy only the files that changed.

My watch now looks like this:

 var path = require('path');

 grunt.event.on('watch', function(action, filepath, target){
    grunt.log.writeln(target + ': ' + filepath + ' might have ' + action);
    var siteDirectory = path.dirname(filepath);

    //changes changed file source to that of the changed file
    var option = 'copy.changedFiles.src';
    var result = filepath;
    grunt.log.writeln(option + ' changed to ' + result);
    grunt.config(option, result);

    //customizes output directory so that file goes to correct place
    option = 'copy.changedFiles.dest';
    result = path.resolve(__dirname + '/dist');
    grunt.log.writeln(option + ' changed to ' + result);
    grunt.config(option, result);

});

Now running grunt copyChangedFiles will watch the app directory for changes, and any time a *.css or *.js file is modified, it will copy it over to the dist directory.

I really hope this helps someone else since I spent two weeks getting this to work properly.

like image 25
jjsquared Avatar answered Oct 14 '22 02:10

jjsquared