When we released the
strongloop API Server package last week, we worked hard on making the installation simple and repeatable. One of the neat ways we packaged our tooling was using peer-dependencies. Or so we thought! Turns out it was not that clever after all. In order to create a simple API we installed with
npm install -g strongloop, which in turn installed
grunt, and the
loopback yeoman generator. This gave us simplicity because yeoman created the scaffolding for
loopback, however it resulted in a morass of peer-dependency issues. The solution did not work for all cases. In particular:
- npm doesn’t have a mechanism to update peer dependencies
- there are conflicts of interest related to versions
- post-install scripts get in the way.
Some of these issues you’ve reported caused us to re-think the approach and we iterated quickly to release this next version of
In particular, today’s release of
strongloop should resolve many of the issues customers have had with installing the
strongloop package by removing the need for
npm can now update
slc and all its dependencies, and only a single global package is installed,
strongloop, which provides the
slc command line tool.
This blog shares a bit of the reasoning behind our original approach and explains how we modified it to work better.
How it began
When we first started building the StrongLoop CLI (
slc) last year, we designed it to be modular. It was loosely based on how the ‘git’ command is designed with sub-commands split out into their own repositories. It was a cleaner approach to code maintenance, and helped maintain our lego-like, composable approach to our tooling.
Enter peer dependencies
Looking around at the Node ecosystem, the idea of peer-dependencies seemed like a perfect fit. For a while the details of how peer-dependencies worked was not well documented. Fortunately, you can now find details in the npm documentation.
The intended use of peer-dependencies, as described in the peer dependency blog, is to make dependency hell explicit, so incompatible versions of modules are reported as incompatible. A side-effect is to enable easy installation of sets of global packages. For example, the fact that peer-dependencies are automatically installed if needed, is used by many yeoman plugins to ensure that the Yeoman CLI is also made available when the generator is installed. Yeoman uses it to ensure that the grunt-cli command is installed globally. If you have a collection of tools, that can be installed independently into your path, but that you’d like to bundle together into a tool suite, it seems peer dependencies might be for you.
The problem with global installs and peer dependency
Unfortunately, global installs and peer dependency usage quickly runs into a critical issue: they are installed if absent, but never updated if already present. So, for first use, it works out great, for example, installing yo also installs grunt – but grunt will never be updated thereafter! The only way around is to require a
npm install -g grunt-cli at post-install time. (Note that global npm installs, unlike local, will update if the global is there already).
So, we thought, just create a post-install script that will update the dependencies. Right? Well…not quite. The post-install script will work great on a normal dev box where the developer has sensibly installed node. Meaning, in a way that they don’t need to use sudo. But as soon as you use sudo, you realize that npm drops root permissions before running scripts, and it is no longer possible to install or upgrade npm packages in npm scripts!
Next, we had to write our own wrapper around npm install (
slc update). At this point, like it or not, we were steadily walking down the path of writing our own install tool alternative to npm. Yeoman did this, it uses its menu system to allow you to search for generators, to see what are installed, to update any that are out of date. It’s certainly nice, but replacing the install interface of npm is a lot of work, and not exactly on our roadmap.
So, is there another option?
In the case of the
slc command line interface, there is: use direct dependencies. Most
slc sub-commands are implemented as separate modules which were originally installed as peer-dependencies. We executed them using
child_process.spawn(), which required them to be in the PATH. With direct dependencies, any bin scripts exposed by the dependency are not in the PATH, which makes them a bit harder to execute. However, it is possible to use
require.resolve() to locate the exact location of the script. This is very robust. Npm doesn’t guarantee much about where it decides to put modules it installs, but what it absolutely does guarantee is that after a dependency is installed, it will be possible to
require() it from Node. So, ask Node where it will require the direct dependency from, and spawn that exact path, and you now have a way to run your direct dependencies!
This has a side-effect of course. Earlier, using peer dependencies, your peer dependency was also available globally, and directly. You could side-step
slc debug and use node-debug. This is no longer possible with the
strongloop npm package. If you do want to use
generator-loopback directly without
slc, you still can: install it yourself, and be responsible for upgrading it yourself. However, if you use
strongloop, when we publish an update to npmjs.org, and you install it, it will come with all of its dependencies, guaranteed up-to-date by the npm well-trodden direct npm dependency mechanism (not peer dependencies).
To transition to this, however, you should probably uninstall all the old
slc peer dependencies, or at least, any you don’t want to maintain and update yourself. At the very least, to get the latest rev of
strongloop, please uninstall the following:
$ npm uninstall -g strong-cli
$ npm uninstall -g loopback-sdk-angular-cli
strongloop by running
npm install -g strongloop To read further on how to use Strongloop and its tooling, see Installing StrongLoop software.
- What’s in the upcoming Node v0.12 release? Big performance optimizations, read Ben Noordhuis’ blog to learn more.
- Ready to develop APIs in Node.js and get them connected to your data? Check out the Node.js LoopBack framework. We’ve made it easy to get started either locally or on your favorite cloud, with a simple npm install.
- Need training and certification for Node? Learn more about both the private and open options StrongLoop offers.