npm scripting: configs and arguments... and some more tricks

· August 24, 2015

I’ve written two posts on npm scripting with package.json and during the course of these post I’ve picked up some tricks that I didn’t really use in the posts.

This post will hence be a bit less structured by contain some small tidbits of information for you to, hopefully, enjoy and use.

I’ll be working of the code here and add all the features I describe (as far as possible) to that repository.

[UPDATED] This, and other posts on npm scripting, has drawn a lot of attention. I wanted you to know that I have created a PluralSight course on this topic, published in late October 2015. You can find it here.

Also, don’t miss the other posts on this blog on npm scripting:

If you liked this post I know you will love the course! Thank you for reading this

Here we go:

Chaining tasks… more options

In the last post I used && to call task one after another.

There’s more options just using normal command line functionality:

Piping result into the next command

I also “borrowed” from this post an example where you want to send the output from one task into the next one. Here’s that example again:

"build-js": "browserify -t reactify app/js/main.js | uglifyjs -mc > static/bundle.js"

By using the | you can take the result of one task and pass it on to the next. The output of the browserify command is a bundled file with all the code from all the required modules into one file (the -t reactify is simply packing up .jsx React stuff).

That file is then passed to the uglifyjs (you front-end guys and your names) that minifies the file and puts it into the static/bundle.js.

Now all of that is packed into the build-js command.

Running in parallel

We will see later, under Watching, we will have the need to start more than one thing simultaneous, running tasks in parallel.

This can be accomplished, also using normal “linux” commands, with the & switch or what you call it.

Here we start our node server and a live reload functionality for our browser at the same time. Meaning that we something changes on the server nodemon will restart the server for us. If something changes on the client, our browser(s), will be reloaded.

"scripts": {
	// other scripts
	"watch:server"	: "nodemon --harmony app.js",
	"watch:client"	: "live-reload --port 9091 ./",
	"watch" 		: "npm run watch:server & npm run watch:client"
}

This will fire up both the npm run watch:server and npm run watch:client at the same time, concurrent. Now when I make a change on the server Nodemon will reload the server.

The client will be reloaded with the help of live-reload that simply is a server listening on port 9091, our case. Should the ./ directory be changed in any way the browsers open will be reloaded. This requires that you include a simple script-tag on your page:

	<script src="//localhost:9091"></script>
	// Note the matching port number 9091, to the live-reload command

Calling remote scripts

As you probably can see scripting in the package.json file can only get you so far. No sweat though, if needed you can always call out to a bash or command file:

"scripts": {
	"deploy:complex" : "./longdeploy.sh"
}

Now you are free to write the script how you want. This will of course require that you have permissions to execute that script.

Also it hides some of the functionality of the script. So I would steer away from this as much as possible.

External arguments, options etc.

Speaking of breaking out to separate files and contradicting myself a bit sometimes all the options and their parameters might get out of hand. An example where this is likely to happen would be for a linting task, that potentially could have a lot of parameters.

Options in separate files

You can, just as at the command prompt, run this command with all options in a separate file. Here’s two versions of a linting task; one with options in-line and one in a .jslint options file:

"lint:optionsfile" : "jslint index.js",
"lint:inlineoptions" : "jslint --evil --indent 2 --vars --passfail false --plusplus false index.js"

Yeah… I stand corrected. Sometimes it might be better to externalize the details of a script.

Passing through command line argument

Speaking of parameters and arguments to a command. There’s a feature of npm that I didn’t know of until a couple of days ago; if you pass -- (there’s a space after the --, right there) you can “pass argument through” to the underlying command.

This can be really handy to create versions of a script without having to rewrite it over and over. Let’s say that our application accepts the port number to start it on as an argument; node app.js 3456, or the port set in the ENV defaulting to 3000 for example. Sounds complicated but here it is, for a Koa application:

app.listen(process.env.PORT || (process.argv[2] || 3000));

We could now create a few scripts like this:

"scripts": {
	"start"			: "node --harmony app.js",	// No argument - start with 3000
    "start:test"	: "npm start -- 4000",		// Start on port 4000 in testing
    "start:stage"	: "npm start -- 5000"		// Start on port 5000 in staging
}

