---
title: RFC: Task / Plugin Unification
---
This RFC presents an alternative plugin API that matches the current task API.
Unifying tasks and plugins should encourage task reuse (as plugins), simplify the overall API, and allow a single core mental model for how taskr tasks and plugins function.
Generally, a unified task or plugin has the following shape:
type Result = void | any | Task;
function (task: Task, ...options: any[]): Result | Promise<Result> {
// Transform given task with options
}
- All tasks and plugins are passed to
Bluebird.coroutine
, so all function types, including async functions and generators, are supported. - If a
Task
is returned, taskr will continue work from that task's state, allowing task chains to be created (more details below). - For non-task returns, the current
val
approach is used, to pass values to subsequent tasks.
To utilize this new approach in a semver-minor-friendly manner, plugins using the unified model are denoted with named exports
and use peerDependencies
to specify a minimum supported taskr version. This should allow changes while maintaining compatibility with the existing ecosystem.
// Existing approach
module.exports = function(task) {
task.plugin('name', { files: true }, function * (files) {
// ...
});
};
// Unified approach
exports.name = function * (task) {
const files = task._.files;
// ...
};
It's fairly straightforward to match existing functionality with wrapper utilities:
// Before
module.exports = function(task) {
task.plugin('sass', { every: true }, function * (file) {
// ...
});
}
// After
const every = require('@taskr/utils').every;
exports.sass = function(task) {
return every(task, function * (file) {
// ...
});
};
To accomodate this new approach, an updated form of task.run
is introduced which runs the task/plugin directly.
async function build(task) {
task.a().b() === task.run(a).run(task => b(task));
task.start('js') === task.run(js);
task.parallel(['js', 'css']) === task.parallel([js, css]);
}
Finally, to allow chaining, tasks continue from a previous task if a Task
is returned.
async function build(task) {
return task.run(js).target('build');
}
async function js(task) {
// By returning the resulting task from source -> babel
// the parent can continue chain
return task.source('js/**/*').babel();
}
In addition to allowing for chaining, this removes unexpected side effects from tasks run in succession, see #289, by returning a new task from each step rather than mutating the initial task.
async function build(task) {
await task.a().b().c();
// ^ ^ ^
// a new, lightweight task is generated at each step
// -> no side-effects on original task
// task has not been mutated, can safely run subsequent tasks
await task.d().e().f();
}
This new approach allows for many interesting future possibilities, e.g. merging tasks:
async function build(task) {
return task.merge(js, css).target('build');
}
async function js(task) {
return task.source('src/js/**/*.js').typescript().babel();
}
async function css(task) {
return task.source('src/css/**/*.scss').sass();
}
Conditional tasks:
const production = process.env.NODE_ENV === 'production';
function js(task) {
task.source('src/js/**/*.js')
.typescript()
.babel()
.check(production, task => task.uglify());
}
While the side-effect free approach of chaining tasks may be preferred for its "correctness", it may cause compatibility issues with existing systems. Task changes will have to be approached carefully.
These examples are possible with the current version of taskr, but they should be much more straightforward to implement with the unified system.