37

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.

imiric
  • 481
  • 1
  • 4
  • 6
  • Just a thought but you could go with multiple packages. `MyPackage-Core` `MyPackage-Db-Mongo` `MyPackage-Db-Redis` etc... much they way people do bower modules that are meant to [extend angularjs](http://ngmodules.org/). – Mike May 07 '14 at 15:25
  • @Mike: Hmm thanks, I'll consider it. I still think this is a limitation of `package.json` that has been solved in other package managers. – imiric May 07 '14 at 15:47
  • 1
    This is a great question, but I think it's off-topic because it is about using some tool. Such questions are on topic only if they cover how the tool integrates into some development *process* – after all, this site is about Software Engineering. See our [help/on-topic] for details. Please read: [Where does my tool question go?](https://softwareengineering.meta.stackexchange.com/q/7253) Usage of development tools such as NPM would be on topic on Stack Overflow. – amon Apr 04 '18 at 20:26

4 Answers4

15

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.

PhoneixS
  • 323
  • 3
  • 9
  • 2
    npm will install optional dependencies by default so this doesn't help OP's original use-case. – villasv Sep 15 '20 at 15:18
14

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 by npm install, say optionalPeerDependencies
  • a custom require-style function that knows about optionalPeerDependencies and does the right thing, including throwing/warning when nothing is found that fulfills a required class of modules (e.g. neither redis, nor mongo, nor mysql, 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'); }
}
  • I haven't needed this for a while, but I think it solves the problem I had. Thanks! – imiric Feb 22 '15 at 09:36
  • 2
    Yeah I figured it being months old you had probably figured it out or moved on. I found your question whilst searching for answers myself so this was mostly for posterity. More than once I've gone searching, only to find an answer from myself written a few years prior. So consider this enlightened self interest. Also, updated the answer to describe in general what the `codependency` module provides in the event that module evaporates from NPM and because links without excerpts are bad SO form. –  Feb 24 '15 at 06:13
2

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)

aesede
  • 121
  • 2
  • 6
    Asking for user input will screw up automated builds. Running `npm install` again inside of an install script may also trigger unintended behavior. I do not recommend this solution. – Lambda Fairy Oct 03 '18 at 02:33
2

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.

tabrindle
  • 121
  • 1