See how we can reuse the original start-script by simply passing the port number through. Nice!

This could of course be named arguments too: npm test -- reporter:spec for example.

npm configuration

npm also supports a config object. This is yet another way to set parameters for your scripts.

Simply define the values in a config node in package.json like this:

"name"	 : "myapp",
"config" : { "port" : "3000" }

This can now be used in your JavaScript code like this:

console.log("Running on port: " + process.env.npm_package_config_port)

But also in your npm scripts, like this:

"name"	 : "myapp",
"config" : { "port" : "3000" },
"scripts": {
	"start"	: "node --harmony app.js $npm_package_config_port"
}

If you’re like me you probably just went: “Eeeeh…? SUCKS!? Now I have to change stuff in the package.json file if I wanna change the parameter”. But I missed a important tidbit of information. The value of port, or any other config value can be overridden at the command prompt:

npm config set myapp:port 80

It can also be overridden by other scripts:

"name"	 : "myapp",
"config" : { "port" : "3000" },
"scripts": {
	"start"			: "node --harmony app.js $npm_package_config_port",
    "start:test"	: "node --harmony app.js --myapp:port=4000",
    "start:test"	: "node --harmony app.js --myapp:port=5000"
}

Pretty nice, and yet another option to use.

Watching and reloading

Well… you can read about this in the section above on running in parallel. That describes one way to get watching and reloading of browsers. There’s a number of different ways to do this, of course.

npm niftyness

There’s some small things with the npm command that is easily missed and that can prove useful.

-s to silence it down

Any parameter you pass to npm at the command prompt is used for that entire command. For example -s turns logging more or less off (-d is more logging, and -ddd is silly logging, try it!), that can be useful to tweak.

What I found interesting is that this is passed on to npm scripts. So for our mega-build-script-calling-into-other-scripts thing we built before:

"deploy:prod": "npm run test && npm run version:patch && npm run push && npm run launch",

we can simply turn logging up or down by going npm run deploy:prod -ddd or npm run deploy:prod -s.

This can prove very useful as a setting to tweak in your build server for example.

npm run

Just giving the command npm run will list all scripts in your package.json. That in itself can be useful as documentation.

npm completion

You can enable tab-completion in npm for all commands and even the scripts in the package.json too. It’s a little bit weird I think but here’s how it works.

Calling npm completion will create a .sh file that enables the tab completion for the package.json in the current directory. For example npm completion >> myAppTabCompletion.sh will create a file called myAppTabCompletion.sh with the necessary code to get tab-completion to work.

But it doesn’t work. Until you load it into the current shell. I’m not good enough in Linuxy things to know how to do that. But here’s one way, and a tweak;

Instead of creating a separate file, like we did above, we can send it to ~/.bashrc which is a file that is run as you start a new terminal window. Like this:

Now the tab completion, for myApp mind you, will be enabled in all new terminal windows (so start a new one to try it).

npm completion >> ~/.bashrc

That works (and is pretty cool to get tab completion on our scripts), but should you want to add another app it might get unwieldy. For the life of me I cannot understand how to get around it. If I understand it correctly you can, at the end of ~/.bashrc manually run the myAppTabCompletion.sh. But I can’t get it to work.

So I only got half-way there. Sorry.

List binaries for scripting

Something that does work and that is a great help, especially during script-development, is to list all the binaries that your packages exposes.

When we install a package with a binary that you can start from the command line, such as nodemon it’s added to the ./node_modules/.bin folder. By simply listing that folder we can easily see all the commands we can use:

$ ls ./node_modules/.bin/
_mocha		jslint		nodemon
cake		live-reload	tsc
coffee		mocha		tsserver

This also means that we can use these command straight off without prefixing them with ./node_modules/nodemon/bin/nodemon.js as I have done before.

Thanks Juho Vepsäläinen for this tip!

Summary

I love blogging. Because I learn so much. And I get some nice feedback and learn more.

I hope that you could pick up some new and useful things here too.

Twitter, Facebook