Yes, it sure is… but no. Well, which is it!?!? Both. If you’re experimenting with a tool or just using it in general, feel free to use it as a global command line tool. If your project is actually dependent on the tool, then installing it globally probably isn’t the best way to go.
Why not? Well, just like pretty much every package on npm, the command line tools are versioned. If your local/development machine has a different version of the tool than another machine that needs to run that tool for that project, then there could be incompatibilities. It’d be nice if we had an option to save global dependencies inside of
package.json, but honestly a package file shouldn’t include global utilities, as discussed in a GitHub issue for npm.
Grunt did a good job of minimizing this issue by creating a separate npm package for the cli tool that is extremely minimal so you can keep it installed globally with minimal chances of incompatibility. There still are no guarantees, though.
npm is the alternative. Specifically, you should include your development tools in
package.json. That way they can be versioned for each project. But then how do you run it on the command line? Nobody wants to have to type in
node ./node_modules/.bin/grunt every time they want to run a Grunt command. While that would work for the majority of tools, there is a slightly simpler way: npm scripts. You can use
npm run WHATEVER to run any command you put into the
scripts section of your
package.json file, and if you reference a command that doesn’t exist on your computer’s PATH, then it’ll go searching through the node modules of your project for it.
For example, if you didn’t have the Grunt CLI installed globally, but had this in your
Now you can run
npm run grunt -- lint and it’ll run
grunt lint. This new feature with version 2 allows us to use
-- to pass arguments into the command that npm is running. You can also use this to guarantee that some options are always passed to your favorite commands. For example, you can make Grunt always run in verbose mode without the need to specify it each time:
You can run it the same way, but it’ll always be verbose now. This is a great way to make sure some options that you always want set are used without having to specify them every time.
I’ve run into a significant snag, though when doing this in Powershell on Windows. Assume we are still using the
"grunt": "grunt" script setup. If I try typing
npm run grunt -- -h in Powershell, then the
-h argument will actually be sent to
npm run instead of to the
grunt command. Any arguments you try to pass in that start with
-- will not be sent to the command in the
scripts configuration, but to the
npm run command. This doesn’t seem to be an issue with the standard cmd or Git Bash on Windows, nor does it look like Linux/Mac users are affected.
From countless comments I’ve read on the internet, it doesn’t sound like there are terribly many Powershell + npm users out there, but I very highly doubt that I’m the only one. In any case, this issue doesn’t come up all that often, since I tend to put all the commands I’m going to actually run into the
scripts, so I rarely need to pass arguments and I tend to end up with nice aliases that actually tends to make it so that
npm run COMMAND is shorter than the actual command it is running.
In fact, I’m looking around and trying to move beyond Grunt, Gulp, and the like, so I’m trying to use tools individually, instead of their task runner plugins (e.g. using the JSHint command line tool instead of the Grunt plugin). For more information, see “How to use npm as a Build Tool”, but I’m still experimenting with that and I’m certainly not trying to convince you of that just yet. This article is simply about removing the need for global npm packages.
There are certainly some pros and cons to this approach, so let’s take a look at some:
- It usually takes more keystrokes thanks to needing to throw
npm runin front of everything and needing to add
--in every time you want to throw some extra arguments in. Of course, this is negated when you use the scripts to convert long command names down to a short alias.
- It takes a bit of extra work up front to add the scripts into the
- You have an easy place to find documentation on what command should be run. Just typing
npm runby itself will show all the aliases and commands that are run for those aliases.
- Depending on how you set up the npm scripts, they can act as an abstraction layer so that if you want to switch from Grunt to Gulp or something similar, you can just change what commands are being run inside the
scriptsconfiguration and never have to change the commands you actually type into the command line.
- Obviously, one pro has to do with how I started this post: eliminating issues regarding version conflicts from global packages.
- Finally, when someone clones your project, it increases the likeliness that all they will need to do is run
npm installand they will actually get all of the dependencies installed.
If I missed any pros or cons let me know. In any case, what do you think? Is it worth the fuss to prevent global package version conflicts? If you say yes, then how much do we rely on npm scripts versus letting other tools, like task runners do the real work? These are certainly questions you’ll have to answer for yourself or discuss with your team and not something I can just tell you the “right” answer to. I’d still love to hear your thoughts on using npm scripts to any extent, so let me hear it! God bless and happy coding!