59

Is it possible to run two watch tasks simultaneously?

I understand that I can have any number of tasks I want inside watch settings and just launch grunt watch and it will watch all of them, like this

...
watch: {
    A: {
        files: "js/dev/**/*.coffee",
        tasks: ["coffee", "requirejs"]
    },
    B: {
        files: "js/dev/**/*.coffee",
        tasks: ["coffee"]
    },
    C: {
        files: "js/dev/**/*.html",
        tasks: ["copy"]
    }
}
...

...but I don't need this. I just want to have different set of tasks for development and production. As you can guess, the only difference between A (production) and B (development) is minification and concatenation. I don't need to launch A and B tasks at the same time.

First I came with this idea

grunt.registerTask("prod", ["watch:A", "watch:C"]);
grunt.registerTask("dev", ["watch:B", "watch:C"]);

But this didn't work. Just first watch tasks is working (C never works). Is that possible to do what I want?

Vitalii Korsakov
  • 45,737
  • 20
  • 72
  • 90

9 Answers9

74

I've found using grunt-concurrent works:

concurrent: {
  options: {
    logConcurrentOutput: true
  },
  prod: {
    tasks: ["watch:A", "watch:C"]
  },
  dev: {
    tasks: ["watch:B", "watch:C"]
  }
}

Then:

grunt.registerTask("prod", ["concurrent:prod"]);
grunt.registerTask("dev", ["concurrent:dev"]);
Nicolas Hery
  • 1,068
  • 1
  • 10
  • 8
  • 7
    This doesn't work for me. I've got a 'Fatal error : Port 35729 is already in use by another process' when running two watch with concurrent. Any clues ? – jmcollin92 May 09 '14 at 08:09
  • 1
    The best and only working solution is there : https://npmjs.org/package/grunt-focus – jmcollin92 May 09 '14 at 08:33
  • 4
    @jmcollin92 grunt-focus works well for me, but I am not quite sure why it would be the "best and only working solution". grunt-concurrent works as well, and it can execute any task concurrently. grunt-focus is limited to watch targets. – hashchange Jul 18 '14 at 10:08
  • 2
    @hashchange I agree with you. grunt-focus is only valuable for watch targets. grunt-concurrent works well for all aother purposes. – jmcollin92 Sep 18 '14 at 08:22
  • 2
    @jmcollin92 I got the same 'Fatal error : Port 35729 is already in use by another process' when I first ran this. I had multiple configurations for my watch config. I just had to set a different livereload port for each one. [Here's a fiddlr](http://jsfiddle.net/dk44x00t/) – Tony Zampogna Dec 18 '14 at 15:31
  • 1
    I think that grunt concurrent won't work for you if you are using watch liveReload feature as than every watch task is running in a separate watch process. While focus does work. – Alex Shnayder Dec 30 '14 at 06:29
  • Thank you! You made my day. – Trey Piepmeier Aug 10 '16 at 21:39
  • You need to change the port number on livereload config per watch service – marksyzm Feb 15 '17 at 16:35
  • Huge THANKS for this suggestion! – Nikolay Tsenkov Jul 01 '18 at 06:21
18

EDIT: concurrent now has a logConcurrentOutput option! More info here: https://github.com/sindresorhus/grunt-concurrent#logconcurrentoutput.

Watch is a weirdly concurrent but blocking task, so you have to be creative to get multitask-like functionality working.

Concurrent loses all output from the watch tasks, which isn't ideal.

Try dynamically writing the config object in a custom task:

grunt.registerTask('watch:test', function() {
  // Configuration for watch:test tasks.
  var config = {
    options: {
      interrupt: true
    },
    unit: {
      files: [
        'test/unit/**/*.spec.coffee'
      ],
      tasks: ['karma:unit']
    },
    integration: {
      files: [
        'test/integration/**/*.rb',
        '.tmp/scripts/**/*.js'
      ],
      tasks: ['exec:rspec']
    }
  };

  grunt.config('watch', config);
  grunt.task.run('watch');
});
RobW
  • 10,184
  • 4
  • 41
  • 40
  • "Concurrent loses all output from the watch tasks": There is a `logConcurrentOutput` option for Concurrent, which prevents just that. – hashchange Jul 18 '14 at 10:00
12

The best and only working solution is there : https://npmjs.org/package/grunt-focus Add this plugin and then :

focus: {
            sources: {
                include: ['js', 'html', 'css', 'grunt']
            },
            testu: {
                include: ['js', 'html', 'css', 'testu', 'grunt']
            },
            testi: {
                include: ['js', 'html', 'css', 'testu', 'testi', 'grunt']
            }
        },
        watch: {
            js: {
                files: paths.js,
                tasks: ['jshint'],
                options: {
                    livereload: true
                }
            },
            html: {
                files: paths.html,
                options: {
                    livereload: true
                }
            },
            css: {
                files: paths.css,
                tasks: ['csslint'],
                options: {
                    livereload: true
                }
            },
            testu: {
                files: ['test/**/*.js', 'test/**/*.css'],
                tasks: ['mochaTest'],
                options: {}
            },
            testi: {
                files: ['test/**/*.js', 'test/**/*.css'],
                tasks: ['exec:cleanTestDB', 'protractor_webdriver', 'protractor'],
                options: {}
            },
            grunt: {
                files: ['Gruntfile.js', 'server/config/env/*.js'],
                options: {
                    reload: true
                }
            }
        }

Then you use focus:sources or focus:testu as your convenience.

JM.

jmcollin92
  • 2,896
  • 6
  • 27
  • 49
7

grunt-concurrent or grunt-focus are both good solutions, but both of them break livereload functionality.

My solution to this is to compose the watch configuration dynamically, with the assumption that you won't be running both configuration at the same time.

You can do something like this

grunt.config.merge({
  watch: {
    options: {
      livereload: true
    },
    C: {
      files: "js/dev/**/*.html",
      tasks: ["copy"]
    }
  }
});

grunt.registerTask('watch-forProd', function () {
  grunt.config.merge({
    watch: {
      A: {
        files: "js/dev/**/*.coffee",
        tasks: ["coffee", "requirejs"]
      }
    }
  });

  grunt.task.run('watch');
});

grunt.registerTask('watch-forDev', function () {
  grunt.config.merge({
    watch: {
      B: {
        files: "js/dev/**/*.coffee",
        tasks: ["coffee"]
      }
    }
  });

  grunt.task.run('watch');
});

grunt.registerTask("prod", ["watch-forProd"]);
grunt.registerTask("dev", ["watch-forDev"]);
Alex Shnayder
  • 1,362
  • 1
  • 13
  • 24
4

SEPTEMBER 2018

You don't need to use grunt-concurrent anymore grunt now has this built in, here is a sample from one of my current projects...

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        sass: {
            theme: {
                files: {
                    '../../web/sites/all/themes/ifafri/css/generated/theme_ifafri_master.css' : '../../web/sites/all/themes/ifafri/css/master.scss'
                }
            },
            bootstrap: {
                files: {
                    '../../web/sites/all/themes/ifafri/css/generated/theme_ifafri_bootstrap.css' : '../../web/sites/all/themes/ifafri/css/bootstrap/master.scss' 
                }
            }
        },
        watch: {
            theme: {
                files: '../../web/sites/all/themes/ifafri/css/*.scss',
                tasks: ['sass:theme'],
                options: {
                    spawn: false,
                    livereload: true,
                    nospawn: false
                }
            },
            bootstrap: {
                files: '../../web/sites/all/themes/ifafri/css/bootstrap/*.scss',
                tasks: ['sass:bootstrap'],
                options: {
                    spawn: false,
                    livereload: true,
                    nospawn: false
                }
            }
    }
    });
    grunt.loadNpmTasks('grunt-contrib-sass');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-livereload');
    grunt.registerTask('default',['watch']);
}
Simon
  • 723
  • 8
  • 14
  • 3
    **SEPTEMBER 2018** I don't need grunt anymore – Vitalii Korsakov Sep 17 '18 at 08:49
  • Spawn and nospawn are the same option (nospawn was kept for backwards compatibility). Using nospawn: false at the end will result in spawning processes, which is the default behavior, therefore both ooptions are redundant in this example. – zamber May 02 '19 at 10:34
