npm link, peerDependencies and webpack

Alasdair McLeay
Dec 1, 2017 · 4 min read

If you are developing a node library and using it via npm link, you can run in to issues with duplicate dependencies.

Consider 3 node packages: my-app, my-lib and third-party-lib.

my-app has a dependency on my-lib. my-lib is added to my-app via npm link.

my-lib has third-party-lib listed in:

  • devDependencies, because when developing my-lib it is needed for tests and examples.
  • peerDependencies, because it expects my-app to already include it as a dependency.

When a developer works on my-lib, they install devDependencies.

When running my-app to test against my-lib, the developer uses npm link.

The dependency file tree now looks like this:

├─┬ my-app
│ └─┬ node_modules
│ └─┬── my-lib(symlink)
│ └── third-party-lib
└─┬ my-lib
└─┬ node_modules
└── third-party-lib

node dependency resolution means that my-lib uses a different copy of third-party-lib to the one used by my-app.

This is a problem if:

  • my-app generates a bundle (e.g. using webpack) and you want to avoid duplication to reduce file size.
  • third-party-lib expects a single instance (e.g. React).

Similarly, if you don’t install devDependencies on my-lib:

├─┬ my-app
│ └─┬ node_modules
│ ├── my-lib(symlink)
│ └── third-party-lib
└── my-lib

my-lib will no longer be able to resolve third-party-lib under this tree and webpack will complain when bundling.


Node has a --preserve-symlinks option, so that at runtime it treats my-lib as a subfolder of my-app/node_modules rather than it’s actual file path, though in the commit message this feature is listed as being a temporary solution:

This should be considered to be a temporary solution until we figure out how to solve the symlinked peer dependency problem in a more general way that does not break everything else

If you run node using the --preserve-symlinks flag (or webpack with resolve.symlinks set to false) then the file tree instead looks like this:

└─┬ my-app
└─┬ node_modules
├─┬ my-lib(symlink)
│ └─┬ node_modules
│ └── third-party-lib
└── third-party-lib

If you don’t install devDependencies in my-lib under this tree then third-party-lib will be correctly resolved, but you get the same duplication issue when devDependencies are installed. Running npm dedupe before building seems to be a possible solution for this, but unfortunately it does not process linked modules. In addition, you may not want to remove the linked package’s devDependencies if you want to develop both packages at the same time.

So, although preserve-symlinks seems to have been created to resolve issues like this, it isn’t foolproof for this scenario so and we need to find a different solution.


Lerna resolves this with hoisting, but this solution is specific to a monorepo.

Webpack solution

The solution I’m using for webpack, for non monorepos, consists of 2 steps:

  1. in the webpack config for my-app, add webpack aliases for known duplicated packages so that they always resolve to the my-app node_modules folder.
  2. add the duplicate-package-checker webpack plugin to warn or error if a package is duplicated in future.

Webpack aliases

resolve: {
alias: {
react: path.resolve('./node_modules/react'),
'react-dom': path.resolve('./node_modules/react-dom')


const mapToFolder = (dependencies, folder) =>
dependencies.reduce((acc, dependency) => {
return {
[dependency]: path.resolve(`${folder}/${dependency}`),
}, {});
...resolve: {
alias: {
...mapToFolder(['react', 'react-dom'], './node_modules')

Duplicate Package Checker

I feel that just adding webpack aliases manually is a bit hacky as if more dependencies are added in future then they could easily be duplicated in the build without the developer realising. To manage this, we can add a check in the webpack build to ensure there are no duplicate packages.


If multiple versions of a depencency are used by several other dependencies then adding the webpack alias will break npm’s semver management.

As noted by duplicate-package-checker:

Note: Aliasing packages with different major versions may break your app. Use only if you’re sure that all required versions are compatible, at least in the context of your app

Update November 2018: It looks as though a solution may be close, in the form of Yarn PnP —


8 February 2013 — npm introduces peerDependencies.

15 April 2014 — npm#5080 npm link and peerDependencies issues raised,

4 August 2014 — npm#5875 again,

25 March 2015 — npm#7742 and again.

10 April 2015 — webpack#966, issue relating to npm link.

3 May 2016 — preserve-symlinks flag added to node.

7 August 2016 — create-react-app#393 React development issues.

7 September 2016 — resolve.symlinks added to webpack/enhanced-resolve.

18 September 2016 — create-react-app#675 issues relating to multiple instances of React summarised on create-react-app.

28 November 2016 — create-react-app#1107 npm link workflow.

25 January 2017 — lerna#529 hoisting as a monorepo solution.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store