Task automation with npm run

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:

(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"


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.


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.

Add Comment

Required fields are marked *. Your email address will not be published.