JavaScript - - By Craig Buckler

How to Pass Command Line Parameters to Gulp Tasks

I’m a big fan of Gulp.js and recent surveys indicate almost 44% of front-end developers are using Gulp tasks today.

Gulp’s simplicity is one of its most attractive features. You write a task function in your gulpfile.js:

gulp.task('doSomething', () => {

  // do something

});

then execute that task from the command line with gulp doSomething. Tasks can be as basic or complex as you like and include further sub-tasks.

However, it’s not possible to pass arguments on the command line which can be used by that task, e.g.

gulp doSomething --option1 "my string" --option2 123 --option3

(where option3 would equate to true)

The arguments would be passed to the Gulp application itself – not your task. Gulp knows nothing about these values so they’re not made available within gulpfile.js and cannot be examined or used within your task functions.

Do Gulp Tasks Need Arguments?

Generally not – otherwise, a facility to pass arguments to tasks would have been added many years ago! Gulp tasks are written in JavaScript so you can set value defaults within your code.

You can also analyze environment variables such as NODE_ENV. For example, you can check whether the value is set to production or similar on a live server. The setting can then be used to determine whether JavaScript source files are minified when the task runs, e.g.

// is this a development build?
const devBuild = ((process.env.NODE_ENV || 'development').trim().toLowerCase() === 'development');

// Gulp plugins
const
  stripdebug = require('gulp-strip-debug'),  // remove debugging code
  uglify = require('gulp-uglify');           // minify

// build JavaScript
gulp.task('js', () => {

  let jsbuild = gulp.src('src/js/*')
    .pipe(some-plugin1())
    .pipe(some-plugin2());

  // production server tasks
  if (!devBuild) {
    jsbuild = jsbuild
      .pipe(stripdebug())
      .pipe(uglify());
  }

  return jsbuild.pipe(gulp.dest('build/js/'));

});

You can now set export NODE_ENV=production on Linux/Mac or set NODE_ENV=production on Windows prior to running the gulp js task. It will then remove console.log and debugger statements before minifying your JavaScript files.

Finally, if you want a task to do something slightly different, you can create a new task. Tasks can be chained together to run in sequence as necessary, for example:

gulp.task('doSomething1', () => {

  return gulp.src('src/*')
    .pipe(some-plugin1())
    .pipe(gulp.dest('build/'));

});

// run doSomething1 first
gulp.task('doSomething2', [doSomething1], () => {

  // do something else
  return gulp.src('src/*')
    .pipe(some-plugin2())
    .pipe(gulp.dest('build/'));

});

Running gulp doSomething1 will execute the first task. Running gulp doSomething2 will execute both tasks in order because doSomething1 is defined as a dependency in the optional array following the task name.

Should We Ever Consider Arguments?

Arguments should be avoided when there are better alternative options. Your --option1 argument could become a valid command-line option in the next release of Gulp and have undesired consequences.

That said, there are always edge cases…

1. Passwords and security

You should normally avoid hard-coding credentials such as IDs and passwords into gulpfile.js. Consider the following task which deploys files to a server via FTP using the vinyl-ftp plug-in:

gulp.task('deploy', () => {

  let
    ftp = require('vinyl-ftp'),
    conn = ftp.create({
      host      : 'mysite.com',
      user      : 'myuserid',
      password  : 'mypassword',
      parallel  : 5
    }),
    glob = [
      'build/**/*'
    ],
    src = {
      base      : 'build/',
      buffer    : false
    },
    remotePath = '/public_html/';

  return gulp.src(glob, src)
    .pipe(conn.newerOrDifferentSize(remotePath))
    .pipe(conn.dest(remotePath));

});

(Admittedly, FTP is not a great deployment method but it’s still used by many developers and can be the only option on some hosts.)

There are several problems with this approach:

  1. The FTP host, user ID, password, and path are hard-coded into the file. That would lead to security issues if the code were stored in a public GitHub repository and could be viewed, cloned and run by anyone.
  2. Any developer can run gulp deploy at any time from any device. That’s unlikely to be desirable for larger teams who want to control when deployments occur.
  3. If the credentials change, you must manually update gulpfile.js to ensure the deployment task still works.

2. Differing source, build or task locations

Gulp can be used for something other than typical website tasks. For example, you might have general tasks to wipe folders, create databases, transfer files, etc. Hard-coding something like a database or folder name would make those tasks less useful.

3. Complex tasks

Imagine a complex task involving dozens of plug-ins. If it’s not practical to split it into multiple sub-tasks, it may become difficult to add configuration options without editing gulpfile.js directly prior to running the task.

You may be able to think of further edge cases (comments welcome!)

How to Pass Arguments to Your Gulp.js Tasks

The process.argv property in Node.js returns an array containing the the process, script and all command line arguments. For example, gulp task1 --a 123 --b "my string" --c returns the following array (values may differ depending on your OS and set-up):

[
'/usr/bin/nodejs',
'/home/user/.node_modules_global/bin/gulp',
'task1',
'--a',
'123',
'--b',
'my string',
'--c'
]

This array can be parsed in gulpfile.js. The following code creates an object named arg containing argument values:

// fetch command line arguments
const arg = (argList => {

  let arg = {}, a, opt, thisOpt, curOpt;
  for (a = 0; a < argList.length; a++) {

    thisOpt = argList[a].trim();
    opt = thisOpt.replace(/^\-+/, '');

    if (opt === thisOpt) {

      // argument value
      if (curOpt) arg[curOpt] = opt;
      curOpt = null;

    }
    else {

      // argument name
      curOpt = opt;
      arg[curOpt] = true;

    }

  }

  return arg;

})(process.argv);

The function loops through the process.argv array. When it encounters a value preceded with one or more dashes, it creates a new named value in the arg object which is set to true. When it encounters a value without dashes, it sets the previous named value (if available) to that string.

When we run gulp task1 --a 123 --b "my string" --c, the arg object is set to:

{
  "a": "123",
  "b": "my string",
  "c": true
}

We can, therefore, examine and use those values as required.

Assuming arg is set at the top of gulpfile.js, we can rewrite our FTP deployment task so we can pass:

  • the user ID as a --user or --u argument
  • the password as a --password or --p argument
gulp.task('deploy', () => {

  let
    ftp = require('vinyl-ftp'),
    conn = ftp.create({
      host      : 'mysite.com',
      user      : arg.user || arg.u,      // command line option
      password  : arg.password || arg.p,  // command line option
      parallel  : 5
    }),
    glob = [
      'build/**/*'
    ],
    src = {
      base      : 'build/',
      buffer    : false
    },
    remotePath = '/public_html/';

  return gulp.src(glob, src)
    .pipe(conn.newerOrDifferentSize(remotePath))
    .pipe(conn.dest(remotePath));

});

A deployment would only occur if we run the task with appropriate FTP credentials, e.g.

gulp deploy --u myuserid --p mypassword

Wrapping Up

As we’ve seen, with a little bit of custom code it is possible to pass parameters in to Gulp tasks. Although it’s often not necessary for your tasks to receive arguments, we saw there are certain cases where it’s useful. It’s definitely a good technique to have in your toolkit.

The argument parsing code can be used for any Node.js command line process. However, the commander module offers considerably more sophistication should you require it in non-Gulp projects.

I hope you find this useful. Of course, just because you can pass arguments to Gulp tasks, it doesn’t follow that you should! If you come up with any more good use cases for this, let me know in the comments.

This article was peer reviewed by Tim Severien. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Sponsors