Npm as a build tool

Npm

It's normal to use Gulp, Grunt, Broccoli or others as a front-end build tool. I tend to use it, but sometimes I think: It's necessary to add another dependency in your project? I read about npm scripts and I start using them. Here is an introduction to check the benefits of moving all the task into your package.json file.

Having your scripts in your package.json is much easier to you to maintain your code.

Getting started

Firstly, it's important to learn how npm works with scripts. The npm run-script (or npm run) command with the name of the script, for example npm run test searches the object test in the scripts parameter of the package.json file.

Lets see an example. First of all, create a package.json file with this content:

{
  "name": "example",
  "devDependencies": {
    "jshint": "latest"
  },
  "scripts": {
    "lint": "jshint **.js"
  }
}

As I said before, the npm run [script-name] executes the script in the [script-name] label. In the example above, if you run npm run lint you executes the jslinter of all of your Javascript files.

Shortcut scripts
Npm provides some shortcuts for scripts we should use more than others. Here is a list of that:

npm test
npm start
npm stop

The main reason is to provide a standard interface to use this scripts. You very usually use test, start a server or stop it. So you can use this commands as standard provided by npm.

Config variables
Also, npm has another object which we can use in our scripts named config. The config object is to pass configuration variables which we can pick up in our scripts explained above.
For example, take a look at this package.json file:

{
  "name": "example",
  "devDependencies": {
    "mocha": "latest"
  },
  config: {
    "reporter": "xunit"
  },
  "scripts": {
    "test": "mocha test/ --reporter $npm_package_config_reporter"
  }
}

All of the config variables are available in the npm_package_config_[name-of-config-variable] variable. I think it is not the best solution, but it works and maybe you can find it useful.

Manage multiple files
Also you can use "globs" to select multiple files like *.js

"devDependencies": {
  "jshint": "latest"
},
"scripts": {
  "lint": "jshint *.js"
}

Running multiple tasks
Tools like Grunt and Gulp have the capability to manage multiple tasks. npm gives us the && operator to execute multiple tasks in one script.
For example:

"devDependencies": {
  "stylus": "latest",
  "browserify": "latest"
},
"scripts": {
  "build:css": "stylus assets/styles/main.styl > dist/main.css",
  "build:js": "browserify assets/scripts/main.js > dist/main.js",
  "build": "npm run build:css && npm run build:js",
}

Streaming multiple tasks
Like Gulp, with npm we can stream multiple tasks using the | operator. For example:

"devDependencies": {
  "autoprefixer": "latest",
  "cssmin": "latest"
},
"scripts": {
  "build:css": "autoprefixer -b 'last 2 versions' < assets/styles/main.css | cssmin > dist/main.css"
}

Cleaning directories
In some cases, you need to remove an entire directory, for example when you are running a task to build a production app. The command rm -rf dist/* works in npm, for example:

"scripts": {
  "clean": "rm -r dist/*"
}

The problem is Windows, which does not support rm. Luckily, there is a package named rimraf, which has multiple OS support:

"devDependencies": {
  "rimraf": "latest"
},
"scripts": {
  "clean": "rimraf dist"
}

Watch
This is one of the main reasons people use Grunt/Gulp. Is very useful to watch files, detect when it changes and execute tasks to re-compile or do something. Of course is very useful to quickly development.
Lot of packages gives the watch option in their scripts. For example Mocha has the -w option to see the file changes, as Node-sass and others. Browserity has the watchify package also.
However, this feature is not present in all packages, so we need to have a custom watcher to execute task when a file, or an entire directory, changes.

To solve this problem, we need to have tasks to compile all of your assets. Probably you need to compile your HTML if you are using Jade, or compile your styl files with Stylus, or compile your Javascript modules with Browserify. Take a look at this example:

"devDependencies": {
  "stylus": "latest",
  "jade": "latest",
  "browserify": "latest",
  "watch": "latest",
},
"scripts": {
  "build:js": "browserify assets/scripts/main.js > dist/main.js",
  "build:css": "stylus assets/styles/main.styl > dist/main.css",
  "build:html": "jade assets/html/index.jade > dist/index.html",
  "build": "npm run build:js && npm run build:css && npm run build:html",
  "build:watch": "watch 'npm run build' .",
}

If you need to optimize this feature, there ir a package named Parallelshell, which will keep multiple process at one time. For example:

"devDependencies": {
  "stylus": "latest",
  "jade": "latest",
  "browserify": "latest",
  "watch": "latest",
  "parallelshell": "latest"
},
"scripts": {
  "build:js": "browserify assets/scripts/main.js > dist/main.js",
  "watch:js": "watch 'npm run build:js' assets/scripts/",
  "build:css": "stylus assets/styles/main.styl > dist/main.css",
  "watch:css": "watch 'npm run build:css' assets/styles/",
  "build:html": "jade index.jade > dist/index.html",
  "watch:html": "watch 'npm run build:html' assets/html",
  "build": "npm run build:js && npm run build:css && npm run build:html",
  "build:watch": "parallelshell 'npm run watch:js' 'npm run watch:css' 'npm run watch:html'",
}

If you run npm run build:watch, the watchers will run individually. This results in a better compilation. For example, if you change a JS file, only executes the build:js task, not all of this.

Livereload
Livereload is another popular one. The npm package live-reload is a pretty client which provides all you need.

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

If you run <strong>npm run livereload</strong> and visit the HTML page, any changes in the dist folder will notify serves and reload the page.