1

Without worrying grunt.registerTask() in Gruntfile.js, I sometimes run grunt as background processes by typing the following in the command line:

$ grunt watch:A &
$ grunt watch:C &

You can make the commands as a batch script for more convenience. Hopefully it helps.

0

I know this not answer directly to the question, but my solution is now to use Gulp instead of Grunt. With Gulp you code and not only configure. So you are more free to do what you want.

JM.

jmcollin92
  • 2,896
  • 6
  • 27
  • 49
  • If you like writing all this "Server" utility stuff then gulp is OK. We did an experiment, 1 week to convert our Grunt functionality with Gulp, and we couldn't accomplish the task. I am still on the Grunt side of the fence. – augurone Mar 17 '16 at 19:02
  • 2
    It tooks me less than one day to do the gulp migration. Very easy to understand, fully featured with tons of plugins and I don't to go back. – jmcollin92 Mar 27 '16 at 14:20
  • Stoked your experiment went better than ours. I did not personally attempt to do it, maybe it would go easier for me. – augurone Mar 29 '16 at 18:58
0

Just change the port address and the livereload port. For eg. if the port is 9000 change it to 8000 and live reload from 35729 to 36729

Yash
  • 65
  • 6
-1

Concurrent works fine for me

concurrent: {
            options: {
                logConcurrentOutput: true
            },
            set1: ['watch:html', 'watch:styles'],
        },

grunt.registerTask('default', 'watch');
grunt.registerTask('htmlcss', ['concurrent:set1']);