There are some fancy tools for doing build automation on
javascript projects that I've never felt the appeal of because the lesser-known
npm run command has been perfectly adequate for everything I've needed to do
while maintaining a very tiny configuration footprint.
Here are some tricks I use to get the most out of npm run and the
package.json "scripts" field.
the scripts field
If you haven't seen it before, npm looks at a field called
scripts in the package.json of a project in order to make things like
npm test from the scripts.test field and npm start from the
scripts.start field work.
npm test and npm start are just shortcuts for npm run test and
npm run start and you can use npm run to run whichever entries in the
scripts field you want!
Another thing that makes npm run really great is that npm will automatically
set up $PATH to look in node_modules/.bin, so you can just run commands
supplied by dependencies and devDependencies directly without doing a global
install. Packages from npm that you might want to incorporate into your task
workflow only need to expose a simple command-line interface and you can always
write a simple little program yourself!
building javascript
I write my browser code with node-style commonjs module.exports and
require() to organize my code and to use packages published to npm.
browserify can resolve all the require() calls
statically as a build step to create a single concatenated bundle file you can
load with a single script tag. To use browserify I can just have a
scripts['build-js'] entry in package.json that looks like:
"build-js": "browserify browser/main.js > static/bundle.js"
If I want my javascript build step for production to also do minification, I can
just add uglify-js as a devDependency and insert it straight into the
pipeline:
"build-js": "browserify browser/main.js | uglifyjs -mc > static/bundle.js"
watching javascript
To recompile my browser javascript automatically whenever I change a file, I can
just substitude the browserify command for
watchify and add -d and -v for
debugging and more verbose output:
"watch-js": "watchify browser/main.js -o static/bundle.js -dv"
building css
I find that cat is usually adequate so I just have a script that looks
something like:
"build-css": "cat static/pages/*.css tabs/*/*.css > static/bundle.css"
watching css
Similarly to my watchify build, I can recompile css as it changes by
substituting cat with catw:
"watch-css": "catw static/pages/*.css tabs/*/*.css -o static/bundle.css -v"
sequential sub-tasks
If you have 2 tasks you want to run in series, you can just npm run each
task separated by a &&:
"build": "npm run build-js && npm run build-css"
parallel sub-tasks
If you want to run some tasks in parallel, just use & as the separator!
"watch": "npm run watch-js & npm run watch-css"
the complete package.json
Altogether, the package.json I've just described might look like:
{
"name": "my-silly-app",
"version": "1.2.3",
"private": true,
"dependencies": {
"browserify": "~2.35.2",
"uglifyjs": "~2.3.6"
},
"devDependencies": {
"watchify": "~0.1.0",
"catw": "~0.0.1",
"tap": "~0.4.4"
},
"scripts": {
"build-js": "browserify browser/main.js | uglifyjs -mc > static/bundle.js",
"build-css": "cat static/pages/*.css tabs/*/*.css",
"build": "npm run build-js && npm run build-css",
"watch-js": "watchify browser/main.js -o static/bundle.js -dv",
"watch-css": "catw static/pages/*.css tabs/*/*.css -o static/bundle.css -v",
"watch": "npm run watch-js & npm run watch-css",
"start": "node server.js",
"test": "tap test/*.js"
}
}
If I want to build for production I can just do npm run build and for
local development I can just do npm run watch!
You can extend this basic approach however you like! For instance you might want
to run the build step before running start, so you could just do:
"start": "npm run build && node server.js"
or perhaps you want an npm run start-dev command that also starts the
watchers:
"start-dev": "npm run watch & npm start"
If you need to add a stage that executes after the watchify bundle is regenerated, you can use a tool like onchange to wire that up:
"watch": "npm run watch-js & npm run watch-css & npm run post-js",
"post-js": "onchange static/bundle.js -- npm test"
onchange can even accept globs as arguments to watch a whole directory tree of
files.
You can reorganize the pieces however you want! The dream of unix, alive and well!
when things get really complicated...
If you find yourself stuffing a lot of commands into a single scripts field
entry, consider factoring some of those commands out into someplace like bin/.
You can write those scripts in bash or node or perl or whatever. Just put the
proper #! line at the top of the file, chmod +x, and you're good to go:
#!/bin/bash
(cd site/main; browserify browser/main.js | uglifyjs -mc > static/bundle.js)
(cd site/xyz; browserify browser.js > static/bundle.js)
"build-js": "bin/build.sh"
windows
A surprising amount
of bash-isms work on windows but we still need to get ; and & working to get
to "good enough".
I have some experiments in the works for windows compatibility that should fold in very well with this npm-centric approach but in the meantime, win-bash is a super handy little bash implementation for windows.
conclusion
I hope that this npm run approach I've documented here will appeal to some of
you who may be unimpressed with the current state of frontend task automation
tooling, particularly those of you like me who just don't "get" the appeal of
some of these things. I tend to prefer tools that are more steeped in the unix
heritage like git or here with npm just providing a rather minimal interface on
top of bash. Some things really don't require a lot of ceremony or coordination
and you can often get a lot of mileage out of very simple tools that do very
ordinary things.
If you don't like the npm run style I've elaborated upon here you might also
consider Makefiles as a solid and simple alternative to some of the more baroque
approaches to task automation making the rounds these days.
git clone http://substack.net/blog.git









