gulp-di is a dependency injection framework for the Gulp
streaming build system.
- Shrink your Gulpfile to less than ten lines.
- Manage gulp plugins solely via npm and variables in your task module files.
- No major refactoring required.
See below for further examples!
Installation
$ npm install --save gulp-di
Built-in dependencies
You can use the following dependencies in your modules and tasks.
Name | Type | Description |
---|
_ | Function | lodash |
basePath | Function | Resolves a path relative to your project path |
chalk | Object | chalk for using colors when logging |
gulp | Object | The gulp instance |
gutil | Object | gulp-util |
log | Function | gulp-util's log |
Package | Object | package.json as object |
runningTasks | Array | currently running gulp tasks (experimental) |
taskInfo | Object | infos about task functions (experimental) |
In addition, all Gulp modules which are installed in your package.json are
available in your modules, driven by gulp-load-plugins.
For example, if you install the "gulp-concat" as devDependency (in your
package.json) plugin using ...
$ npm install --save-dev gulp-concat
... it will become available as dependency in each module file:
module.exports = (gulp, concat) => {
return gulp.src('src/**/*.txt')
.pipe(concat('result.txt'))
.pipe(gulp.dest('public/'));
};
Please read the API notes below for configuring a pattern for packages which
do not start with "gulp-" or "gulp.*".
API
GulpDI(object gulp, object options)
Creates a new GulpDI instance. The first argument must always be the gulp
instance from your Gulpfile.
If you specify options, these will be passed to gulp-load-plugins under the hood ("camelize" cannot be configured at the moment).
let di = GulpDI({
DEBUG: false,
pattern: ['gulp-*', 'gulp.*'],
config: 'package.json',
scope: ['dependencies', 'devDependencies', 'peerDependencies'],
replaceString: /^gulp(-|\.)/,
rename: {},
renameFn: function (name) { ... }
}, require('gulp'));
The following options are additionally available:
Name | Type | Description |
---|
DEBUG | Boolean | Whether to log debugging information or not |
noBuiltin | Boolean | Disables helpers: basePath, log, Package |
noHelp | Boolean | Toggles the "help" task/providing taskInfo |
noModules | Boolean | Do not expose chalk, gutil and lodash (_) |
noRunningTasks | Boolean | Do not provide the "runningTasks" function |
Chainable methods
The following methods are chainable:
.inject(function fn, returnValue)
Executes code which depends on the current's instance dependencies.
Please note that you will have to resolve() your instance if you should depend
on anything else than payloads which have been initialized by provide().
When returnValue is set, this method returns the return value of the injected
function (e.g. the function is not chainable when set to true)
di.provide({ 'PI' : Math.PI, 'DEG_TO_RAD' : 180 / Math.PI });
let circle = di.inject(['PI','DEG_TO_RAD', (PI, DEG_TO_RAD) => {
return 2 * PI * DEG_TO_RAD;
}], true);
console.log(circle);
.provide(string name, * payload)
Provides a constant for further usage in modules and tasks. You can can also
provide a hashmap of constants as following:
di.provide({ 'PI' : Math.PI, 'SIN' : Math.sin });
.task(function fn)
Adds a task module. Add your provide()d dependencies to the function's argument
list to require a dependency. Their order does not matter.
Please note that tasks don't have names (in contrast to modules) as
you shouldn't depend on them using gulp-di. Use the "deps" array when calling
gulp.task() instead in order to achieve a specific task execution order.
You can also use a hashmap as following:
di.task({
assets : (gulp) => gulp.src('src/**/*.png').pipe(gulp.dest('public/'))
});
.module(string name,function fn)
Adds a module with the given name. In contrast to a task, a module has a name
and its return value will be provided - thus, modules can be injected into tasks
as well as your constants.
You can provide a hashmap, too.
.resolve()
Resolves all dependencies and runs all task modules. You need to call this
method after your declarations.
.tasks(string directory)
Loads all tasks from the specified directory, using require-dir.
.modules(string directory)
Loads all modules from the specified directory.
.byId(string name)
Returns the specified dependency. When you are demanding modules, please keep in
mind that you will need to call resolve() beforehand, otherwise, this would
return your module function.
Using dependency injection in general
By design, dependency injection is opt-in for everything, thus you can use
gulp-di for use cases beyond your gulp tasks.
Setting values
When using DI, you can set values on your instance to arbitrary values. They
will become available in your module files.
const gulp = require('gulp');
let di = require('gulp-di')(gulp, { DEBUG: false });
di.provide('PI', Math.PI);
di.provide('RAD_TO_DEG', 180 / Math.PI);
Declaring task modules
You can then declare a single function or a whole directory of
CommonJS modules using the tasks() method.
di.task((PI, RAD_TO_DEG, basePath) => {
console.log((PI * 2 * RAD_TO_DEG) + '°');
});
Note that you have access to the gulp-di instance in the task function's scope.
You could f.e. access the "options" object in your function as following:
di.task(() => {
if (this.options.DEBUG) { console.log('Debug is enabled'); }
});
Running all modules and resolving dependencies
You need to execute all provided values and modules by running resolve()
afterwards.
While you place calls to task(), tasks() and provide, the execution order of your
depends on the declared dependencies. With resolve(), the graph is being
resolved and all modules are called with the provided values.
di.resolve();
Experimental functionalities
Please use these features with caution for now.
gulp help
This example is included by default.
An included experiment uses the API to intersect all calls to gulp.task to
collect information about the tasks.
It registers a gulp "help" task which logs then all comments included in
gulp.task. In addition, a "taskInfo" hashmap is available for injection. It
contains the information about your gulp tasks which is being also used by
the "help" task.
const gulpDi = require('gulp-di');
let di = gulpDi()
.task(function (gulp, taskInfo, log, chalk) {
gulp.task('my-help', function () {
for (let name in taskInfo) {
let entry = taskInfo[name];
log(chalk.magenta(name), 'with dependencies', entry.deps);
}
});
})
.resolve();
runningTasks
This function returns an array of strings, containing all current Gulp tasks,
including dependencies.
Helper functions
The following functions are currently not integrated into gulp-di, but you can
require them as following:
const getPath = require('gulp-di/contrib/get-path');
const standardTask = require('gulp-di/contrib/standard-task');
getPath(string key)
Returns a property specified by the given dot-delimited key:
const data = {
at : 1350506782000,
ids : ['507f1f77bcf86cd799439011', '507f191e810c19729de860ea']
};
let PI = getPath(Math, 'PI');
let at = getPath(data, 'at'));
let firstId = getPath(data, 'ids.0');
standardTask(string name, string src, string dest, ...)
Generates a default task, assuming you want to pipe src through the plugin specified by name to dest. All parameters following "dest" are serialized if necessary and passed
to the plugin.
Using this method, you will just need to install a gulp plugin with npm and set
up a task as following:
let taskFn = standardTask('jade', 'templates/**/*.jade', 'public', { pretty : false });
di.task(taskFn);
console.log(taskFn.toString());
function jadeTask(gulp, jade) {
gulp.task("jade", () => {
return gulp.src("templates/**/*.jade")
.pipe(jade({"pretty":false}))
.pipe(gulp.dest("public"));
});
}
Example: Basic refactoring
Instead of declaring all gulp tasks in a single file, gulp-di allows you to
take a more modular approach. You can separate your stream definitions from
configurations while gulp-di handles the majority of your existing calls to
require() for you.
In this example, we will see how we can move a call to gulp.task() to a
separate file.
Additionally, we will see how we can detach a constant (in this case: a glob
matching all files with the "jpg" extension) from the actual task.
The following task
const gulp = require('gulp');
gulp.task('images', () => {
return gulp.src('./**/*.jpg')
.pipe(gulp.dest('output/'));
});
would be re-factored to the file at tasks/images.js as following.
module.exports = (gulp, imagesPath) => {
gulp.task('images', () => {
return gulp.src(imagesPath)
.pipe(gulp.dest('output/'));
});
};
Notice that the function uses the "imagePath" constant. Such constants can
be defined in your Gulpfile files in order to separate them from the tasks.
Thus, you can now use all of your Functional programming skills with Gulp and
assemble your tasks from other declarations and build much more
flexible and re-usable tasks.
gulp-di should help you to reduce your Gulpfile's complexity.
In this example, we will declare additionally the "imagePath" constant which is
being used in our "images" task.
const gulp = require('gulp');
let di = require('gulp-di')(gulp);
.tasks('./tasks')
.provide('imagesPath', 'src/**/*.jpg')
.resolve();
Additionally, you can now "inject" arbitrary values into your functions, e. g.
run-time configurations, asynchronous methods for usage with Gulp's asynchronous
API, constants etc.
The following example uses constants and modules in order to compose a helper
function.
const gulp = require('gulp');
let di = require('gulp-di')(gulp);
.tasks('./tasks')
.provide('sourcePath', 'src')
.module('extensionPath', (sourcePath) => {
return (extname) => `${sourcePath}/**/*.${extname}`;
})
.task(function (gulp, extensionPath) {
gulp.task('copy-images', function () {
return gulp.src(extensionPath('jpg')).pipe(gulp.dest('public/'));
});
})
.resolve();
FAQ
How do I insert tasks? Gulp claims "Task 'default' is not in your gulpfile" all the time!
Please call the resolve() method after all of your module and task declarations.
gulp-di does not find my module!
In order to make your gulp plugins available, gulp-di uses
gulp-load-plugins internally
by default.
The default pattern is ['gulp-', 'gulp.', '!gulp-di']. You need to initialize
gulp-di using the "options" object in order to make plugins with arbitrary names
available:
const gulp = require('gulp');
let di = require('gulp-di')(gulp, {
pattern : ['gulp-*', 'gulp.*', '!gulp-di', 'webpack-stream']
});
di.task((webpackStream) => {
gulp.task('webpack', () => {
return gulp.src('src/client.js')
.pipe(webpackStream({ output : { filename : '[name].js' }}))
.pipe(gulp.dest('public'));
});
});
di.resolve();
Is it possible to add tasks asynchronously?
No, you will need to set up asynchronous tasks which depend on each other in
order to perform asynchronous tasks. Nevertheless, all tasks must be declared
synchronously in order to use Gulp correctly.
I dislike the camelCaseName of my favorite module, can I change it?
Using gulp-load-plugins, you
can change the assigned name for arbitrary modules.
Using a rename
object:
let di = require('gulp-di')(gulp, {
rename : {
webpackStream : 'webpack'
}
});
Using a renameFn
:
let replacements = { webpackStream : 'webpack'};
let di = require('gulp-di')(gulp, {
renameFn : (key) => return replacements[key]
});
Changelog
0.0.3 - 03/12/2016
- ES2015 rewrite
- CI using Travis
- using and providing lodash by default
- Adding "standardTask" and "getPath" helpers
- supporting gulp-load-plugins's lazy loading of gulp plugins
0.0.2 - 02/01/2016
- Updating dependencies
- Adding "runningTasks" helper function
- Exposing gulp-util as "gutil" by default
- Parsing of multi-line comments rewritten
- added new options : noModules, noBuiltin, noRunningTasks
- ES2015+ support with parse-function
0.0.1 - 12/20/2015
- Initial release, incorporating Resolver and tests from dijs.
Licence
MIT