Executing A Project-Specific Node/NPM Package A-la “bundle exec”
It’s no secret that I love NodeJS, though I really haven’t blogged about it much (if at all). Frankly, I think Node and NPM are going to be eating Ruby, RubyGems and Bundler’s $25,000 lunch – at least on OSX. But there’s at least one piece of bundler that I miss from NPM: the ability to say ‘bundle exec’ and have it run a specific gem’s bin file (binary / executable) for the current project gem configuration and version.
If I run “npm install express” from within a folder, the “express” package will be installed in to my ./node_modules folder.
But I can’t say “express foo” on the command line at this point, to run express. And I can’t say “npm express foo” or “npm exec express foo” or anything like that. This ability is sadly missing from NPM. There are a few ways to get around this, though.
When you install a Node module with the `-g` option, it installs in to the global module list. Global modules can have their bin files run from the command line just like any other command. So if you install the “express” module, for example, with the global flag then you’ll be able to say “express foo” from the command line and watch it build a fresh new project for you in the “foo” folder.
This is nice for packages that you want to have available for any application you’re building. I use global packages a lot because some of the tools that you can find in node packages are quite useful, even outside the context of a node project. But not every package should be installed in the global package repository for your computer.
Run Bin From Relative Path
You can also run bin files from node modules by using the relative path of ./node_modules/.bin/ to find the command.
This is a bit tedious, though it is functional. I’ve build a few simple command / shell scripts to execute some of my installed modules this way, for some of my projects. That makes it a bit easier overall, but you have to set up a shell script for each package you want.
Run Bin From Generic Shell Script
Since all of the bin files for all of the node packages are installed in to ./node_modules/.bin/ for a given project, why not build a generic “npm_exec” shell script and just forward all the parameters?
Just “chmod +x npm_exec” to make this file executable, and away we go.
Now I can run any arbitrary package binary I want. I could even put this little shell script in my /usr/local/bin folder and it would be available for all of my node projects, anywhere on my box.
This is a nice little solution, even if it’s a work around for something that should be built in to npm.
NPM Scripts In package.json
This last option is one that I’m using more and more often for project specific, recurring needs.
When you create an express app, it builds a package.json file for you. One of the pieces that it sticks in there is the “scripts” setting which contains a “start” setting, by default.
It turns out you can use these “scripts” from the npm command line. When you call “npm start”, npm will execute the “scripts”/”start” configuration for you. By default, this is just a call to “node app” which runs the app.js file in node and gets your express.js app up and running.
You can do more with “scripts”, too. You can add your own named script, in fact.
But you can’t just “npm foo”. The “start” script is recognized by npm explicitly. For other non-standard script names, you have to use the “run-script” command from node: npm run-script foo
The other thing that this does for us, is give us direct access to all of the bin files that our node packages have installed. So within a “script” configuration, we can call any arbitrary package’s bin file.
For example, if I want to use the node-supervisor package to restart my app whenever any files change, I can set up my package.json file to look like this:
This will install the “supervisor” package for development only, and set up the “start” script to run “supervisor app”. Now from the command line, I can’t run “supervisor app” directly:
But I can run “node start” and node will pick up the ./node_modules/.bin/ folder for me, allowing supervisor to be executed:
This works well for recurring / repetitive tasks within a project. But if you want ad-hoc package commands, you’re going to be in for a little more work and will likely have to fall back to one of the other solutions I’ve mentioned.
I really do miss the “bundle exec” feature of bundler. I honestly don’t know why npm doesn’t have an equivalent built in to it. Maybe it just needs someone to come along and contribute this feature. Maybe this feature was rejected. I don’t know. I haven’t looked in to it. But I wanted to get something in place now, so that I don’t have to install all of my project specific packages in the global package list.
There might be better option than the ones I’ve listed, as well. So, what am I missing? Is there something built in to npm to make this easier? Are there other ways that you’ve worked around this? Or am I going to stick with my “npm_exec” script and using npm “scripts” in my package.json fil?