11

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!

jocull
  • 20,008
  • 22
  • 105
  • 149
  • look at that could be a good solution for expectations but without grunt... https://www.npmjs.com/package/gulp-connect – Synoon Nov 04 '15 at 07:20
  • Connect is not what I need, unfortunately. I don't need a web server as ours is a custom server process. – jocull Nov 04 '15 at 14:20

4 Answers4

10

My simple implement via config of package.json using nodemon to watch src for ts file changes and compiling typescript/ts files with tsc...

"scripts" section from package.json:

  "scripts": {
     "debug": "nodemon -e js,ts --watch src --exec \"yarn run build:ts && yarn run start:app\"",
     "build:ts": "node_modules/.bin/tsc",
     "start:app": "node dist/app"
  },

tsconfig.json

{
   "compilerOptions": {
     "target": "es6",
     "module": "commonjs",
     "outDir": "dist"
   },
   "include": [
     "src/**/*.ts"
   ],
   "exclude": [
     "node_modules"
   ]
}
Adam Cox
  • 3,341
  • 1
  • 36
  • 46
5

Here's my solution. I have a nodemon which watches the src folder and triggers a build cycle + node call whenever it sees changes. It is called using npm run dev:src. It is a pretty simple solution:

package.json

"scripts": {
  ...
  "build:dev": "npm run clean && npm run compile:dev",
  "clean": "rimraf dist",
  "compile": "babel src -d dist && npm run copy:static",
  "compile:dev": "babel src -d dist -s && npm run copy:static",
  "copy:static": "cp -R src/static dist/static",
  "dev:dist": "npm run build:dev && node --inspect dist/server.js",
  "dev:src": "npm run build:dev && nodemon --watch src --exec npm run dev:dist",
}

EDIT:

This is an archaic choice of technology. I know this may be out of scope, but I'd suggest to either for a webpack/rollup approach like this:

"scripts": {
    "start": "node build/index.js",
    "build": "webpack",
    "dev": "cross-env NODE_ENV=development nodemon --watch src --watch package.* --watch .env --watch .env.development --watch .env.development.local --watch webpack.config.js --exec \"npm run build && node build/index.js\"",
    "lint": "eslint ./src --ext .js && prettier --check ./src/**/*{.js,.mjs}",
    "lint:fix": "eslint ./src --ext .js --fix && prettier --write ./src/**/*{.js,.mjs}",
  },

or consider strongly Kent C. Dodds' approach:

"scripts": {
    "start": "node .",
    "build": "babel --delete-dir-on-start --out-dir dist --copy-files --ignore \"**/__tests__/**,**/__mocks__/**\" --no-copy-ignored src"
  }

https://kentcdodds.com/blog/how-i-structure-express-apps

Philippe Hebert
  • 1,616
  • 2
  • 24
  • 51
1

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)

Use something like Nodemon : https://www.npmjs.com/package/nodemon There are also grunt / gulp packages for it.

On file change, stop the other running process, recompile, and restart when finished

Nodemon will stop on changes to js and restart the app.

basarat
  • 261,912
  • 58
  • 460
  • 511
  • Grunt's nodemon package says it's unsupported so I was unsure. Can you stage it to stop, recompile, and then restart? I was having a issue where nodemon locked the folder and prevented recompile. – jocull Nov 04 '15 at 05:28
  • I am using nodemon here : https://github.com/TypeScriptBuilder/tsb granted without grunt as I use atom-typescript to do the ts -> js compile on save lazily and `tsc` to do the compile on push / pull. Nevertheless `nodemon` has never blocked a compile for me. – basarat Nov 04 '15 at 05:39
  • I don't get *port blocked* either. I kills cleanly for me .... and I use it on windows + mac, I guess *something* about your system is different `¯\_(ツ)_/¯` – basarat Nov 04 '15 at 05:40
0

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']);
};
jocull
  • 20,008
  • 22
  • 105
  • 149
  • Are you still using this, or have you found a better way? I was looking to do this too, but the lack of information out there led me to believe I was doing something wrong – Kavi Siegel Feb 18 '16 at 15:02
  • 1
    @KaviSiegel I have still been using this or some derivation of it. It's hacky, a crash in the child process will lead it to not come back to life sometimes. – jocull Feb 18 '16 at 15:04