Automating Linkage or: How I Learned To Stop Worrying And Love The Build
Building with Gulp.js
I've recently come to Javascript, and the first thing I wanted to do was set up a build for my project. We'd been using Grunt, but that all looked a bit ANTsy, so when a colleague recommended gulp.js, I jumped right in.
Getting started was easy, with npm and Bower downloading and installing the dependencies I needed; it all felt very familiar to Maven, Gradle and Leiningen I'm used to from back-end dev. But there's one part missing: Bower pulls the libraries, but doesn't provide them to my app. I have to maintain my dependency list in my build file as well, to ensure the libraries are linked in my index.html in the right order.
"That's not very DRY", I thought, "I should automate this".
I took a look around and couldn't find an existing tool to do it, so I put it together myself. Here's how to do it.
Dependency management with wiredep and gulp-inject
Bower knows about transitive dependencies, but doesn't expose that information in a way that can be easily consumed in
Javascript. Instead, we need to calculate the dependency tree ourselves by walking the bower.json
files of each
dependency in order. This job is done for us by wiredep. Its gulp plugin
isn't very configurable, but that's ok, because we don't need gulp plugins. Wiredep itself
can produce a list of our dependencies and wire it into our index.html
. Wiredep won't do the same for our own code
though, for that we'll use gulp-inject.
First, we add placeholders for our CSS links and script tags to our index.html
, which is where wiredep
(using bower:blah) and gulp-inject (using inject:blah) will write in our tags:
In the spirit of automating ALL THE THINGS, in our gulpfile let's use the gulp-load-plugins plugin:
That exposes all the plugins we have in our package.json
inside the plugins variable, and we don't have to worry about
adding each one manually. As we aren't using the wiredep plugin, we still require it directly.
Wiredep calculates the dependency list and stores it in its js
and css
variables; we can use these in our gulp
tasks to copy the files to the build
folder:
Now we process the index.html
to reference all these files. We pipe it through wiredep and gulp-inject, and write it
out at the end:
There are two main things going on here: wiredep's injection of third party dependencies, and gulp-inject's, um,
injection of our own dependencies into the index.html
.
We're using wiredep's stream()
function, so we can use it as part of a
node stream; it reads our project's bower.json
, calculates the
dependency tree, generates the <script>
and <link>
tags for all our dependencies, in the correct order, and injects
them into the index.html
. The configuration here is telling it that the tags it creates should be pointing into the
vendor
directory, where we've stuck all our third party libraries in the vendor-scripts
and vendor-css
gulp tasks
(above).
We also want to inject our own script files after all the dependencies, but wiredep doesn't stretch that far, so we add
a bit of work for gulp-inject to do: we simply give it a glob that picks up our code in the output folder, and
transforms the paths so they're relative to the build index.html
instead of the source one. Much like we did with
wiredep, we tell it how to generate the <script>
and <link>
tags in the right way.
Overriding Bower
It's nearly there! Every time I add a dependency to my bower.json
, it'll automatically get wired into my
index.html
, so long as the main entry in its bower.json
is correct. Sadly quite a few libraries don't provide this
information. In these cases, you need to add an override to your bower.json
for the problematic library:
Bower and npm seem pretty standard now in the Javascript world, so if you find a library where the main element is missing, why not throw over a Pull Request to fix it? It's likely to be quickly accepted (like the authors of the excellent angular-gridster project from the example above did), removes config from your application and helps others. Literally everybody wins!