redun

A Gulp task generator to help keep your task definitions DRY.


Keywords
redun, gulp
License
MIT
Install
npm install redun@0.2.1

Documentation

Redun

Redun is a a Gulp task generator to help keep your task definitions DRY. It makes it easier to create multiple tasks (called a task family), that perform similar functions, using cascading configurations, while encouraging definition of one file/module per task family.

Installation

Install via NPM:

npm install --save-dev redun

Requiring Redun

To require redun in CommonJS you must access the default exported property, which is also exported as redun:

var redun = require('redun').redun;
var redun = require('redun').default;

If you're writing in ES6 syntax, in addition to the above, you can import using either of these two approaches:

import {redun} from 'redun';
import redun from 'redun';

Basic Usage

/******** gulpfile.js ********/

var gulp = require('gulp');
var redun = require('redun').redun;

redun.add('tasks/**/*.js');
redun.bootstrap();

gulp.task('default', ['hello']);
/******** tasks/hello.js ********/

// export a function
module.exports = function(callback) {
  console.log('Hello Redun');
  callback();
};

Creates a hello gulp task, which runs the exported function.

Tasks with a configuration object

Configuration for tasks can be specified by exporting an object instead of a function, and specifying a formula property for the configuration, and an actions property which specifies the task function itself. The configuration object is generated from the formula and available as this.config within the context of the executing function.

/******** tasks/hello.js ********/

// export an array
module.exports = {
  formula: {
    name: 'Merott'
  },

  action: function(callback) {
    console.log('Hello ' + this.config.name);
    callback();
  }
}

Task Families

Prefixing a config property name with : indicates a subtask/child task. This task will inherit config properties of the parent task but can override them.

/******** tasks/hello.js ********/

module.exports = {
  formula: {
    ':merott': {
      name: 'Merott'
    },

    ':james': {
      name: 'James'
    }
  },

  action: function(callback) {
    console.log('Hello ' + this.config.name);
    callback();
  }
};

Creates 6 tasks:

  • hello - runs both hello:merott and hello:james tasks, using gulp task dependencies.
  • hello.
  • hello:merott
  • hello:merott.
  • hello:james
  • hello:james.

Redun calls these a Task Family.

See Tasks without external dependencies to understand what the dot-suffixed versions of the tasks are about.

Note that when a task has at least one child task, it becomes an alias task and does not execute itself, but instead triggers its children tasks to run. It's equivalent to defining a Gulp task like this:

gulp.task('say-hi', ['say-hi:hello', 'say-hi:hola']);

It is possible to explicitly set which children tasks to run when calling an alias task, by defining the #default property:

/******** tasks/hello.js ********/

module.exports = {
  formula: {
    '#default': 'merott', // string, or array of string(s), e.g. ['merott']

    ':merott': {
      name: 'Merott'
    },

    ':james': {
      name: 'James'
    }
  },

  action: function(callback) {...}
};

With the above configuration, running gulp hello will only run gulp hello:merott, not both.

Tasks with dependencies

Meta configurations for tasks can be specified using special properties that are prefixed with the # symbol. One such special property is #deps which specifies gulp dependencies of that task.

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['clean']
  },

  action: function jsBuildFunction() {
    // return js-build stream
  }
}

Tasks without external dependencies

Every Gulp task created by Redun also comes with a sibling task, that excludes any external task dependencies. For example, when you define a js-build task that depends on some clean task, Redun will also create a js-build. task, that will not depend on clean. This allows the js-build process to be run on its own, without triggering a clean.

To achieve this using pure Gulp, you'd do something like:

/******** gulpfile.js ********/

gulp.task('js-build', ['clean'], jsBuildFunction);
gulp.task('js-build.', [], jsBuildFunction);

function jsBuildFunction() {
  // return js-build stream
}

With Redun, you only have to know that the following export creates a js-build. task as well as js-build.

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['clean']
  },

  action: function jsBuildFunction() {
    // return js-build stream
  }
}

Let's say js-build was actually defined like this:

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['clean'],

    ':es5': {
      transpile: false
    },

    ':es6': {
      transpile: true
    }
  },

  action: function jsBuildFunction() {
    // return js-build stream, transpiled if es6
  }
}

The above would create a js-build task that depends on jsbuild:es5, jsbuild:es6, and clean.

