Skip to content
This repository was archived by the owner on Aug 11, 2022. It is now read-only.
This repository was archived by the owner on Aug 11, 2022. It is now read-only.

Setup multiple local modules with npm link + dedupe #7742

@emmenko

Description

@emmenko

Hi everybody,

I'm trying to use npm link to develop multiple modules locally but it's not working as expected.
I'm not sure if it's actually possible with this setup or if I'm doing something wrong.
Hopefully someone can shred some light here ;)

Let's say I have this kind of structure.

.
├── app
│   └── main.js
├── modules
│   ├── a
│   │   ├── main.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   └── package.json
│   └── c
│       ├── main.js
│       └── package.json
└── package.json

The main app package.json has dependencies of all three modules

{
  "name": "app",
  "version": "0.0.0",
  "main": "./app/main.js",
  "dependencies": {
    "a": "file:./modules/a",
    "b": "file:./modules/b",
    "c": "file:./modules/c"
  }
}

The module a has underscore as only dependencies.

The module b has module a as dependency.

The module c has also module a as dependency.

// module `a`
{
  "name": "a",
  "version": "0.0.0",
  "main": "./main.js",
  "dependencies": {
    "underscore": "1.8.x"
  }
}

// module `b`
{
  "name": "b",
  "version": "0.0.0",
  "main": "./main.js",
  "dependencies": {
    "a": "file:../a"
  }
}

// module `c`
{
  "name": "c",
  "version": "0.0.0",
  "main": "./main.js",
  "dependencies": {
    "a": "file:../a"
  }
}

Scenarios

The easiest way

So if I install the app dependencies I get this

$ npm i
c@0.0.0 node_modules/c

b@0.0.0 node_modules/b

a@0.0.0 node_modules/a
└── underscore@1.8.2

$ tree
.
├── app
│   └── main.js
├── modules
│   ├── a
│   │   ├── main.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   └── package.json
│   └── c
│       ├── main.js
│       └── package.json
├── node_modules
│   ├── a
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── underscore
│   │   │       ├── LICENSE
│   │   │       ├── README.md
│   │   │       ├── package.json
│   │   │       ├── underscore-min.js
│   │   │       ├── underscore-min.map
│   │   │       └── underscore.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   └── package.json
│   └── c
│       ├── main.js
│       └── package.json
└── package.json

Now if I change something in one of the modules I have to re-install them. Running npm i doesn't do anything of course since npm doesn't see any change in the modules definition (e.g.: version bump).

So one thing we can do is rm -rf node_modules && npm i. This will always work but it may be slow depending on your dependency tree.

Funny thing, running npm i -f has a different output.

$ npm i -f
npm WARN using --force I sure hope you know what you are doing.

a@0.0.0 node_modules/a
└── underscore@1.8.2

b@0.0.0 node_modules/b
└── a@0.0.0 (underscore@1.8.2)

c@0.0.0 node_modules/c
└── a@0.0.0 (underscore@1.8.2)
emmenko: ~/dev/src/npm-link-test $ tree
.
├── app
│   └── main.js
├── modules
│   ├── a
│   │   ├── main.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   └── package.json
│   └── c
│       ├── main.js
│       └── package.json
├── node_modules
│   ├── a
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── underscore
│   │   │       ├── LICENSE
│   │   │       ├── README.md
│   │   │       ├── package.json
│   │   │       ├── underscore-min.js
│   │   │       ├── underscore-min.map
│   │   │       └── underscore.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── a
│   │   │       ├── main.js
│   │   │       ├── node_modules
│   │   │       │   └── underscore
│   │   │       │       ├── LICENSE
│   │   │       │       ├── README.md
│   │   │       │       ├── package.json
│   │   │       │       ├── underscore-min.js
│   │   │       │       ├── underscore-min.map
│   │   │       │       └── underscore.js
│   │   │       └── package.json
│   │   └── package.json
│   └── c
│       ├── main.js
│       ├── node_modules
│       │   └── a
│       │       ├── main.js
│       │       ├── node_modules
│       │       │   └── underscore
│       │       │       ├── LICENSE
│       │       │       ├── README.md
│       │       │       ├── package.json
│       │       │       ├── underscore-min.js
│       │       │       ├── underscore-min.map
│       │       │       └── underscore.js
│       │       └── package.json
│       └── package.json
└── package.json

You can see the nested (duplicate) dependencies in each module.

The symlink way

So npm has the ability to symlink a package folder which is particularly convenient for developing modules locally.
Let's try it then (I will remove first node_modules to have a clean start).

