Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gulp: passing parameters to task from watch declaration

Tags:

gulp

The problem: I want to maintain 'collections' of files. This will help with build times, and flexibility. for example, everytime i edit my app.js file, I don't want to re-compile all my twitter bootstrap files.

I can certainly achieve this with 2 tasks and 2 watch declarations - the problem is that the tasks are identical save for the files array. Ideally I would like to pass through these as parameters in the watch declaration Is there a way to do something like the following psuedo-code?:

var files = {
    scripts: [
        'www/assets/scripts/plugins/**/*.js', 
        'www/assets/scripts/main.js', 
    ],
    vendor: [
        'vendor/jquery/dist/jquery.js',
        'vendor/jqueryui/ui/jquery.ui.widget.js',                       
        'vendor/holderjs/holder.js'
    ],              
};
...


gulp.task('js', ['lint'], function (files, output) {
    return gulp.src(files)
        .pipe(debug())
        .pipe(concat(output))
        .pipe(uglify({outSourceMap: true}))
        .pipe(gulp.dest(targetJSDir))       
        .pipe(notify('JS minified'))
        .on('error', gutil.log) 
});

...

gulp.watch('scripts/**/*.js', ['lint', 'js'], files.scripts, 'app.min.js');
gulp.watch('vendor/**/*.js', ['lint', 'js'], files.vendor, 'vendor.min.js');

Flipping round another way: is to namespace the watch declaration that called the task? That way I could check which watch triggered the task, and conditional those things within the task itself.

like image 602
Beans Avatar asked Mar 21 '14 15:03

Beans


People also ask

What is gulp watch command?

The watch() API connects globs to tasks using a file system watcher. It watches for changes to files that match the globs and executes the task when a change occurs. If the task doesn't signal Async Completion, it will never be run a second time.

How do you define a task in gulp?

A private task looks and acts like any other task, but an end-user can't ever execute it independently. To register a task publicly, export it from your gulpfile. const { series } = require('gulp'); // The `clean` function is not exported so it can be considered a private task.

What is gulpfile js?

A gulpfile is a file in your project directory titled gulpfile. js (or capitalized as Gulpfile. js , like Makefile), that automatically loads when you run the gulp command.


2 Answers

the problem is that the tasks are identical save for the files array.

I believe lazypipe (see its gh page) is well suited to your wants. This was an interesting problem. I'm going to try to answer both what I think you're asking about (which is satisfied by lazypipe) as well as what I think you're probably thinking about or would end up thinking about if you got past the parameterization of pipes issue.

One aspect of what we want is that we don't want to rerun jshint on files that haven't changed. Additionally, we want to keep it DRY, and we want to pick up new files in addition to changed ones.

This is tested and works for me:

var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var es = require('event-stream');
var lazypipe = require('lazypipe');
var gutil = require('gulp-util');
var path = require('path');

var files = {
  scripts: ['src/**/*.js'],
  vendor: ['vendor/**/*.js']
};

// sets up a lazy pipe that does jshint related stuff
function getJsMultiPipe(name) {
  return lazypipe()
    .pipe($.jshint)
    .pipe($.jshint.reporter, 'jshint-stylish')

    // if you don't want to fail on style errors remove/comment this out:
    .pipe($.jshint.reporter, 'fail');
}

// sets up a lazy pipe that does concat and post-concat stuff
function getJsCombinedPipe(groupName, outfile) {
  return lazypipe()
      .pipe($.concat, outfile)
      .pipe($.uglify, {outSourceMap: true})
      .pipe(gulp.dest, 'build')
      .pipe($.notify, {message: groupName + ' JS minified', onLast: true});
}

// sets up a pipe for the initial build task, combining the above two pipes
function getBuildPipe(groupName, outfile) {
  return gulp.src(files[groupName])
        .pipe(getJsMultiPipe(groupName)())
        .pipe(getJsCombinedPipe(groupName, outfile)());
}

