Creating a modular Gulp configuration
Although you can add all the tasks that you want to run to a single configuration file, this quickly becomes unwieldy as you add more tasks to your environment. In order to keep your configuration maintenance easy, it's best to split everything up into separate files.
This means that aside from our project's directory structure, our Gulp configuration has its own structure. The following shows how I like to structure my configuration:
project-root/ gulpfile.js # Stub (loads in everything else) gulp/ config.js # Configuration – files to copy, # output paths, etc. settings.js # values of command-line flags tasks.js # Stub (loads in all the tasks) tasks/ # Contains each task, in its own some-task.js # JavaScript file another-task.js utils/ # Utility functions all tasks share paths.js # path manipulation methods
Let's go over the code that is in some of the above files. First, let's look at a simplified gulp/config.js
file, which stores the base paths, as well as source and destination paths for our project:
var config = { paths: { base: process.cwd(), // [1] dest: "build", // [2] src: "src", // [3] config: "config" // [4] }, assets: { // [5] copy: [ // [6] {src: "www/*.*", dest: "www"}, {src: "www/html/**/*", dest: "www/html"}, {src: "www/img/**/*", dest: "www/img"}, {src: "www/js/lib/**/*", dest: "www/js/lib"}, {src: "res/**/*", dest: "res"} ] } } module.exports = config; // [7]
This is a fairly simple configuration file—we'll end up adding much more to it as the book progresses.
The first section defines the various paths that Gulp will need to know in order to copy our project files as well as those necessary for transforming our code. The base
path ([1]
) is used as the foundation for every other path, and as such, every other path you see will be relative, not absolute.
The output directory is specified in [2]
, and the source directory is specified in [3]
. Configuration files that we might need for code transformation and style checking are specified in [4]
. Each one is relative to the base
path.
Every project has a set of assets, and ours is no exception – these are specified in section [5]
. In this case, we don't have very many, but even so, they need to be specified so that our tasks know what files they need to work with. We may have many different assets, some of which may require different processing, so we can add to this section as we need. For now, we just need to copy some files, and so we add them to the copy section ([6]
). Notice that we specify them in terms of a source wildcard string and a destination path. These will automatically be made relative to the src
([3]
) and dest
([2]
) paths.
The final line ([7]
) is used to export the information out of this file. We can then require
the file later in another file (and most of our tasks and the like will do so). This means that our asset and path configuration only needs to be maintained in one place.
Gulp can accept custom command-line arguments, and these can be used to control how various tasks operate. A typical argument might specify the amount of logging that is generated. This is all handled by the gulp/settings.js
file. Let's take a look:
var gutil = require("gulp-util"); var settings = { VERBOSE: gutil.env.verbose ? (gutil.env.verbose === "yes") : false } module.exports = settings;
Right now, there's not a lot going on in this file, and that's because we really don't have tasks that need to be configured using command line arguments. But we'll be adding to this file as the book goes on.
By itself, this file doesn't do much. All it is doing is using gutil.env
to read the arguments passed on the command line. In this case, it's checking to see if we passed verbose
on the command line. If we did, and the value was yes
, settings.VERBOSE
would be set to true
. If we didn't (or if we did and the value was no
), settings.VERBOSE
would be set to false
. If we want to take advantage of this setting later on in a task, we can do so.
There's one other file in the gulp/
directory, so let's take a look at gulp/tasks.js
:
var path = require("path"); var tasks = require("require-all")(path.join(__dirname, "tasks")); module.exports = tasks;
As you can see, it's a very short file. All it does is find all the tasks within gulp/tasks/
and load them into the tasks
object. Right now that would return an empty object, but by the end of the chapter, the tasks
object will contain several methods that Gulp can use. We use the require-all
package to make life easier on us—that way we don't have to inpidually require
each and every task. Later on, when we add additional tasks to our Gulp configuration, it means we don't have to later come back and edit this file.
Next, let's look at gulp/utils/paths.js
:
var path = require("path"), config = require("../config"); // [1] function makeFullPath(filepath, relativeTo) { var pathComponents = [config.paths.base]; if (relativeTo) { pathComponents.push(config.paths[relativeTo]); } pathComponents = pathComponents.concat(filepath.split("/")); return path.join.apply(path, pathComponents); } module.exports = { SRC: "src", DEST: "dest", CONFIG: "config", makeFullPath: makeFullPath };
This utility file provides a mechanism our tasks can use to craft paths that are relative to the source, destination, configuration, and base paths in our project. It makes heavy use of Node.js' path
library so that our Gulp tasks can work across different platforms.
Finally, we need to create the actual gulpfile.js
file that kicks everything off. It doesn't do much on its own; instead it loads everything else in and configures any available tasks with Gulp:
require ("babel/register"); // [1] var gulp = require("gulp"), tasks = require("./gulp/tasks"); // [2] Object.keys(tasks).forEach(function(taskName) { // [3] var taskOpts = tasks[taskName]; if (typeof taskOpts === "function") { gulp.task(taskName, taskOpts); // [4] } else { gulp.task(taskName, taskOpts.deps, taskOpts.task); // [5] } });
The first line ([1]
) imports Babel so that we can use ES2015 code in our tasks should we choose to. The third line ([2]
) imports all the tasks that are available. Right now this will be an empty object, but as we add tasks, it will contain more and more functions that Gulp can use to copy and transform files.
The code starting at [3]
just takes all the available tasks and creates a corresponding Gulp task. Each task can either be a function, or it can be an object that specifies dependencies (and more), hence the two different method invocations at [4]
and [5]
.