coool
coool

Reputation: 8297

How can Gulp be restarted upon each Gulpfile change?

I am developing a Gulpfile. Can it be made to restart as soon as it changes? I am developing it in CoffeeScript. Can Gulp watch Gulpfile.coffee in order to restart when changes are saved?

Upvotes: 67

Views: 22979

Answers (15)

whojr
whojr

Reputation: 21

Thank you to Matt for inspiring this slight modification of your autoReLoad task.

This modification addresses the issue with terminating the old process and starting a new one. The solution is how spawn is called in the original task. The change was to add the options detached: true which detaches the new child process from it's parent. As well as calling unref() on the spawned process so it doesn't wait for it's parent to terminate.

My Environment

  • Windows 10
  • npm: 11.0.0
  • node: v23.6.0
  • gulp CLI: 3.0.0
  • local: 5.0.0

Sample gulpfile.js

I created a sample gulpfile.js to show the autoReLoad function in action

const gulp = require("gulp");
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers");
const argv = yargs(hideBin(process.argv)).argv;
const htmlmin = require("gulp-htmlmin");
const replace = require("gulp-replace");
const { spawn } = require("node:child_process");
const log = require("fancy-log");

let LOCDEV = "./";
let DEST = "build/";

const buildConfig = {
  html: {
    sourcePaths: ["./*.html"],
    exportPath: `${DEST}/`,
    watchPath: "./*.html",
  },
  toolOptions: {
    // https://github.com/kangax/html-minifier
    htmlOptions: {
      collapseWhitespace: true,
      removeComments: true,
    },
  },
};

function buildHTML() {
  return gulp
    .src(buildConfig.html.sourcePaths, { allowEmpty: true })
    .pipe(replace("%HOME_PATH%", LOCDEV))
    .pipe(htmlmin(buildConfig.toolOptions.htmlOptions))
    .pipe(gulp.dest(buildConfig.html.exportPath));
}

function autoReLoad() {
  var pid;
  var currentProcess = process;

  // Spawn the child and detach it from the parent
  pid = spawn("gulp", argv._, {
    stdio: "ignore",
    shell: true,
    detached: true,
  }).unref();

  // Kill the parent
  currentProcess.kill(currentProcess.pid, "SIGTERM");
}

let buildSys = gulp.series(buildHTML);

exports.build = buildSys;

exports.watch = function () {
  gulp.watch(buildConfig.html.sourcePaths, buildHTML);
  gulp.watch("./gulpfile.js", autoReLoad);
};

The way it works

When you make a change to the gulpfile.js it triggers the watch function to run autoReload. On entering it saves the current process identifier and spawns a new gulp. The new gulp is launched in a new window and the old process is killed with a SIGTERM.

There's a few things to note

  1. I installed gulp globally as well as locally. This allows me to just call "gulp" and let windows figure out how to access it.
  2. The option shell: true must be thrown since the invocation ("gulp") resolves to shell script and due to security you can't run a script without it.
  3. The option detached: true separates the child process from it's parent and the .unref() says don't wait for the parent to exit. The effects:
  • a. The new process doesn't wait for the parent and immediately opens a new window.
  • b. The new window is spawned regardless of running in vscode or not (annoying).
  • c. The parent window - the gulp process is killed (stopped) but the window does not go away.
  • d. The new window that is spawned with the new gulp inherits the IO of the parent and runs as it is the parent process until a change is made. Then the spawned window is terminated and a new window takes it place. The results is a SINGLE process running gulp.
  • e. The new process (gulp window) can't be dismissed with control^C. Instead you have to dismiss the window and that terminates the process. To dismiss the window you have to click the X in the upper right hand corner of the window itself (annoying).
  • f. To solve this you need to add signal handling possibly (AbortSignal) so there's room for improvemnt :-).

Basic Run

npm run watch

> watch
> set NODE_ENV=development && gulp watch

[13:54:41] Using gulpfile C:\webdev\foo\gulpfile.js
[13:54:41] Starting 'watch'...

[Using another terminal]
tasklist | findstr /I /C:node
node.exe                      7908 Console                    1     43,108 K
node.exe                     12896 Console                    1     54,348 K

[Makes change to html]
[13:54:41] Using gulpfile C:\webdev\foo\gulpfile.js
[13:54:41] Starting 'watch'...
[13:56:10] Starting 'buildHTML'...
[13:56:10] Finished 'buildHTML' after 71 ms

[makes change to gulpfile.js]
Old process terminates

[new process terminal]
[13:56:58] Using gulpfile C:\webdev\foo\gulpfile.js
[13:56:58] Starting 'watch'...

[Using another terminal]
tasklist | findstr /I /C:node
node.exe                     28092 Console                    1     53,908 K

Only one process running. The original terminal is still running but the gulp process has been successfully terminated.

