9

Given the following piece of code from my gulpfile.js, everytime I save or change a file, the task runs twice instead of one single time, why is that? I just want it to run one time.

var gulp = require('gulp');

gulp.task('default', function() {

  gulp.watch('server/**/*.js', function(){
    console.log('This runs twice everytime I change/save a javascript file located at server/**/*.js');
  }); 

});

I have also experienced the same with grunt and the plugin called grunt-contrib-watch.

Kyle Robinson Young
  • 13,732
  • 1
  • 47
  • 38
Øyvind
  • 839
  • 2
  • 15
  • 22
  • 1
    With that exact same code, it runs only once for me. Are you using an editor that would touch another file when saving? What platform are you using? What else is in your gulpfile? What version of gulp is that? – Mangled Deutz Feb 06 '14 at 16:54
  • My guess is that your editor saves a temporary file first, then renames it, which would trigger the task twice. – Paul Mougel Feb 06 '14 at 17:01
  • That is the only thing in my gulpfile, I have another gulpfile for other projects, same issue there. I'm using gulp version 3.5.1. I'm sitting on a macbook pro and my editor is Coda 2. You're tip about my editor made me try the same file in Webstorm, seems like it only run once there – Øyvind Feb 06 '14 at 17:20
  • Any tips to fix this issue in Coda 2? – Øyvind Feb 06 '14 at 17:26
  • Do you need to ignore a temporary folder? – robrich Feb 24 '14 at 04:48
  • I'm not sure, cause I dont know how Coda 2 handles the file saving. If I knew there was a temp folder within the project that Coda 2 saves a temp file to, I guess a solution could be to have gulp ignore that folder somehow. – Øyvind Feb 24 '14 at 11:58

12 Answers12

13

The problem is occurring because your editor, in this case Coda 2, is modifying the file twice on save. The same problem occurs in vim because of how vim creates buffer backups on save.

The solution to the problem in vim is to add

set nowritebackup

to your ~/.vimrc file. This changes the default save protocol to only make one edit to the original file.

In other words, the default save protocol is as follows:

  1. Write the buffer to the backup file
  2. Delete the original file
  3. Rename the backup to the name of the original file

And adding set nowritebackup simply replaces the original file on save. This protocol exists to reduce risk of data loss in the event of an I/O error on save.

Community
  • 1
  • 1
Ryan Atallah
  • 2,977
  • 26
  • 34
  • 1
    Thanks for illuminating why this is happening, but I'm not actually comfortable with the solution. – tremby May 26 '15 at 21:53
  • grr... I tried `set backupcopy=yes` to no avail so coming across this setting I was so hopeful! but it still fails for me (with or without the backupcopy setting). I'm on OSX 10.11.2 (El Capitan) with vim v.7.3. any other thoughts? – ekkis Dec 25 '15 at 03:53
  • Thank you for this! I saw from the output of fswatch what was happening but did not know how to stop it from happening. – Felix Schlitter Jan 26 '16 at 23:45
  • I use sublime and it does similar thing. At least i am on the right track. Is there a solution for Sublime? – atilkan Apr 04 '16 at 23:52
13

I add the same problem in Espresso and you can "fix" it in Gulp by using the debounceDelay option like this :

gulp.watch('/**/*.less', {debounceDelay: 2000}, ['less']);

That did the trick for me. But it's a pain to add it to every gulp.watch I don't know if we can put this option globaly...

Mushr00m
  • 2,258
  • 1
  • 21
  • 30
  • 1
    To me, this option waits 2 seconds, then fires the handler twice. – Yanick Rochon Jun 10 '16 at 12:42
  • @YanickRochon You would be right if he used, e.g., the `readDelay` option, but debouncing is different. The idea behind debouncing is this: when an event is fired, queue up the event handler, but wait for a certain amount of time before calling it. If the event is fired again during that timeout, discard the currently queued handler, queue up a new one, and start the timer over again. In the basic case this will repeat continually, with the handler never actually being called, until the event stops being triggered within the timeout, at which point the handler will be fired exactly once. – Ken Bellows Dec 21 '17 at 13:38
10

This worked for me

.pipe(watch('/**/*.less', { awaitWriteFinish: true }))

https://github.com/paulmillr/chokidar#api

Trisha Crowley
  • 101
  • 1
  • 2
2

You should be able to use gulp-batch to batch the changes, since it has a debounce option.

Something like this:

gulp.src(['server/**/*.js'], batch({debounce: 50}, function(events) {
    return events
        .pipe(...); // your code here
}));
OverZealous
  • 39,252
  • 15
  • 98
  • 100
  • I'm afraid that didn't help. It also seems like gulp-batch kinda solves another problem. Think maybe the solution is either within the editor, or within some of the other modules gulp are using. Like gaze or similar. – Øyvind Feb 06 '14 at 22:50
  • Just in case, did you try adjusting the debounce time? – OverZealous Feb 07 '14 at 00:10
  • I did, I also had problems actually running the stuff inside the batch. Just to clarify, I'm not using the gulp-watch plugin, just the built in watch feature. I also have the same problem using grunt-contrib-watch with Coda 2. I do think both those plugins use the same modules under the hood. Shame I have to switch editor just because of this, its a small thing, but its quite important, I dont wanna run my unit tests etc twice every run. – Øyvind Feb 07 '14 at 07:39
  • This `gulp-batch` option is now called `timeout`. Use it like `batch({timeout: 500}, function(events) { ... })`. The default value of `timeout` is 100 ms, which should already be enough time for editors to finish writes-and-renames. – Rory O'Kane Feb 27 '17 at 20:20
