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:
- npm scripting: git, version and deploy
- npm scripting: configs and arguments… and some more tricks
- Pre and Post hooks for 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.