79

Is there a way to specify OS specific dependencies in a npm package.json file?

For example, I would only want to install 'dbus' (https://npmjs.org/package/dbus) as a dependency for my module if the user is running Linux. I would have a different dependency for Mac and Windows.

sandeepmistry
  • 2,058
  • 4
  • 20
  • 25
  • 5
    Good question. I know there is the `os` field in [package.json](https://npmjs.org/doc/json.html), but that doesn't allow you to swap out dependencies based on current platform -- it just declares what platforms a package is whitelisted/blacklisted on. For example, this property in `package.json`: `"os" : [ "!win32", "darwin" ]` means "this package will not run in windows but will run on macs". Unfortunately, this doesn't really achieve what you're asking for. – smithclay Mar 05 '13 at 18:29
  • ^ That's exactly the problem, if the dbus module has native bindings that will only compile on a specific OS ([as mentioned below in the comment](http://stackoverflow.com/questions/15176082/npm-package-json-os-specific-dependency#comment22801812_15670089)), its package.json should include that `os` field. – Chris Vandevelde Jul 16 '14 at 19:31
  • Is there a way too install a package ignoring OS requirements? – kinger6621 Apr 13 '21 at 03:38

4 Answers4

45

There's a possible good way of doing this, depending on your setup.

npm package.json supports an os key,

and also optionalDependencies

  • os can be used to specify which OS a module can be installed on.
  • optionalDependencies are module dependencies that if they cannot be installed, npm skips them and continues installing.

In this way you can have your module have an optional dependency for each OS, and only the one which works will be loaded/installed ^.^

EDIT: As @Sebastien mentions below, this approach is dangerous. For any given OS, at least one of your dependencies is "required" and the rest "optional". Making all versions of the dependency optional means that if your installation fails for a legitimate reason, it will silently skip installation and you will be missing a dependency you really need.

TinyTimZamboni
  • 5,275
  • 3
  • 28
  • 24
  • 2
    Just found this answer (after replying to your comment about antipatterns). This looks like a far better solution than using an install script! Now that I know this is available, you can disregard my comment (I'll try and delete/edit it if it's not too late) – Metalskin Nov 25 '14 at 05:42
  • I kinda wish I could delete my comment too, since I ended up using install scripts heavily, along with this solution >. – TinyTimZamboni Jan 09 '15 at 08:49
  • can you show me a complete example? say `fsevents` is optional dependency on OSX which I do NOT care because my build script is running on LINUX. – chen bin Sep 08 '15 at 06:47
  • @chenbin I use it in this package.json file: https://github.com/appium/sample-apps/blob/master/package.json – TinyTimZamboni Sep 09 '15 at 17:12
  • 9
    This is a way, maybe the better way currently supported by npm, but not a good way. If you run npm install on the right OS and one of your optional dependency fail - for an unknown reason - npm will skip it and will not throw any error. The result will be a not working environment. Optional dependency is designed for dependencies that are optionals, here we want to manage dependency that are compulsory on a specific OS. – Sébastien BATEZAT Oct 15 '15 at 14:09
  • Ah right, as @Sebastien points out: making your dependency 'optional' now means that if the installation errors for a legitimate reason, your code will ignore it. – TinyTimZamboni Oct 15 '15 at 18:54
  • Beware: optional dependencies are still installed, they just are skipped if it fails to install https://coderwall.com/p/rmosmg/npm-package-json-optionaldependencies So I recommend using an install script instead https://stackoverflow.com/a/15670089/230462 – jonperl Dec 30 '19 at 20:18
  • Can you not just add a check to the post-install to ensure that at least one of the optional dependencies is available/installed successfully? – Ben Ellis Mar 24 '22 at 12:57
9

I think the short answer is no. I can think of a couple of workarounds though - the simplest is to just add everything to package.json regardless of OS, and then require() the correct one at runtime.

If that doesn't work for you, you might be able to use an install script to get the result you're going for - https://docs.npmjs.com/misc/scripts

I haven't tested this but I think it would work:

Add something like this to your package.json:

,"scripts": {
  "install": "node install_dependencies.js"
}

And then add a install_dependencies.js file that checks the OS and runs the appropriate npm install ... commands.

Justin Emery
  • 1,644
  • 18
  • 25
Nathan Friedly
  • 7,837
  • 3
  • 42
  • 59
  • 6
    The problem is my dependencies have native bindings that only compile on a specific OS, so I can't have them as explicit dependencies. Your suggestion for the npm install script works, I used os.platform() to detect which platform the user is using; – sandeepmistry Apr 13 '13 at 20:28
  • 2
    install scripts are now considered an 'antipattern' [source](https://www.npmjs.org/doc/misc/npm-scripts.html#note-install-scripts-are-an-antipattern). Instead should be using a .gyp compilation file – TinyTimZamboni Sep 26 '14 at 23:35
  • 2
    Just an observation that wasn't clear to me with the above. If using `package.json` to manage installation for a project and your not publishing, then using .gyp is not the solution. Refer to the post below by TinyTimZamboni, it's far more suitable for this scenario (http://stackoverflow.com/a/26069595/1125784). – Metalskin Nov 25 '14 at 05:44
  • @TinyTimZamboni It's problematic when compiling something with gyp requires different compiler options or environment variables based on the platform – Michael Jan 17 '17 at 18:33
  • >Don't use install. Use a .gyp file for compilation, and prepublish for anything else. You should almost never have to explicitly set a preinstall or install script. If you are doing this, please consider if there is another option. The only valid use of install or preinstall scripts is for compilation which must be done on the target architecture. – TarranJones Feb 19 '17 at 14:49
  • Regarding the "antipattern" of an install script, [per NPM](https://docs.npmjs.com/misc/scripts#best-practices): "The only valid use of install or preinstall scripts is for compilation which must be done on the target architecture." And that is exactly the use case this question is asking about. Using `optionalDependencies` has bitten me. Follow the advice in this answer. Resolve at runtime if possible, otherwise use an install script. – gilly3 Jan 09 '20 at 17:13
1

There's also the bindings-shyp module:

https://www.npmjs.com/package/bindings-shyp

Helper module for loading your native module's .node file

This is a helper module for authors of Node.js native addon modules. It is basically the "swiss army knife" of require()ing your native module's .node file.

Throughout the course of Node's native addon history, addons have ended up being compiled in a variety of different places, depending on which build tool and which version of node was used. To make matters worse, now the gyp build tool can produce either a Release or Debug build, each being built into different locations.

This module checks all the possible locations that a native addon would be built at, and returns the first one that loads successfully.

Community
  • 1
  • 1
TinyTimZamboni
  • 5,275
  • 3
  • 28
  • 24
0

Quoting @npm_support at:

https://twitter.com/npm_support/status/968195526989512705

2/2 If you'd like to avoid installation problems related to dependencies, one route is for you to write a wrapper that's required as a regular dependency, and to make sure that it has optionalDeps (and also ensure that the wrapper verifies you have everything needed to work).

But IMHO it looks more like a workaround than solving the problem for real.

I can understand that npm wants to preserve portability and avoid to deal with platform specifics, but it has to be done anyway and IMHO doing this at runtime is not optimal (specialty if one wants do optimize code size).

So today I have no optimal solution to share but an open discussion for proposal.

Can't "conditional dependencies" be supported in npm ?

The 1st thing that came to my mind was to to add a "override" section that will change (+add, -remove, =replace) current parsed sections.

For example:

dependencies: { "common-stuff": "*" } overrides: { "os: { linux: { dependencies: { "+best-linux-module" } } } }

And other option suggested by a developer I know, would be to introduce a provides keyword, then several modules could provide a same semantic than would be satisfied by resolver (a la debian), but it's generating similar overhead.

I am looking for a generic approach not only focused on OS support but also on other flavors of package (depending on engines for instance).

Do you know any related issue in NPM tracker ? if not I am considering to file a bug to be tracked at:

https://github.com/npm/npm/issues?q=dependencies+conditional

Feedback welcome on this idea.

RzR
  • 3,068
  • 29
  • 26