This post originally appeared on strongloop.com. StrongLoop provides tools and services for companies developing APIs in Node. Learn more...

Most Node.js developers use npm as a package manager. However, npm is also a powerful task runner that can serve as a replacement for gulp. The npm run command lets you define custom scripts in your package.json, so you can reduce complex node-related shell scripts into simple one-liners. In this article, you'll learn about common npm run use cases, including using npm run to pipe ES6 browser code through Babel and Browserify.

Setting Environment Variables and Flags

Node.js has several handy configuration options you can set using environment variables and command line flags (like the old --harmony flag that you used to enable ES2015 features in 0.12.x). The npm run command, and npm start in particular, let you set any flags your application needs before running.

For instance, the NODE_PATH environment variable lets you add additional directories to the require() function's module search path. In other words, suppose your project has a lib directory that contains your source code, and a test directory that contains your tests. Let's say you have a test in test/e2e/my_feature/my_feature.test.js that wants to require() in a function that's declared in lib/server/my_feature/utils.js. That's going to involve a brittle require('../../../lib/server/my_feature_utils'); command that'll break every time your directory structure changes. However, if you run env NODE_PATH=./ node test/e2e/my_feature/my_feature.test.js, require() will know to look in the current directory, so you can instead use require('lib/server/my_feature/utils');.

Unfortunately, every environment variable and flag you need to set adds extra complexity to starting your application. Odds are, if you need to set 17 environment variables and flags to start your application, you're going to forget one or two. Suppose your application relies on the env NODE_PATH=./ trick as well as ES2015 proxies, which are hidden behind the --harmony_proxies flag as of this writing. You can define a start script in your package.json file as shown below.

{
  "scripts": {
    "start": "env NODE_PATH=./ node --harmony_proxies index.js"
  }
}

Now, if you run npm start (which is just short for npm run start), npm will run the start script for you and start your application with your special configuration options.

Command Line Shortcuts

Let's face it, asking users to npm install -g (usually) sucks. You may have heard that the upcoming gulp 4.0 release will be entirely incompatible with gulp 3.x. Since many people install gulp globally and there's no way for package.json to enforce globally-installed packages, a lot of users are going to end up playing a guessing game as to which version of gulp they need.

It's much better to list gulp as a devDependency in package.json, but then you have to run ./node_modules/.bin/gulp watch rather than gulp watch, which is a pain. That's where npm run comes in; it adds ./node_modules/.bin to your PATH. In other words, if you list gulp 3.8 as a devDependency, you can access the gulp executable in your package.json scripts without asking your users to npm install gulp -g.

{
  "scripts": {
    "watch": "gulp watch"
  }
}

Now, npm run watch is a handy shortcut for ./node_modules/.bin/gulp watch. You can do the same thing with mocha.

{
  "scripts": {
    "test": "mocha -r nyan test/*.test.js"
  }
}

Now npm test (which is the same as npm run test) is a handy shortcut for running all your mocha tests in your test directory using the nyan cat reporter.

The mocha executable also has some neat command line flags. For instance, the --grep (or -g for short) mocha flag lets you only run tests whose names match the given regular expression. In npm >= 2.14.0, you can use -- to pass extra flags to mocha. For instance, the below commands are equivalent.

# This command...
npm test -- -g "login.*fails"
# is the same thing as this one
./node_modules/.bin/mocha -r nyan test/*.test.js -g "login.*fails"

An Alternative to Gulp

Gulp is a powerful streaming build system that lets you parallelize compiling your files. It's a great tool, but it can be overkill for some applications, especially if your team doesn't have much experience with Node.js streams. The npm run command can serve as a less-performant replacement for gulp in many use cases. For instance, suppose you have some ES2015 code in example.js that you want to transpile with babel and then pipe into browserify for use in the browser.

'use strict';

const co = require('co');

co(function*() {
  console.log('Hello, world!');
});

If you wanted to use gulp to compile this, you'd probably use the gulp-babel and gulp-browserify npm modules, which wrap babel and browserify for gulp. However, babel and browserify have command line interfaces, so you can compile this file using Unix-style pipes. Note that the below example requires babel 5.x, it won't work with babel 6.

./node_modules/.bin/browserify example.js | ./node_modules/.bin/babel > ./bin/example.js

Once again, the ./node_modules/.bin part is annoying. Thankfully, if you define a compile script in package.json, you can just achieve the same result with npm run compile.

{
  "scripts": {
    "compile": "browserify example.js | babel > ./bin/example.js"
  }
}

Closing Thoughts

The npm run command lets npm function as a versatile task runner in addition to a task manager. Good node.js applications leverage npm start and npm test to make it explicit how to run your application and how to test it. Also, npm run makes including executable npm modules (gulp, mocha, karma, etc.) as devDependencies more convenient. You can even leverage npm run and Unix streams to run your build processes without the help of a build system like gulp or grunt. Conceptually, npm run and the scripts section in package.json should define how to do the most common command-line tasks for your application, like starting the application, testing it, and running any transpilers.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus