Handle circular NPM package dependencies with Webpack

If you maintain your own set of JavaScript libraries and publish them on NPM, you might find yourself in a situation where a package A depends on a package B, which in turn depends on A itself. Although you should always strive to avoid such an ordeal, one can always imagine some edge case where this is a valid way of implementing things. The main problem is that it means each package will require a version of itself installed in node_modules which, on top of being semantically ugly, can make for really painful upgrades, as bumping the version of B in A and then releasing A should be followed by updating to this new version in B, and so forth... so what can we do to avoid this?

The quickest way to break the cycle is to remove A from B's dependencies (or the other way around, depending on what makes the most sense) and add it to its peerDependencies instead. It basically tells your package consumers that the latest compatible version of A should be installed alongside B for everything to work properly, without installing it directly: this transfers responsibility of maintaining the installed version of A onto them. You might also want to add A to B's devDependencies if its presence is necessary for tests to run properly, or anything else related to its maintenance. This should ease the pain within your dependency tree!

Finally, when developing A (which still explicitly depends on B), you might need to set things up so import calls targetting A resolve to your local code, the twin dependency not being around anymore. Doing so depends on your specific build setup, but it is pretty easy to do with Webpack thanks to the resolve part of its configuration:

{
resolve: {
// tell Webpack to look for third-party code in A's root directory before node_modules
modules: [__dirname, "node_modules"],

alias: {
"A": __dirname // tell it to resolve A to this same directory
}
},

// ...
}

This example assumes your entry point (typically index.js) lives in A's root directory; if it rather is is src/, for example, you will want to use path.resolve(__dirname, "src") instead.

Feel free to let me know of a better way to overcome such pitfalls when they cannot be avoided, if you know any!