2

Hint: the debounce parameter only works for the SAME file/event. If multiple events/files change, it won't help. Sometimes (e.g. I copied files into the a directory being served by my local server) gulp-cached might help, sometimes excluding certain files/patterns (e.g. the sourcemaps files) might help (use ! to negate the selection). e.g.

gulp.watch(['js/**/*', '!js/**/*.map'])
MattDiMu
  • 4,873
  • 1
  • 19
  • 29
1

I was seeing a similar issue, but it was caused by having the page open in multiple tabs/windows.

LukeAskew
  • 93
  • 2
  • 7
1

One year later ...

Using

  • nodejs 0.10.25
  • gulp 3.8.10

Gaz debounceDelay option did not change anything for me, neither did I understand how to use gulp-batch callback argument :/ ...

To avoid consecutives task calls after several files have been changed, I used the oldschool setTimeout function:

// ...
var
SRC = ['js/*.js', '!js/*.min.js'], DEST = 'js',
processJs = function(){
    util.log('Processing: ['+ SRC.join(' | ') +']');
    var stream = gulp.src(SRC)
        .pipe(uglify())
        .pipe(concat('scripts.min.js'))
        .pipe(gulp.dest(DEST));
    return stream;
};


gulp.task('default', function(){
    var delay = 2000, timer,
        watcher = gulp.watch(
            SRC,
            // Callback triggered whatever the event type is (added / changed / deleted)
            function(event) { // .path | .type
                // If the timer was assigned a timeout id few time ago..
                // Prevent last postpone task to be run
                if(timer){
                    clearTimeout(timer);
                }
                // Postpone the task
                timer = setTimeout(
                    function(){processJs();timer=0;}
                    ,delay
                );
            }
        );
    util.log("/!\\ Watching job "+ Array(25).join('~'));
});
Stphane
  • 3,368
  • 5
  • 32
  • 47
  • 2
    sidenote: gulp-batch is an undocumented pieceofcrap ;) quite same with gulp-watch itself, but with some experiments I got it running – Arek Bal May 05 '15 at 00:23
1

Had the same issue and it turns out, that gulp-sourcemaps causes it (see -> Using source maps causes task to run twice)

Get a solution with gulp-notify and the attribute onLast:

.pipe(notify({message: 'YOUR MESSAGE', onLast: true}));
Alex
  • 927
  • 1
  • 12
  • 18
0

Seems like the answer to this question is a feature of the editor that was used, Coda 2. Based on some of the comments here and testing with multiple editors, it seems like Coda 2 saves a temporary file or similar and that causes the gulp watch function to be run twice.

I have not found a viable solution to this when using Coda 2, ended up with switching to Sublime Text 3.

Øyvind
  • 839
  • 2
  • 15
  • 22
  • 1
    Sublime have the same issue. – atilkan Apr 05 '16 at 00:47
  • Been some time since I had this problem, my problem was gone when switching to Sublime. Maybe they changed something in an update now, if you are seeing this problem? I am using Sublime Text 3 build 3103 and not seeing any issues. – Øyvind Apr 06 '16 at 06:26
  • I found my problem caused by Dropbox. It triggers the update of files. – atilkan Apr 07 '16 at 22:56
0

Aside from editor specific solutions, I wrote a little gulp plugin that solves the problem. You can use gulp-debounce like so:

npm install --save-dev gulp-debounce

var debounce = require('gulp-debounce'),
    watch    = require('gulp-watch'),
    through  = require('through2');

gulp.watch('server/**/*.js')
.pipe(debounce({ wait: 1000 }))
.pipe(through.obj(function(vinyl) {
  console.log("this won't fire multiple times in 1000ms", vinyl.path);
});
mnzaki
  • 3
  • 1
0

I write here my experience, maybe helps someone. This cost me 1 week to detect. I made every possible debug. Removed files, reinstalled clean node packages. Removed Sublime plugins. Reported as a bug to Sublime github and plugins github pages.

My solution Close dropbox if open. I use Dropbox for my project and work there. When Dropbox is open, tasks run twice because Dropbox detects file change and does something. Specially Typescript files, i don't know why.

atilkan
  • 4,527
  • 1
  • 30
  • 35
0

I tried debounce and awaitWriteFinish. It didn't work. This did:

const gulp = require("gulp");
const exec = require('child_process').exec;
let run = false;

gulp.task("watch", () => {
  console.log("watching ..");
  gulp.watch("**/*.js", ["browserify"]);
});

gulp.task("browserify", () => {
  if (run) { return; }
  run = true;
  console.log("calling browserify");
  exec("browserify app.js -o bundle.js");
  setTimeout(() => {
    run = false;
  }, 1000);
});
Peheje
  • 12,542
  • 1
  • 21
  • 30