Gulpfiles over time is an interesting little window into how my style and preferences have evolved, it pains me to have so much subtly duplicated functionality spread across so many projects, none of which are kept consistently up-to-date.
Aside from my own meandering laziness, I believe this is partly owing to Gulp's design, and partly what Gulp was created in reaction to. Whereas Grunt expected you to cram every varying combination of options into declarative configuration, which is easy to store and move around, but not very expressive, Gulp encourages composition through the definition of many small sub-tasks, which is highly expressive but tends to result in tightly-coupled processes & repeated steps, and managing configuration is left up to the developer. Both approaches leave something to be desired.
The middle path
Furthermore, while Gulp's chaining style makes for terse syntax, and its factory-function style makes it easy to configure individual process steps, it presents no clear way to configure the process itself. The ideal is a unified way to configure both the steps and the process. Also, a way to cleanly separate the process from the configuration, compose them in one place, and extract out any project-specific details, so that one
Gulpfile can rule them all.
Fortunately, most of our projects have similar needs, and follow a relatively consistent directory structure:
build/: intermediate build files
dist/: production-ready distributable files
spec/: BDD spec files, suffixed as
src/: library/application source files
Most new JS projects are written in ES6 (ES2015), so our needs for each project generally look something like:
- Compile source & tests to a particular module format (usually CommonJS or UMD)
- Run tests using built files
- Generate minified and non-minified production builds
- Generate sourcemaps
- Watch source & tests for changes during development, re-run tests
- Run a dev server to serve files for in-browser testing (sometimes)
After reviewing a few of our most recent projects, I was able to come up with a
Gulpfile that meets the above requirements, but also satisfies my desired constraints on process & configuration. Without further ado, weighing in at 55 lines (at the time of this writing), here is the listing in its entirety:
Important things to note:
- As of Gulp 3.9, files named
Gulpfile.babel.jsare automatically transpiled from ES6, which for us means that
- Almost all of the configuration is encapsulated in 2 blocks: one for file and directory paths, one for build settings
- Project-specific configuration, such as the name of the build file and the port on which to run the dev server (where applicable) has been extracted out to
package.jsonin a custom key called (naturally)
- All possible build steps are listed out in one place (
pipeline(), lines 29-36); since the order of each step relative to others is always consistent, configuration can be used to toggle them on and off — which is made easy with gulp-if (aliased as
- A few small utility functions (
chain()) are used to map named build configurations (with optional overrides) to a flattened-out
.pipe()chain, and tighten up function-handling syntax (
- Though it's not really necessary for low-complexity scripts like this, I've gotten into the habit of declaring things as
constwhenever possible (even though JS
consts aren't really real constants)
Each build task is then assigned a function with a named configuration, which can be merged with passed parameters (in the form of command line arguments in the case of
The end result is that almost everything is expressed exactly once. Not only that, it allows for any configurable option to be overidden from the command line, for free. For example, if I wanted a minified production file with sourcemaps and AMD-formatted modules, I could run
gulp dist --min --smaps --modules=amd. This approach also has the benefit of programatically enforcing project structure conventions.
Gulpfile, and project-specific tasks can always be added transparently by introducing a
So that's my take. If you too are obsessive enough about build configuration to have a philosophy for managing it, I'd love to hear about it.