$ cd modules/a
$ npm link
underscore@1.8.2 node_modules/underscore
/usr/local/lib/node_modules/a -> /Users/emmenko/dev/src/npm-link-test/modules/a
$ cd ..
$ cd b
$ npm link
a@0.0.0 node_modules/a
└── underscore@1.8.2
/usr/local/lib/node_modules/b -> /Users/emmenko/dev/src/npm-link-test/modules/b
$ cd ..
$ cd c
$ npm link
a@0.0.0 node_modules/a
└── underscore@1.8.2
/usr/local/lib/node_modules/c -> /Users/emmenko/dev/src/npm-link-test/modules/c
$ cd ..
$ cd ..
$ npm link a
/Users/emmenko/dev/src/npm-link-test/node_modules/a -> /usr/local/lib/node_modules/a -> /Users/emmenko/dev/src/npm-link-test/modules/a
$ npm link b
/Users/emmenko/dev/src/npm-link-test/node_modules/b -> /usr/local/lib/node_modules/b -> /Users/emmenko/dev/src/npm-link-test/modules/b
$ npm link c
/Users/emmenko/dev/src/npm-link-test/node_modules/c -> /usr/local/lib/node_modules/c -> /Users/emmenko/dev/src/npm-link-test/modules/c
$ tree
.
├── app
│   └── main.js
├── modules
│   ├── a
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── underscore
│   │   │       ├── LICENSE
│   │   │       ├── README.md
│   │   │       ├── package.json
│   │   │       ├── underscore-min.js
│   │   │       ├── underscore-min.map
│   │   │       └── underscore.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── a
│   │   │       ├── main.js
│   │   │       ├── node_modules
│   │   │       │   └── underscore
│   │   │       │       ├── LICENSE
│   │   │       │       ├── README.md
│   │   │       │       ├── package.json
│   │   │       │       ├── underscore-min.js
│   │   │       │       ├── underscore-min.map
│   │   │       │       └── underscore.js
│   │   │       └── package.json
│   │   └── package.json
│   └── c
│       ├── main.js
│       ├── node_modules
│       │   └── a
│       │       ├── main.js
│       │       ├── node_modules
│       │       │   └── underscore
│       │       │       ├── LICENSE
│       │       │       ├── README.md
│       │       │       ├── package.json
│       │       │       ├── underscore-min.js
│       │       │       ├── underscore-min.map
│       │       │       └── underscore.js
│       │       └── package.json
│       └── package.json
├── node_modules
│   ├── a -> /usr/local/lib/node_modules/a
│   ├── b -> /usr/local/lib/node_modules/b
│   └── c -> /usr/local/lib/node_modules/c
└── package.json

So we can see the symlinks in node_modules, but we also see that each module has duplicated dependencies.

What we actually want is a to be a top level dependency (since is shared between modules) and most importantly that underscore is not duplicated between the other modules.

Again, npm (should) come to the rescue as it provides a way to reduce duplication via the dedupe command.

Searches the local package tree and attempts to simplify the overall structure by moving dependencies further up the tree, where they can be more effectively shared by multiple dependent packages.

Let's run it then and see what happens:

$ npm dedupe
$ tree
.
├── app
│   └── main.js
├── modules
│   ├── a
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── underscore
│   │   │       ├── LICENSE
│   │   │       ├── README.md
│   │   │       ├── package.json
│   │   │       ├── underscore-min.js
│   │   │       ├── underscore-min.map
│   │   │       └── underscore.js
│   │   └── package.json
│   ├── b
│   │   ├── main.js
│   │   ├── node_modules
│   │   │   └── a
│   │   │       ├── main.js
│   │   │       ├── node_modules
│   │   │       │   └── underscore
│   │   │       │       ├── LICENSE
│   │   │       │       ├── README.md
│   │   │       │       ├── package.json
│   │   │       │       ├── underscore-min.js
│   │   │       │       ├── underscore-min.map
│   │   │       │       └── underscore.js
│   │   │       └── package.json
│   │   └── package.json
│   └── c
│       ├── main.js
│       ├── node_modules
│       │   └── a
│       │       ├── main.js
│       │       ├── node_modules
│       │       │   └── underscore
│       │       │       ├── LICENSE
│       │       │       ├── README.md
│       │       │       ├── package.json
│       │       │       ├── underscore-min.js
│       │       │       ├── underscore-min.map
│       │       │       └── underscore.js
│       │       └── package.json
│       └── package.json
├── node_modules
│   ├── a -> /usr/local/lib/node_modules/a
│   ├── b -> /usr/local/lib/node_modules/b
│   └── c -> /usr/local/lib/node_modules/c
└── package.json

Hmm, nothing changed.

So is this how it is suppose to work? Does it actually work with local paths and symlinks?

$ npm -v
2.7.3

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions