node.jsgruntjstypescriptgrunt-contrib-watch

Watch, recompile, and restart


I have been looking all over for an example of a way to use Grunt's watch module to perform a few steps in order when a file change occurs. I haven't found a good example of any of this, so if someone could point me in the right direction that would be great.

  1. Build TypeScript project (I have this working)
  2. Watch directories for file changes (this works too)
  3. Start running the compiled JavaScript in a node process, while still watching for file changes (what's the best way to do this via Grunt? The watch module seems to kick off the recompile task OK)
  4. On file change, stop the other running process, recompile, and restart when finished. Keep watching for changes (No idea on this one - the restart is the tricky part!)

I've tried a few different ways such as starting a child process with Grunt, but I always end up with dangling processes, locked up ports, misdirected STDIO, or other issues. I'd like for the child processes to be killed if the Grunt process exits.

Is there a good way to handle this? Thanks!


Solution

  • I ended up having to roll my own with child processes. Nodemon will block watch from happen and isn't flexible enough to handle the recompile steps.

    Here's my Gruntfile, using the watch, copy, clean, and TypeScript modules.

    var loader = require('load-grunt-tasks');
    var cp = require('child_process');
    
    module.exports = function (grunt) {
      loader(grunt, {});
    
      grunt.initConfig({
        tsFiles: [
          "**/*.ts",
          "!typings/**/*.ts",
          "typings/tsd.d.ts",
          "!build/**/*.ts",
          "!bower_modules/**/*.ts",
          "!node_modules/**/*.ts",
        ],
        buildDir: 'build/',
    
        clean: {
          build: '<%= buildDir %>'
        },
    
        ts: {
          build: {
            src: [
              "**/*.ts",
              "!typings/**/*.ts",
              "typings/tsd.d.ts",
              "!build/**/*.ts",
              "!bower_modules/**/*.ts",
              "!node_modules/**/*.ts",
            ],
            outDir: '<%= buildDir %>',
            options: {
              "target": 'ES5',
              "module": 'commonjs',
              "sourceMap": true,
            }
          }
        },
    
        copy: {
          build: {
            expand: true,
            cwd: './',
            src: [
              '*.json',
              'config/**/*.json',
              'test/**/*.js'
            ],
            dest: '<%= buildDir %>/',
            filter: 'isFile'
          }
        },
    
        watch: {
          run: {
            files: [
              '**/*.ts',
              '**/*.js',
              '**/*.json',
              '!.*/**/*.*',
              '!build/**/*.*',
              '!node_modules/**/*.*',
              '!logs/**/*.*'
            ],
            tasks: ['server-stop', 'build', 'server-restart'],
            options: {
              spawn: false
            }
          }
        }
      });
    
      var child = null;
      function killChild(){
        if (child){
          child.kill();
          child.disconnect();
          child = null;
        }    
      }
    
      grunt.registerTask('server-stop', 'Stop the dev server', function(){
        killChild();
      });
    
      grunt.registerTask('server-restart', 'Stop the dev server', function(){
        killChild();
        child = cp.fork('./build/app.js');
      });
    
      grunt.registerTask('build', ['copy', 'ts']);
      grunt.registerTask('rebuild', ['clean', 'build']);
      grunt.registerTask('default', ['rebuild']);
      grunt.registerTask('run', ['default', 'server-restart', 'watch']);
    };