LiveReload

LiveReload was another popular one. If you don’t know what LiveReload is – its a combination of command line tool and browser extension (or custom server) – as files change, LiveReload triggers the page you’re looking at to reload meaning you never have to press refresh. The npm package live-reload is a pretty suitable command line client for this – it runs a server which only serves a JS file, which if you include on your page will notify the page of changes. Simple, yet effective. Here’s an example of how to get it working:

"devDependencies": {
  "live-reload": "latest",
},
"scripts": {
  "livereload": "live-reload --port 9091 dist/",
}
<!-- In your HTML file -->
<script src="//localhost:9091"></script>

Now running npm run livereload – when you visit the HTML page it’ll start listening to the livereload server. Any changes to files in the dist/ directory will notifiy clients, and the page will be reloaded.

Running tasks that don’t come with binaries

It was pointed out to me that there are libs that don’t come with binaries – such as favicon – and so Grunt/Gulp plugins can be useful because they wrap the tools so they can be used within the task runners. If you find a package that you want to use, but it doesn’t have a binary then simply write some JavaScript! You would have to if using Grunt or Gulp, so don’t be afraid to just chuck a bit of JavaScript somewhere that wires it all up (or even better, submit a PR to the maintainers convincing them to support a command line interface!):

// scripts/favicon.js
var favicons = require('favicons');
var path = require('path');
favicons({
    source: path.resolve('../assets/images/logo.png'),
    dest: path.resolve('../dist/'),
});
"devDependencies": {
  "favicons": "latest",
},
"scripts": {
  "build:favicon": "node scripts/favicon.js",
}

A fairly complex config

In my previous post many were telling me I was missing the point about task runners – they’re for wiring up complex sets of tasks, not just running odd tasks. So I thought I’d wrap up this piece with a complex set of tasks typical of a multi-hundred-line Gruntfile. For this example I want to do the following:

  • Take my JS and lint, test & compile it into 1 versioned file (with a separate sourcemap) and upload it to S3
  • Compile Stylus into CSS, down to a single, versioned file (with separate sourcemap), upload it to S3
  • Add watchers for testing and compilation
  • Add a static file server to see my single page app in a web browser
  • Add livereload for CSS and JS
  • Have a task that combines all these files so I can type one command and spin up an environment
  • For bonus points, open a browser window automagically pointing to my website

I’ve chucked up a simple repository on GitHub called npm-scripts-example. It contains the layout for a basic website, and a package.json to fit the above tasks. The lines you’re probably interested in are:

  "scripts": {
    "clean": "rimraf dist/*",

    "prebuild": "npm run clean -s",
    "build": "npm run build:scripts -s && npm run build:styles -s && npm run build:markup -s",
    "build:scripts": "browserify -d assets/scripts/main.js -p [minifyify --compressPath . --map main.js.map --output dist/main.js.map] | hashmark -n dist/main.js -s -l 8 -m assets.json 'dist/{name}{hash}{ext}'",
    "build:styles": "stylus assets/styles/main.styl -m -o dist/ && hashmark -s -l 8 -m assets.json dist/main.css 'dist/{name}{hash}{ext}'",
    "build:markup": "jade assets/markup/index.jade --obj assets.json -o dist",

    "test": "karma start --singleRun",

    "watch": "parallelshell 'npm run watch:test -s' 'npm run watch:build -s'",
    "watch:test": "karma start",
    "watch:build": "nodemon -q -w assets/ --ext '.' --exec 'npm run build'",

    "open:prod": "opener http://example.com",
    "open:stage": "opener http://staging.example.internal",
    "open:dev": "opener http://localhost:9090",

    "deploy:prod": "s3-cli sync ./dist/ s3://example-com/prod-site/",
    "deploy:stage": "s3-cli sync ./dist/ s3://example-com/stage-site/",

    "serve": "http-server -p 9090 dist/",
    "live-reload": "live-reload --port 9091 dist/",

    "dev": "npm run open:dev -s & parallelshell 'npm run live-reload -s' 'npm run serve -s' 'npm run watch -s'"
  }

(If you’re wondering what the -s flag is, it just silences output from npm on those tasks, cleaning up the log output, try disabling them to see the difference)

To do the equivalent in Grunt, it’d take a Gruntfile of a few hundred lines, plus (my finger in the air estimate) around 10 extra dependencies. It’s certainly subjective as to which version would be more readable – and while npm is certainly not the holy grail of readability, I personally think the npm scripts directive is easier to reason about (i.e. I can see all tasks and what they do, at a glance).

Conclusion

Hopefully this article shows you how capable npm can be as a build tool. Hopefully it has demonstrated to you that tools like Gulp and Grunt should not always be the first thing to jump to in a project, and that tools you probably already have on your system are worth investigating.

As always, feel free to discuss this with me on The Twitter, I’m @keithamus, you can “Follow” me there too, apparently.