// sets up a watch pipe, such that only the changed file is jshinted,
// but all files are included in the concat steps
function setWatchPipe(groupName, outfile) {
  return $.watch({
    glob: files[groupName],
    name: groupName,
    emitOnGlob: false,
    emit: 'one'
  }, function(file, done) {
    return file
        .pipe($.debug({title: 'watch -- changed file'}))
        .pipe(getJsMultiPipe(groupName)())
        // switch context
        .pipe(gulp.src(files[groupName]))
        .pipe($.debug({title: 'watch -- entire group'}))
        .pipe(getJsCombinedPipe(groupName, outfile)())
        .pipe($.debug({title: 'watch -- concatted/source-mapped'}))
        .pipe($.notify({message: 'JS minified', onLast: true}));
  });
}

// task to do an initial full build
gulp.task('build', function() {
  return es.merge(
      getBuildPipe('scripts', 'app.min.js'),
      getBuildPipe('vendor', 'vendor.min.js')
  )
  .pipe($.notify({message: 'JS minified', onLast: true}));
});


// task to do an initial full build and then set up watches for
// incremental change
gulp.task('watch', ['build'], function(done) {
  setWatchPipe('scripts', 'app.min.js');
  setWatchPipe('vendor', 'vendor.min.js');
  done();
});

My dependencies look like:

  "devDependencies": {
    "jshint-stylish": "^0.1.5",
    "gulp-concat": "^2.2.0",
    "gulp-uglify": "^0.2.1",
    "gulp-debug": "^0.3.0",
    "gulp-notify": "^1.2.5",
    "gulp-jshint": "^1.5.3",
    "gulp": "^3.6.0",
    "gulp-load-plugins": "^0.5.0",
    "lazypipe": "^0.2.1",
    "event-stream": "^3.1.1",
    "gulp-util": "^2.2.14",
    "gulp-watch": "^0.5.3"
  }

EDIT: I just glanced at this again and I notice these lines:

    // switch context
    .pipe(gulp.src(files[groupName]))

Be aware that I believe the gulp.src API has changed since I wrote this, and that it currently doesn't switch the context when you pipe things into gulp.src, therefore this spot might require a change. For newer versions of gulp, I think what will happen is that you will be adding to the context, instead and presumably losing a small bit of efficiency.

like image 130
laurelnaiad Avatar answered Oct 14 '22 03:10

laurelnaiad


You could write a wrapper function for tasks to capture parameters and pass it to the task. E.g. (with the help of the lodash library):

// We capture the options in this object. We use gulp.env as a base such that
// options from cli are also passed to the task.
var currentOpts = _.clone(gulp.env);

// Here we define a function that wraps a task such that it can receive
// an options object
function parameterized(taskFunc) {
    return function() {
        taskFunc.call(null, currentOpts);
    }
}

// Here we create a function that can be used by gulp.watch to call
// a parameterized task. It can be passed an object of "task" : {options} pairs
// and it will return a task function that will capture these options
// before invoking the task.
function withArgs(tasks) {
    return function() {
        _.each(tasks, function (opts, task) {
            currentOpts = _.extend(currentOpts, opts);
            gulp.run(task);
            currentOpts = _.clone(gulp.env);
        });
    }
}

var files = {
    scripts : [ "src/**/*.js"],
    vendor : ["vendor/**/*.js"
};

// We pass the task function to parameterized. This will create a wrapper
// function that will pass an options object to the actual task function
gulp.task("js", parameterized(function(opts) {
    gulp.src(files[opts.target])
        .pipe(concat(opts.output));
}));

gulp.task("watch", function() {
    // The withArgs function creates a watch function that invokes 
    // tasks with an options argument
    // In this case it will invoke the js task with the options object
    // { target : "scripts", output : "scripts.min.js" }
    gulp.watch(files.scripts, withArgs({
        js : {
            target : "scripts",
            output : "scripts.min.js"
        }
    }));
    gulp.watch(files.vendor, withArgs({
        js : {
            target : "vendor",
            output : "vendor.min.js"
        }
    }));
});
like image 2
Tiddo Avatar answered Oct 14 '22 04:10

Tiddo