I have a similar question to this, but not quite the same.
I would like for the user of my app to install it with whatever dependencies are needed for the way he would want to use it. So, for example, if they want to persist to MongoDB, then only Mongo-related libraries will be installed, but if they want to persist to Redis, then only Redis-related libraries will be installed. I don’t want to make them download and install libraries they won’t be using.
I know I can do that for development purposes with devDependencies
, but this goes farther than that. As the answer in the question above says, this is more closely related to Python’s setuptools
extras_require
and Clojure’s leiningen
profiles. Anything like that in npm? I really feel like devDependencies
should be a dev
profile of a more versatile way of specifying dependencies.
3
The codependency module may be what you’re looking for, or anything that does something similar to:
- declare optional dependencies in
package.json
that aren’t automatically installed bynpm install
, sayoptionalPeerDependencies
- a custom
require
-style function that knows aboutoptionalPeerDependencies
and does the right thing, including throwing/warning when nothing is found that fulfills a required class of modules (e.g. neitherredis
, normongo
, normysql
, etc. are installed). - document the expectation that consumers of this module install at least 1 of the optional peer modules
One variation would be if the module’s core functionality works without any optional dependencies (e.g. plugin pattern), no error/warning when nothing is found that fulfills a peer dependency.
Another variation is doing the list above while accounting for production versus development dependencies, i.e. an analog for dependencies
and devDependencies
.
Perhaps combined with an on-demand require such that optional modules are required lazily, e.g.:
exports = {
Core : require('./core'),
get redis(){ return require('./redis'); },
get mongo(){ return require('./mongo'); }
}
2
If you want simple optional dependencies like plugins, e.g. if you install foo you will run it colorful but if not installed, you don’t have any problem and see it in gray, then you could use optionalDependecies in the package.json:
{
"name": "watchit",
"version": "1.2.3",
"optionalDependencies": {
"foo": "^2.0.0"
}
}
And in the code:
try {
var foo = require('foo')
var fooVersion = require('foo/package.json').version
} catch (er) {
foo = null
}
if ( notGoodFooVersion(fooVersion) ) {
foo = null
}
// .. then later in your program ..
if (foo) {
foo.doFooThings()
}
Extracted from the package.json documentation.
1
What I do is configure an install script in my package.json, inside scripts
, like this:
"install": "node ./my-tools/my-install.js",
It will run right after npm install
finishes. I use it mostly for auto-generating a .env
file with defaults.
The my-install.js
script could run different commands, create files, ask for user input, so there you could say “Want Redis or Mongo?”:
const exec = require('child_process').exec;
const readline = require('readline');
// Insert "Ask question script" here
// using readline core module
if ( option == 'mongo' )
exec('npm install mongoose');
if ( option == 'redis' )
exec('npm install redis');
This is a very quick answer, check out readline for reading user input properly and child process for running commands and processing output, etc.
Also notice that the install script could be whatever you wish (python, bash, etc)
1
npm really wasn’t designed for this, as one of the hardest parts of dependency management is ensuring fast, reproducible builds that are easy and relatively failsafe. But I believe there is a use case, and there certainly was for me. So I wrote a package to do exactly what you are asking for.
My package is install-subset
, and can be installed globally with npm install -g install-subset
https://www.npmjs.com/package/install-subset
First, you build whitelists and blacklists for named install subsets in your package.json like this:
"subsets": {
"build": {
"whitelist": [
"babel-cli",
"dotenv"
]
},
"test": {
"blacklist": [
"eslint",
"lint-rules",
"prettier"
]
}
}
Then call it with, for example, install-subset test
This will temporarily rewrite your package.json to not install those packages blacklisted, then restore it, which depending on the packages can save a lot of time and bandwidth.
Also works with yarn, is open source and issues/PRs are welcome.
In many cases I use this on our ci server to lower build time, and on our latest React Native project, took our typical fresh developer install from 72 seconds to about 20 seconds.