Pros It listens for changes to the gulpfile.js. Regardless of how many times you make a changes to the gulpfile ONLY ONE PROCESS runs. The old process is successfully terminated.

Cons It spawns (on windows) a new (annoying) terminal running gulp. If the original process was running in VSCode then the new window will be independent of VSCode. The original VSCode terminal will continue to run but it will no longer contain the process.

I hope this helps in your development efforts with gulp.

Upvotes: 0

vIGGS
vIGGS

Reputation: 71

I spent a whole day trying to make this work on Windows / Gulp 4.0.2, and I (finally) made it...

I used some solutions from people on this page and from one other page. It's all there in the comments...

Any change in any function inside "allTasks" will take effect on gulpfile.js (or other watched files) save...

There are some useless comments and console.logs left, feel free to remove them... ;)

const { gulp, watch, src, dest, series, parallel } = require("gulp");
const spawn = require('child_process').spawn;

// This function contains all that is necessary: start server, watch files...
const allTasks = function (callback) {
   console.log('==========');
   console.log('========== STARTING THE GULP DEFAULT TASK...');
   console.log('========== USE CTRL+C TO STOP THE TASK');
   console.log('==========');

   startServer();
   // other functions (watchers) here

   // *** Thanks to Sebazzz ***
   // Stop all on gulpfile.js change
   watch('gulpfile.js', function (callback) {
      callback(); // avoid "task didn't complete" error
      process.exit();
   });

   callback(); // avoid "task didn't complete" error
}


// Restart allTasks
// ********************************************
// CALL GULPDEFAULT WITH THE GULP DEFAULT TASK:
// export.default = gulpDefault
// ********************************************
const gulpDefault = function (callback) {
   let p = null;

   watch('gulpfile.js', spawnChildren);

   // *** Thanks to Sphinxxx: ***
   // New behavior in gulp v4: The watcher function (spawnChildren()) is passed a callback argument
   // which must be called after spawnChildren() is done, or else the auto-reload task
   // never goes back to watching for further changes (i.e.the reload only works once).
   spawnChildren(callback);

   function spawnChildren(callback) {

      /*
      // This didn't do anything for me, with or without the delay,
      // so I left it there, but commented it out, together with the console.logs...

      // kill previous spawned process
      if (p) {
         // You might want to trigger a build as well
         //args.unshift('build');

         setTimeout(function () {
            console.log('========== p.pid before kill: ' + p.pid); // a random number
            console.log('========== p before kill: ' + p); // [object Object]
            p.kill();
            console.log('========== p.pid after kill: ' + p.pid); // the same random number
            console.log('========== p after kill: ' + p); // still [object Object]
         }, 1000);
      }
      */

      // `spawn` a child `gulp` process linked to the parent `stdio`
      // ['watch'] is the task that calls the main function (allTasks):
      // exports.watch = allTasks;
      p = spawn('gulp', ['watch'], { stdio: 'inherit', shell: true });
      // *** Thanks to people from: ***
      // https://stackoverflow.com/questions/27688804/how-do-i-debug-error-spawn-enoent-on-node-js
      // Prevent Error: spawn ENOENT
      // by passing "shell: true" to the spawn options

      callback(); // callback called - thanks to Sphinxxx
   }
}

exports.default = gulpDefault;
exports.watch = allTasks;

Upvotes: 0

Peter Kottas
Peter Kottas

Reputation: 943

I've been dealing with the same problem and the solution in my case was actually very simple. Two things.

  1. npm install nodemon -g (or locally if you prefer)
  2. run with cmd or create a script in packages like this:

    "dev": "nodemon --watch gulpfile.js --exec gulp"
    
  3. The just type npm run dev

--watch specifies the file to keep an eye on. --exec says execute next in line and gulp is your default task. Just pass in argument if you want non default task.

Hope it helps.

EDIT : Making it fancy ;) Now while the first part should achieve what you were after, in my setup I've needed to add a bit more to make it really user friend. What I wanted was

  1. First open the page.
  2. Look for changes in gulpfile.js and restart gulp if there are any
  3. Gulp it up so keep an eye on files, rebuild and hot reload

If you only do what I've said in the first part, it will open the page every time. To fix it, create a gulp task that will open the page. Like this :

