NPM Needs a better solution for local development
Given the following layout of multiple modules in development:
- /my-repo
- /my-application
- package.json
- /my-lib
- package.json
- /my-application
And given my-application
depends on my-lib
:
If I’m making changes to both of these projects, or I wish to make changes to my-lib
and see the effect they have within my-application
, I currently have two supported options:
- Publishing tagged versions to the global repository, or
- Using
npm link
The first option works, but it is slow, requires constantly changing the tag name, consumes valuable community resources, and pollutes the global namespace if you’re not using private packages.
The second option (using a symlink) while it apparently works for most people most of the time, can produce significantly different results than what you would get by installing a “real” published dependency via the npm public repositories:
- The fileset is different, as all files located within the source tree are visible, not just those specified by
files:
,.gitignore
, and.npmignore
- More importantly, due to the node module resolution algorithm, the dependencies of
my-lib
will be wrong:- Concrete dependencies will be immune from npm’s resolution of version ranges and de-duplication
- Any
devDependencies
and locally-installedpeer-dependencies
will be present and resolved within the scope ofmy-lib
instead ofmy-app
.
The second problem can cause pretty serious difficulties, when it leads to duplicates of libraries that should not have multiple instances, such as react, and it also breaks TypeScript: Any @types/foo
dev-dependencies become effectively duplicated with different identities, causing a bunch of nonsensical Expected a FooBarBaz, but found a FooBarBaz
errors.
I’m not sure what the best solution to this problem is, but I have a proposal that might be acceptable:
- Add a flag to
npm publish
which performs all the steps of a publish, but then copies the artefact to a known location within the local filesystem’s npm cache - Add the ability to (within the scope of
my-application
) temporarily set the resolution of an installed dependency to use the local artefact as the source instead of fetching it from the cloud or using a direct symlink as currently used bynpm link
.
The idea to use a known location instead of relative paths to the artefact produced by npm pack
is so that it works for transitive dependencies also, in case your project is more complex than this simple example, perhaps you have several libraries and an application which all depend on a “common” or “base” library, and they might not all have the same relative paths to it.