A case for using npm3 instead of npm2 for library development

A while ago, I created a library in NodeJS to ease server- and client-side rendering. It uses React and React Router to render the appropriate HTML on the website. While rendering HTML on the server and client with React is easy, various specific issues normally arise during development, like how to send state to the front-end or how to inject React to the website. The purpose of my library is to solve them.

Project Structure

All the npm modules my library requires to work properly need to be downloaded first using npm and are later combined with source code written for a specific project into a single JavaScript file used by the web browser. The library uses Browserify for combining JavaScript files on its own. On the server, NodeJS is able to read CommonJS modules directly so no such process is required.

The hierarchy of dependencies for projects using the library will look roughly like this most of the time:

project:
-- react (for creating React components)
-- my library:
---- react (for rendering React components)

Notice that React is present twice. My library needs to use React, specifically its renderToString method for server-side rendering. Also, when running Browserify, it dynamically replaces require(‘moduleX’) calls with references to custom modules created for that specific project if needed. Thanks to this approach, the library is easy to run with sensible defaults and configurable in a variety of ways if needed. But there’s also a downside to this approach. If Browserify and React are used by the project directly the client-side code will end up having two separate versions of React. What is worse though, React will be loaded twice and start working incorrectly. It’s not an easy situation to detect, as React won’t warn about it happening. Instead, it will just spit out errors where it shouldn’t. For example, in my test projects the this.props object was missing some properties that normally would’ve been injected into it.

The issue described above relates to something more general about older versions of npm — they create hierarchies of node_modules folders. So the project gets one node_modules, my library gets another and all its related modules get node_modules if they need it. So simply putting React in package.json in the main project and my library will create a duplicate React download and cause problems but they both need to have it. Browserify isn’t to blame because it can’t easily understand that one specific folder with React should be referenced.

The Solution

This is where peerDepencencies comes into play. Its format is the same as for dependencies or devDependencies keys inside package.json. But if peerDependencies is put inside of package.json for my library and I do a npm install in the project’s directory, React will only be added to one node_modules folder. And if I’m in the process of developing the library separately from the project, I can npm install peerDepencies in this case and get React this way with a bit more work.

Now onto why npm3 is better in this case. Npm3 flattens the hierarchy completely. Doing npm install in npm 3 creates a single node_modules folder where the closest package.json was found. On one side, having a flat hierarchy will dump every dependency into a single folder, which is more difficult to manage during development. On the other:

  • Windows can now handle complicated package.json files. In npm 1 & 2, doing npm install on Windows created a long folder hierarchy that the system was not able to handle properly. Developers had to use something like rimraf to delete it because the operating system was unable to.
  • peerDependencies is no longer needed. npm3 warns about missing peerDependencies but won’t install them with the plain old npm install anymore. The flat folder hierarchy effectively removes the problem I had with having two React modules in two separate node_modules folders. Many other libraries had this problem as well due to how they worked.
  • There’s no duplication of libraries. For example, even if multiple libraries depend on React, it will only exist in a single node_modules folder, unless explicitly downloaded into many by a developer.
  • I’ve always had problems with explaining how peerDependencies works to developers, because it tried to solve a problem specific to how some libraries need to work.

Summary

Use npm3, especially if you’re developing node modules. I’m glad peerDependencies is being phased out, I won’t miss it. While always installing the latest version of npm might seem like a no-brainer and in this case solved my problem better, knowing why the issue I stumbled upon happened in the first place and finding ways to solve it gave me a better understanding of dependency management.

This post was inspired by https://codingwithspike.wordpress.com/2016/01/21/dealing-with-the-deprecation-of-peerdependencies-in-npm-3/. Jeff explains a different issue that peerDependencies solves, worth a read.