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 bothhello:merott
andhello: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