April 24, 2024
Building with NPM

Install NPM And Start Building Using NPM Scripts

 

While NPM is usually used for package installation with dependency management, we can also use it for building.

This might seem kind of odd, using a package management tool for building our JavaScript applications, but I think it has some great advantages:

  • Simplicity: there’s a lot less configuration.
  • Fewer dependencies: established build tools like grunt and gulp need wrappers for many popular libraries.
  • One less tool in your build process you need to worry about: chances are, you’re already using npm.

And it’s not like npm wasn’t designed for this: every default npm configuration has one build task by default:

    "test": "echo \"Error: no test specified\" && exit 1",

So we’re going to take full advantage of npm’s task-running abilities and learn:

  • How to install npm.
  • Where and how npm build tasks are configured.
  • How errors are handled in npm builds.
  • How you can set up a watch for tools that don’t support it; and,
  • When an npm build isn’t right for you

Let’s get started!

Building with NPMWhat you need to build with npm

You need npm, of course. If you’re doing JavaScript development, you probably have node and npm installed already. If you haven’t, you’re not going to get far without them.

To check, open a command prompt[1].

Enter:

> node -v

If you don’t have node installed this will cause an error. Read on to find out what to do about it (otherwise you can go straight to the next section).

The easiest way to install Node.js – and by extension npm – is to use one of the installers from the Node downloads page. This will make life a lot easier.

Linux users should use their system’s package installer. The Node.js website has excellent instructions for doing this.

As soon as you’re done with your installation, run node -v and npm –v to check everything’s working:

> node -v
v6.2.1
> npm -v
4.3.0
>

How to build with npm scripts

NPM build scripts

The scripts section of npm’s package.json is where all the action happens. This is where the test command is included in package.json by default.

The configuration of a scripts entry is pretty straight-forward:

"scripts": {
  "<command name>": "<command to execute>"
},

Where command to execute is anything that your command line can run.

Just to make the point – if we have the following in package.json:

"scripts": {
  "make-point": "echo \"Making the point.\""
},

And if we run that:

> npm run make-point

Then we get:

> npm-build@1.0.0 make-point /Users/luke/sync-default/npm-build
> echo "Making the point."
Making the point.
> 

Which is illustrative, but not very useful. A more useful example would be:

"scripts": {
  "build": "webpack"
},

Which I run like this:

> npm run build

To get:

> npm-build@1.0.0 build /Users/luke/sync-default/npm-build
> webpack

Hash: b6cf6e19a26c3c889f63
Version: webpack 2.2.1
Time: 57ms
    Asset     Size  Chunks             Chunk Names
bundle.js  2.57 kB       0  [emitted]  javascript
   [0] ./NpmBuilt.js 64 bytes {0} [built]

Because of the heavy lifting webpack can do for us, I think projects that use it (and browserify too!) are great candidates for npm builds.

Pre and Post hooks with npm

Chaining NPM commands

The neat thing about npm’s script commands is that we can automate tasks by prepending ‘pre’ or ‘post’ to them. So let’s make sure our tests always run before we build:

"scripts": {
  "test": "mocha",
  "prebuild": "npm test",
  "build": "webpack"
},

So that running:

> npm run build

Produces:

npm-build@1.0.0 prebuild /Users/luke/sync-default/npm-build
> npm test

> npm-build@1.0.0 test /Users/luke/sync-default/npm-build
> mocha

  nb
    ✓ should return 0
  1 passing (8ms)

> npm-build@1.0.0 build /Users/luke/sync-default/npm-build
> webpack

Hash: b6cf6e19a26c3c889f63
Version: webpack 2.2.1
Time: 57ms
    Asset     Size  Chunks             Chunk Names
bundle.js  2.57 kB       0  [emitted]  javascript
   [0] ./NpmBuilt.js 64 bytes {0} [built]

Ok, so that’s a bit more fun. Of course, we need to do more with our code than test it.

Here’s our next version of the scripts configuration in package.json:

  "scripts": {
    "lint": "jshint ./src",
    "test": "mocha",
    "pretest": "npm run lint",
    "prebuild": "npm ./test",
    "build": "webpack"
  },

