Three tips when using NPM as a website build system
Daniel Ireson

Three tips when using NPM as a website build system

Sat Apr 29 2017

Node.js got some some honourable mentions in the Stack Overflow Developer Survey 2016. It’s used by 17% of the surveyed developers, and of those, 60% have a willingness to continue using it. If it were a programming language it would be the 10th most loved language. Its ease of use and extensible package manager make it a great candidate for a website build system. Utilizing npm scripts to create a website build system is not a new idea and there’s a variety of great tutorials online that can teach you how to do so. What I’d like to discuss are three things that work well for me when using npm as a build system.

Tip #1: move scripts into separate files

To keep your package.json clean I’d recommend moving your npm run scripts into separate JavaScript files to hide any complexities.

{
  "name": "npm build system",
  "scripts": {
    "build": "node scripts/build.js",
    "watch": "node scripts/watch.js",
    "serve": "node scripts/serve.js"
  },
}

You can use the npm-run-script npm package to run commands inside your JavaScript files as if they were being ran from npm scripts. This allows you to reuse any commands you’re currently using.

Tip #2: extract common functionality

To minimise duplication I like to create a helper file with common script functionality. An example of a build script using a helper file is shown below. We allow the build script to accept one argument, and depending on what is passed, different commands are ran. Running npm run build css will run the scss and autoprefixer commands. Note that the command definitions have been omitted for the sake of brevity.

const helper = require('./helper')

const scriptDefinitions = {
  "autoprefixer": "",
  "scss": "",
  "uglify": "",
  "imagemin": ""
}

let scripts = []
let arg = process.argv.slice(2)[0]

switch(arg) {
  case 'css':
    scripts.push('scss', 'autoprefixer')
    break
  case 'js':
    scripts.push('uglify')
    break
  case 'images':
    scripts.push('imagemin')
    break
}

helper.run(scripts, scriptDefinitions)

The helper file would look something like the following. I’m using the npm-run-script package mentioned above to run the commands.

const npmRunScript = require('npm-run-script')

module.exports.run = function(scripts, scriptDefinitions) {
  if (scripts.length > 0) {
    let command = getRunCommand(scripts, scriptDefinitions)
    let child = npmRunScript(command, { stdio: 'ignore' })
    child.once('error', error => handleError(error))
    child.once('exit', code => handleExit(code))
  }
}

function getRunCommand(scripts, scriptDefinitions) {
  let command = ''
  scripts.forEach((script, i) => {
    command += scriptDefinitions[script]
    if (i !== scripts.length - 1) {
      command += ' & '
    }
  })
  return command
}

function handleError(error) {
  console.error(error)
  process.exit(1)
}

function handleExit(code) {
  process.exit(code)
}

Tip #3: run scripts in parallel

Install the npm-run-all package so you can run your scripts in parallel (invoke with the -p flag). If you’re running lots of scripts where their completion times can be independent of each other (like when minifying css, js and html) this can speed up execution time drastically.

{
  "name": "npm build system",
  "scripts": {
    "build": "node scripts/build.js",
    "watch": "node scripts/watch.js",
    "serve": "node scripts/serve.js",
    "start": "npm-run-all -p 'serve' 'watch -- css' 'watch -- js' 'watch -- images' 'watch -- html'"
  },
}

Summary

If you’re building a small and simple brochure website Node.js makes a great candidate as an easy to use and extensible build system. In this article i’ve gone through three tips I use to keep code clean and maintainable. It isn’t for every use case, for more advanced websites you’ll probably be better suited going with something like Webpack, but the ease of setup means it’s great to start with.

The cover image for this post uses graphics from SAP Scenes.

Creating a word count webhook for GitHub using AWS Lambda

Creating a word count webhook for GitHub using AWS Lambda

How to export Medium posts to Markdown format

How to export Medium posts to Markdown format