gulp.task('open', function(){
return gulp
.src(config.buildDest + '/index.html')
.pipe(plugins.open({
    uri: config.url
}));

Then in my main tasks I have :

gulp.task('default', ['dev-open']);
gulp.task('dev-open', function(done){
    plugins.sequence('build', 'connect', 'open', 'watch', done);
});
gulp.task('dev', function(done){
    plugins.sequence('build', 'connect', 'watch', done);
});

Then modifying your npm scripts to

"dev": "gulp open & nodemon --watch gulpfile.js --watch webpack.config.js --exec gulp dev"

Will give you exactly what you want. First open the page and then just keep live reloading. Btw for livereload I use the one that comes with connect which always uses the same port. Hope it works for you, enjoy!

Upvotes: 4

Davit Yavryan
Davit Yavryan

Reputation: 298

Install nodemon globally: npm i -g nodemon

And add in your .bashrc (or .bash_profile or .profile) an alias:

alias gulp='nodemon --watch gulpfile.js --watch gulpfile.babel.js --quiet --exitcrash --exec gulp'

This will watch for file gulpfile.js and gulpfile.babel.js changes. (see Google)

P.S. This can be helpful for endless tasks (like watch) but not for single run tasks. I mean it uses watch so it will continue process even after gulp task is done. ;)

Upvotes: 1

Install gulp-restart

npm install gulp-restart

This code will work for you.

var gulp = require('gulp'); var restart = require('gulp-restart');

gulp.task('watch', function() { gulp.watch(['gulpfile.js'], restart); })

it will restart gulp where you do changes on the gulpfile.js

Upvotes: 0

fstr
fstr

Reputation: 940

Here's a short version that's easy to understand that you can set as a default task so you just need to type "gulp":

gulp.task('watch', function() {
  const restartingGulpProcessCmd = 'while true; do gulp watch2 --colors; done;';
  const restartingGulpProcess = require('child_process').exec(restartingGulpProcessCmd);
  restartingGulpProcess.stdout.pipe(process.stdout);
  restartingGulpProcess.stderr.pipe(process.stderr);
});

gulp.task('watch2', function() {
  gulp.watch(['config/**.js', 'webpack.config.js', './gulpfile.js'],
    () => {
      console.log('Config file changed. Quitting so gulp can be restarted.');
      process.exit();
    });

  // Add your other watch and build commands here
}

gulp.task('default', ['watch']);

Upvotes: 0

Sebazzz
Sebazzz

Reputation: 1373

A good solution for Windows, which also works well with Visual Studio task runner.

/// <binding ProjectOpened='auto-watchdog' />
const spawn = require('child-proc').spawn,
      configPaths = ['Gulpconfig.js', 'bundleconfig.js'];

gulp.task('watchdog', function () {
    // TODO: add other watches here

    gulp.watch(configPaths, function () {
        process.exit(0);
    });
});

gulp.task('auto-watchdog', function () {
    let p = null;

    gulp.watch(configPaths, spawnChildren);
    spawnChildren();

    function spawnChildren() {
        const args = ['watchdog', '--color'];

        // kill previous spawned process
        if (p) {
            // You might want to trigger a build as well
            args.unshift('build');

            setTimeout(function () {
                p.kill();
            }, 1000);
        }

        // `spawn` a child `gulp` process linked to the parent `stdio`
        p = spawn('gulp', args, { stdio: 'inherit' });
    }
});

Main changes compared to other answers:

  • Uses child-proc because child_process fails on Windows.
  • The watchdog exits itself on changes of files because in Windows the gulp call is wrapped in a batch script. Killing the batch script wouldn't kill gulp itself causing multiple watches to be spawned over time.
  • Build on change: Usually a gulpfile change also warrants rebuilding the project.

Upvotes: 1

bid
bid

Reputation: 86

I know this is a very old question, but it's a top comment on Google, so still very relevant.

Here is an easier way, if your source gulpfile.js is in a different directory than the one in use. (That's important!) It uses the gulp modules gulp-newer and gulp-data.

var gulp          = require('gulp'         )
  , data          = require('gulp-data'    )
  , newer         = require('gulp-newer'   )
  , child_process = require('child_process')
;

gulp.task( 'gulpfile.js' , function() {
    return gulp.src( 'sources/gulpfile.js' ) // source
        .pipe( newer(     '.'            ) ) // check
        .pipe( gulp.dest( '.'            ) ) // write
        .pipe( data( function(file)        { // reboot
            console.log('gulpfile.js changed! Restarting gulp...') ;
            var t , args = process.argv ;
            while ( args.shift().substr(-4) !== 'gulp' ) { t=args; }
            child_process.spawn( 'gulp' , args , { stdio: 'inherit' } ) ;
            return process.exit() ;
        } ) )
    ;
} ) ;

It works like this:

  • Trick 1: gulp-newer only executes the following pipes, if the source file is newer than the current one. This way we make sure, there's no reboot-loop.
  • The while loop removes everything before and including the gulp command from the command string, so we can pass through any arguments.
  • child_process.spawn spawns a new gulp process, piping input output and error to the parent.
  • Trick 2: process.exit kills the current process. However, the process will wait to die until the child process is finished.

There are many other ways of inserting the restart function into the pipes. I just happen to use gulp-data in every of my gulpfiles anyway. Feel free to comment your own solution. :)

Upvotes: 3

Haydar C.
Haydar C.

Reputation: 805

try this code (only win32 platform)

gulp.task('default', ['less', 'scripts', 'watch'], function(){
    gulp.watch('./gulpfile.js').once('change' ,function(){
        var p;
        var childProcess = require('child_process');
        if(process.platform === 'win32'){
            if(p){
                childProcess.exec('taskkill /PID' + p.id + ' /T /F', function(){});
                p.kill();
            }else{
                p = childProcess.spawn(process.argv[0],[process.argv[1]],{stdio: 'inherit'});
            }
        }
    });
});

Upvotes: 1

stringparser
stringparser

Reputation: 745

Another solution for this is to refresh the require.cache.

var gulp = require('gulp');

var __filenameTasks = ['lint', 'css', 'jade'];
var watcher = gulp.watch(__filename).once('change', function(){
  watcher.end(); // we haven't re-required the file yet
                 // so is the old watcher
  delete require.cache[__filename];
  require(__filename);
  process.nextTick(function(){
    gulp.start(__filenameTasks);
  });
});

Upvotes: 3

joemaller
joemaller

Reputation: 20576

I was getting a bunch of EADDRINUSE errors with the solution in Caio Cunha's answer. My gulpfile opens a local webserver with connect and LiveReload. It appears the new gulp process briefly coexists with the old one before the older process is killed, so the ports are still in use by the soon-to-die process.

Here's a similar solution which gets around the coexistence problem, (based largely on this):

var gulp = require('gulp');
var spawn = require('child_process').spawn;

gulp.task('gulp-reload', function() {
  spawn('gulp', ['watch'], {stdio: 'inherit'});
  process.exit();
});

gulp.task('watch', function() {
  gulp.watch('gulpfile.js', ['gulp-reload']);
});

That works fairly well, but has one rather serious side-effect: The last gulp process is disconnected from the terminal. So when gulp watch exits, an orphaned gulp process is still running. I haven't been able to work around that problem, the extra gulp process can be killed manually, or just save a syntax error to gulpfile.js.

Upvotes: 9

Simon Epskamp
Simon Epskamp

Reputation: 9976

I use a small shell script for this purpose. This works on Windows as well.

Press Ctrl+C to stop the script.

// gulpfile.js
gulp.task('watch', function() {
    gulp.watch('gulpfile.js', process.exit);
});

Bash shell script:

# watch.sh
while true; do
    gulp watch;
done;

Windows version: watch.bat

@echo off
:label
cmd /c gulp watch
goto label

Upvotes: 13

anatoo
anatoo

Reputation: 530

I created gulper that is gulp.js cli wrapper to restart gulp on gulpfile change.

You can simply replace gulp with gulper.

$ gulper <task-name>

Upvotes: 43

ddunderfelt
ddunderfelt

Reputation: 47

Here's another version of @CaioToOn's reload code that is more in line with normal Gulp task procedure. It also does not depend on yargs.

Require spawn and initilaize the process variable (yargs is not needed):

var spawn = require('child_process').spawn;
var p;

The default gulp task will be the spawner:

gulp.task('default', function() {
  if(p) { p.kill(); }
  // Note: The 'watch' is the new name of your normally-default gulp task. Substitute if needed.
  p = spawn('gulp', ['watch'], {stdio: 'inherit'});
});

Your watch task was probably your default gulp task. Rename it to watch and add a gulp.watch()for watching your gulpfile and run the default task on changes:

gulp.task('watch', ['sass'], function () {
  gulp.watch("scss/*.scss", ['sass']);
  gulp.watch('gulpfile.js', ['default']);
});

Now, just run gulp and it will automatically reload if you change your gulpfile!

Upvotes: 1

Caio Cunha
Caio Cunha

Reputation: 23394

You can create a task that will gulp.watch for gulpfile.js and simply spawn another gulp child_process.

var gulp = require('gulp'),
    argv = require('yargs').argv, // for args parsing
    spawn = require('child_process').spawn;

gulp.task('log', function() {
  console.log('CSSs has been changed');
});

gulp.task('watching-task', function() {
  gulp.watch('*.css', ['log']);
});

gulp.task('auto-reload', function() {
  var p;

  gulp.watch('gulpfile.js', spawnChildren);
  spawnChildren();

  function spawnChildren(e) {
    // kill previous spawned process
    if(p) { p.kill(); }

    // `spawn` a child `gulp` process linked to the parent `stdio`
    p = spawn('gulp', [argv.task], {stdio: 'inherit'});
  }
});

I used yargs in order to accept the 'main task' to run once we need to restart. So in order to run this, you would call:

gulp auto-reload --task watching-task

And to test, call either touch gulpfile.js or touch a.css to see the logs.

Upvotes: 61

Related Questions