grunt-thrall
v0.0.4
Published
Grunt task orchestrator/warchief
Downloads
10
Maintainers
Readme
grunt-thrall
Grunt task orchestrator/warchief
"The beginning of wisdom is the statement 'I do not know.' The person who cannot make that statement is one who will never learn anything. And I have prided myself on my ability to learn." - Thrall in Cycle of Hatred, page 77
Why?
When a project uses lots of Grunt Tasks, the Gruntfile.js
tends to get really messy and
hard to maintain.
With grunt-angular-toolbox, we tried to extract this complexity into a toolbox that handles all grunt related things for a main project.
This works fine, but the toolbox itself was still spaghetti-code-ish and hard to understand and maintain for most users.
Thrall contains all orchestration logic and is 100% test covered. This allows consuming packages to focus on task definition without having to worry to much about configuration loading and option handling.
Usage
Install the module:
npm install grunt-thrall --save-dev
// gruntfile.js
module.exports = function(grunt) {
var thrall = require('grunt-thrall');
thrall.init({
/* see config */
});
};
This can be used for any project or grunt plugin. See:
API
thrall.init(config)
- Load all grunt plugins, specified in the projects
package.json
(heavily inspired by load-grunt-tasks) - merge defaults, provided in
config
with settings isgrunt.config
- Search for custom tasks, specified in the
tasks/
directory - Dynamically load configuration for grunt plugins used by those tasks from
config/
directory
Config
Required string: dir
thrall.init({dir: __dirname + 'myTasks' /* ,... */ });
The basic directory for custom tasks and grunt plugin configuration.
Expects subdir tasks/
for custom tasks and config/
for grunt plugin configuration
to be present.
Required string: basePath
thrall.init({basePath: __dirname /* ,... */ });
The projects base path.
Used to findup node_modules/grunt-*/tasks/*
when auto-loading grunt plugins.
Required object: grunt
thrall.init({grunt: grunt /* ,... */ });
The currently running grunt instance.
string: name
thrall.init({name: 'myProject' /* ,... */ });
Defaults to config.pkg.name
project name from package.json
This is also the key for custom configuration that is merged with the defaults
// pseudo-code
var config = _.merge(config.getDefaults(), grunt.config(config.name));
boolean: loadDevDependencies
thrall.init({loadDevDependencies: false /* ,... */ });
Default: true
Whether or not to include devDependencies
from package.json
when auto-loading grunt plugins.
boolean: loadDependencies
thrall.init({loadDependencies: true /* ,... */ });
Default: false
Whether or not to include dependencies
from package.json
when auto-loading grunt plugins.
object: module
thrall.init({
module: {
myHelper: ['factory', require('./helpers/myHelper')]
}
/* ,... */
});
Default: {}
Task definitions, grunt plugin configurations and getDefaults are being invoked using node-di providing basic node modules.
When you need a custom helper, it can be registered here.
See DI for further informations.
function: getDefaults
thrall.init({
getDefaults: function(/* di here */) {
return {
foo: 'bar'
}
}
/* ,... */
});
Default configuration will be merged and be available as grunt.config(config.name)
.
Task Factories
every file in config.dir
/tasks/
is expected to export a factory function, returning
a task definition object. The name will be generated by the path relative to the tasks dir.
Factories are being invoked using node-di, see DI for further informations.
Naming
// tasks/foo/bar.js
module.exports = function(/* di here */) {
return {};
};
will register task foo:bar
that, when can be called with grunt foo:bar
and does nothing.
string/array: description
module.exports = function() {
return {
description: [
'this is the bar tasks',
'it will foo.'
]
/* ... */
};
};
Task description, which is displayed by grunt --help
.
Arrays will be .join('\n')
-ed.
array: run
Subtasks to run by this task.
// tasks/foo/bar.js
module.exports = function() {
return {
/* ... */
run: [
'jshint:src',
'mochaTest'
]
};
};
Will load for grunt plugin configurations from
config/jshint/src.js
and config/mochaTest.js
(see
Configuration Factories)
and execute both subtasks when grunt foo:bar
is called.
runIf blocks
A runIf block can add tasks to the cue based on grunt configuration.
module.exports = function() {
return {
/* ... */
run: [
'other:task',
{
if: 'coverage.enabled',
task: ['coverage']
},
{
if: [
(null != 1),
'foo.bar'
],
task: 'report',
else: 'say:goodbye'
}
]
};
};
In the above example:
- the
coverage
task will only run whengrunt.config.get('coverage.enabled')
returns a truthy value. - the
report
task will run whengrunt.config.get('foo.bar')
is truthy, (andnull != 1
which is of cause true, too) - when
grunt.config.get('foo.bar')
is falsy thesay:goodbye
task runs instead
All expressions and config values in an if
-array need to be true in order to run the task.
There is no OR
operator or !
-negation.
This works well with options.
object: options
Map CLI options, environment variables and grunt modifiers to grunt config.
// tasks/foo/bar.js
module.exports = function() {
return {
/* ... */
options: {
coverage: 'coverage.enabled'
}
};
};
grunt foo:bar --coverage
will set the grunt.config('coverage.enabled')
to true.
// tasks/foo/bar.js
module.exports = function() {
return {
/* ... */
options: {
'demo-port': {
env: 'DEMO_PORT',
alias: 'port',
key: 'foo.demoPort'
}
}
};
};
either of
grunt foo:bar --demo-port=7000
grunt foo:bar --demo=7000
DEMO_PORT=7000 grunt foo:bar
will set the grunt.config('foo.demoPort')
to 7000.
// tasks/foo/bar.js
module.exports = function() {
return {
/* ... */
options: {
coverage: {
grunt: ':coverage',
key: 'coverage.enabled'
}
}
};
};
grunt foo:bar:coverage
will set grunt.config('coverage.enabled')
to true.
function: runFilter
Filter that may manipulate the tasks cue before execution.
// tasks/foo/bar.js
module.exports = function() {
return {
/* ... */
run: ['foo', 'bar'],
runFilter: function(tasks, args) {
if (args[0] === 'baz') {
tasks.shift();
}
return tasks;
}
};
};
In this case, when grunt foo:bar:baz
is called, only the foo
subtask will run.
Configuration Factories
every file in config.dir
/config/
is expected to export a factory function, returning
a configuration object. The name has to match the path that this configuration will
be placed at, in the grunt config.
Factories are being invoked using node-di, see DI for further informations.
// config/jshint/src.js
module.exports = function(/* di here */) {
return {
options: {
ignores: ['**/*.coffee'],
jshintrc: true,
},
src: [
'<%= my.src.files.js %>'
]
};
};
This is similar to the following standard configuration, only that it's split in to a lot of small files, with is more easy to maintain for big projects.
grunt.initConfig({
jshint: {
src: {
options: {
ignores: ['**/*.coffee'],
jshintrc: true,
},
src: [
'<%= my.src.files.js %>'
]
}
}
});
DI
getDefaults
, Task Factories and
Configuration Factories are being invoked with a
node-di module, providing the following
services:
_
: lodashcliOptions
: CLI Options using minimistdel
: delfindupSync
: node-findup-syncfs
: node modulegetobject
: node-getobjectglob
: node-globgrunt
: currently running grunt instancemkdirp
: node-mkdirppath
: node modulemerged
: (getDefaults
ONLY) See merged callbackname
: (Task Factories ONLY) name of the taskrootTask
: (Task Factories ONLY) the name of the task that has actually been called
merged callback
/* ... */
getDefaults: function(merged) {
merged(function(mergedConfig) {
mergedConfig.foo = 'baz';
});
return {foo: 'bar'};
}