I’m sure most people coming to node.js immediately go looking for the equivalent of gems. They’ll soon find npm and discover that it does exactly what they expected (with a couple of tweaks along the way to avoid running absolutely everything as root). However, the chances are, there’s a simpler alternative they haven’t considered yet.
If you’re a C# developer, you should be familiar with the term “DLL hell”. It’s important to understand that gems do, in fact, suffer from versioning issues. They don’t bug out at the first sign of trouble like .NET assemblies do, but any centralized repository can and does suffer from this problem. Ruby Gems does have a way of addressing this, but it doesn’t work because people don’t use it. To think of this another way, when was the last time you recommended an assembly be put in the GAC?
What’s worse, you probably haven’t figured out yet how you’re going to get your code deployed to live. I suggest you do so now rather than waiting for the inevitable surprise.
How Require Works
First, we need to understand how “require” actually works. Require works through the search path, which includes anything in the environment variable “NODE_PATH”. It will match the following:
- anything with that exact path name
- anything with that path name plus “.js”
- anything with that path name plus “/index.js”
- a native extension
99% of the time you’re looking at one of the first three. Once require has found the correct file, it runs the file and populates the variable “exports” (aka module.exports), which is the return value of “require”.
The last is a bit more complex.
What NPM does
Now, NPM by definition can do what it likes, up to and including deleting everything on your hard drive. But what do most packages actually do? Well, typically they create an index.js file and download the files. There’s complex packages like expresso that are more work, but 99% of the time they’re doing very little at all.
So, instead of installing an npm module and messing around with a global repository, why not keep it simple and download the code yourself? You just type “git submodule add https://github.com/brianc/node-postgres.git” and “require ‘./node-postgres/lib’ (assuming you’ve got your NODE_PATH set correctly). This is just as easy as using NPM in the vast majority of cases (quite a few projects actually include an index.js file in the root of their repository to make this even simpler). You can now do the following:
- Keep an exact version of the package that works versioned with your main repository.
- Switch between released versions easily
- Maintain a branch with quick fixes to bugs you find (this will happen more often than you’d hope)
- Deploy easily
This is incredibly easy to do providing the NPM package isn’t complex, which 99% of them aren’t. Native extensions are more tricky to do this way because you need to automate their build system. However the benefits are just as great.
One thought on “NPM: There is no Spoon”
Instead of not using a dependency manager because of global state, how about using one without global state? zero install works cross-platform and for libraries written in any language (even native libraries). It doesn’t have a global repository of "installed stuff", but instead resolves the set of dependencies that your project requires, and puts it on your $NODE_PATH (for the example of node libraries). http://0install.netThere’s a lot of work to do to get more things packaged for it, but every library that you publish as a 0install feed helps.