I covered the potential simplicity of gulp plugins in my previous post (refresher: gulp, the hot new JavaScript build system, enables writing an asynchronous, streaming plugin in just a couple dozen lines). On the other hand, gulp’s philosophy leads to a pretty lengthy list of strong recommendations for plugins, including: don’t write a plugin if it can be reasonably avoided.
Whereas the configuration-centric Gruntfile seems to require writing a plugin for any and every tool, the code-centric gulpfile makes it easy to call a tool directly from a task. There are two major benefits to skipping the plugin:
- Plugins should be simple, but simplicity conflicts with supporting end users’ various needs
- Plugins hide tool interactions, and non-standard usage may involve time digging into their internals
Karma without the plugin
A couple months ago we needed to call Karma from our gulp build to run tests within a browser (Karma is a test runner that can use test frameworks like Mocha and Jasmine to execute tests in one or more browsers in the background). Since both Karma and gulp are fairly new, there was no clear recipe for this integration.
We started by using the gulp-karma plugin, but quickly ran into two problems. First, we wanted to store the testable file paths in Karma’s config file rather than pass the files to the plugin with gulp.src()
. Second, the Karma plugin wasn’t returning properly. The source file problem was caused by an assumption the plugin writer made about how a user would want to specify testable files, and the second problem was caused by bugs in Karma causing its process to never complete.
In an interesting bug discussion about the Karma public API, the plugin’s author actually recommends against the plugin, explaining that it was a temporary stopgap to work around issues in Karma.
What’s the alternative? The official Karma project has created its own gulp-karma repository that eschews a plugin for a simple example gulpfile.
Here’s a slight variation that uses gulp’s own watch facility and a file-based config:
var gulp = require('gulp');
var path = require('path');
var karma = require('karma').server;
/**
* Run the Karma server once with the given config file path.
*/
function runKarma(configFile, cb) {
karma.start({
configFile: path.resolve(configFile),
singleRun: true
}, cb);
}
gulp.task('test', function(cb) {
runKarma('karma.conf.js', cb);
});
gulp.task('watch', function() {
gulp.watch('test/*.js', ['test']);
});
gulp.task('default', ['test', 'watch']);
When the Karma child process exits, it invokes the given callback with its exit code which in turn completes the test task. A non-zero (i.e. non-falsey) exit code will result in the task failing.
When to skip the plugin
Phil DeJarnett wrote a post useful enough to be linked on gulp’s main plugin documentation page: Why you shouldn’t create a gulp plugin (or, how to stop worrying and learn to love existing node packages). His points are sound:
(A) Don’t write a plugin that simply wraps a node module unless it’s saving significant code mapping file streams to the module.
(B) Don’t write a plugin that encodes too many complex configuration options.
Each project is a unique snowflake. The gulpfile exists not just to chain build steps together but to handle all the distinctive configuration settings of that project. A plugin can best avoid making incorrect assumptions about project usage by either doing only one clear thing, or by not existing at all.