So now we’ve added lint and pretest, so that running our test or build commands will automatically lint our code first.

But what about when things go wrong?

Dealing with errors in npm builds

NPM build errors

You want to know when things go wrong, right? What’s the use of deploying code that doesn’t pass your tests?

Npm helps us out here by making sure that any command from scripts that returns a non-zero value stops npm in its tracks.

For example, if I introduce a lint error and execute my build command, this is what I’ll see:

> npm run build

> npm-build@1.0.0 prebuild /Users/luke/sync-default/npm-build
> npm test

> npm-build@1.0.0 pretest /Users/luke/sync-default/npm-build
> npm run lint

> npm-build@1.0.0 lint /Users/luke/sync-default/npm-build
> jshint ./src

src/NpmBuilt.js: line 2, col 11, Missing semicolon.
1 error
npm ERR! Darwin 16.4.0

[... any many more errors after that.]

So the awesome part of this is that we can drop npm run <whatever makes sense to you> into a CI build tool like Jenkins, Travis CI et al and have 100% awareness when a commit breaks your teams project.

The frustrating part of this for developers is that the error output from npm is verbose and 90% is not related to the actual error!

Here’s a taste:

npm ERR! Failed at the npm-build@1.0.0 prebuild script 'npm test'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the npm-build package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     npm test

That’s about a tenth of the error output I get when my build fails. Npm even suggests I talk to the package author about the problem!

However, I prefer this to the alternative of running npm in silent mode that still provides a sensible error, but doesn’t tell you where it came from, e.g.

> npm run build --silent
src/NpmBuilt.js: line 2, col 11, Missing semicolon.
1 error
>

Clearly, this is npm amidst an identity crisis – package manager or build tool?

As useful and valid as it is to use npm for builds, this is the one major compromise I’ve come across. I live with the crazy error output.

How to watch for changes with npm

A final gotcha for building with npm is that it doesn’t natively support watching for changes to code.

One of the big advantages of the established build tools like Grunt and Gulp is that they provide a universal interface for watching code changes.

This means that even if a particular tool doesn’t support watching for changes, grunt or gulp would take care of it for you.

Luckily for us, watch support in npm is a package install away:

> npm install --save-dev watch

Ok, now we can use our brand new watch like this:

"watch ": "watch 'npm run command' --ignoreDirectoryPattern=dist",

Where command is the command you want to run every time the filesystem changes.

Take note of –ignoreDirectoryPatternFlag=/dist/

This tells the watch command to ignore changes in the dist directory.

If we didn’t there’d be an infinite loop where our build command is triggered by watch, puts new assets in the dist directory, which triggers watch… and so on.

If you use the watch package, you should instruct it to ignore any directory that’s modified by your build process.

Happily, the vast majority of tools that compile or bundle web resources have watch functionality of their own, and are smart enough not to spurred into action by their own side-effects.

Finally, are npm builds for everyone?

Personally, I’m a big fan of using NPM for building.

I’ve been around long enough to have dealt with the giant, crufty Make and ant.xml files that programmers were afraid to touch, and the sight of a couple hundred lines of build configuration gives me the shivers.

So I appreciate the simplicity and ‘obviousness’ of npm builds.

But it’s not for everyone, and here’s why:

  1. You need to be comfortable using the command line and making bash work for you. Redirecting output and chaining commands is common in npm builds.
  2. Related to the first point – you’ll need to learn how to run your pre-compilers, linters and bundlers from the command line. I like this myself, but it’s not for everyone.
  3. Windows users will find precious few usage examples if they’re using cmd.exe.
  4. I’d only use it on new projects. Unless they’re causing real problems, I’ve found that re-writing big build configs for another tool is rarely worth the effort. Refactoring in the existing tool would be a better payoff.

Having said all that, I hope you’ll be able to benefit from using npm for your builds in the future!


Notes:

  1. The Terminal application on macOS; I haven’t had a chance to try bash on Windows 10, but everything discussed here will work in cmd.exe.

Luke Fabish is a professional web developer addicted to learning and sharing knowledge that’ll help other developers. You can learn more at his personal website lukefabish.com.

Leave a Reply

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