Additionally, it creates a jsbuild. task, which depends on jsbuild:es5. and jsbuild:es6., but not on clean.

Optional dependencies

You can make some dependencies optional by prefixing them with a ? symbol:

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['?clean']
  },

  action: function jsBuildFunction() {
    // return js-build stream
  }
}

The clean task will be an optional dependency of the js-build task, and ignored if the task clean does not exist.

Preventing inherited properties

By default all task configuration properties (except meta configurations), are inherited by child tasks in a task family. To prevent a configuration from being inherited, you can prefix the property name with the - symbol. The prefix is automatically removed by Redun during task generation.

module.exports = {
  formula: {
    '-someConfig': 'someValue'
  },

  action: function taskFunction() {}
}

Inheriting meta configurations

By default meta configurations, marked with the # prefix, do not cascade and are not inherited by child tasks. If you want a meta property to cascade to children tasks, you can prefix it with +, so for example to to apply a cascading #deps configuration, you would set it as +#deps.

Merging configurations instead of overriding

Normally a configuration property that is redefined in a child task overrides the configuration that is inherited from the parent. You can tell Redun to merge an object/array with the one from the parent by prefixing the property name using the ^ symbol.

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['clean'],

    ':es5': {
      transpile: false
    },

    ':es6': {
      transpile: true,
      options: {
        comments: true
      },

      ':with-sourcemap': {
        '^options': {
          sourcemaps: true
        } // becomes { comments: true, sourcemaps: true }
      }
    }
  },

  action: function jsBuildFunction() {
    // return js-build stream, transpiled if es6
  }
}

Overriding task names

By default Redun will create Gulp tasks matching the names of the files that it matches. You can override the given name by passing a second argument to redun#add, which has original task names as keys, and replacement task names as values.

redun.add('tasks/**/*.js', {hello: 'hola'});

Alternatively, you can pass a function, which will be called for every task matched, passing the original name and expecting the new name returned:

redun.add('tasks/**/*.js', function(name) {
  return name.replace(/\.task$/, '');
});

Alias tasks

You can define an alias task by providing a single task name or multiple task names in place of the configuration of that task:

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['clean'],

    // js-build:es2015 aliases 'js-build:es6'
    ':es2015': 'js-build:es6',

    // 'js-build:z' aliases 'js-build:x' and 'js-build:y' in parallel
    'z': ['x', 'y'],

    ':es6': {
      transpile: true
    }
  },

  action: function jsBuildFunction() { /* ... */ }
}

It is recommended to use the originQualifier setting when defining task aliases to ensure that the alias will still work even if the task names are overriden:

/******** tasks/js-build.js ********/

module.exports = {
  formula: {
    '#deps': ['clean'],

    ':es2015': 'jsModule/js-build:es6', // jsModule is the originQualifier

    ':es6': {
      transpile: true
    }
  },

  action: function jsBuildFunction() {
    // return js-build stream, transpiled if es6
  }
}

API

redun.add(glob[, overrideNames, originQualifier])

Add recipes to be loaded when redun bootstraps.

glob

Type: String | String[]

A single glob, or an array of globs, that will be expanded to find modules containing task recipes to add. Non-absolute paths will be resolved relative to the current working directory.

overrideNames (optional)

Type: Object | Function

An object map of task names to override, e.g. { webpack: bundle } will change the name of the webpack task to bundle.

Or, a function that will get called for each recipe matched by the glob. Read more under Overriding task names.

originQualifier (optional)

Type: String

Qualify task names using a {originQualifier}/ prefix, e.g. If myPrefix is set as the qualifier, task webpack will also be registered as myPrefix/webpack (aliased). Please note tasks that were given new names through the overrideNames parameter retain their original names in their qualified version. i.e. given myQualifier as the qualifier, renaming webpack to bundle will lead to registering myQualifier/webpack as an alias of bundle.

redun.add('tasks/**/*.js', {hello: 'hola'}, 'origin');

The above registers the hola task, and an alias of it as origin/hello.

To do

Although Redun is usable, it still requires a lot of polishing. Here is a quick to-do list:

  • Unit tests
  • Logging, with multiple levels
  • Auto-add recipes from node_modules
  • Check for recursive dependencies - fatal
  • And more... see // todo(mm): comments