Flat Tree Dependency Resolution in Npm v3
docs.npmjs.com
docs.npmjs.com
But I wish they had aimed higher. The better goal would be that the entire state of the node modules directory is a pure function of the contents of the package.json file plus the platform details (compiler used for native modules).
While they're at it, the ecosystem could be improved considerably if there was some sort of obvious "penalty" applied to any package that compiles native code, because such things cause considerable extra trouble for Windows users. A visible penalty, transitively carried up the dependency tree, would discourage use of such modules with native code; projects would use them (depend on them) only if absolutely necessary instead of accidentally, all over, all the time.
Yes, this would have been much better than this new, also-broken approach. Functional package managers like the one I work on, GNU Guix, have this problem solved and solved well. Language-specific package manager developers would do well to either implement similar systems in their projects, or realize the inherent problems with limiting the scope of dependency management to a single language rather than the full dependency graph and switch to using functional package managers.
This is what we did with Dart's package manager[1]. It works like Bundler where it finds a single set of package versions that satisfy all of the constraints and there are no duplicate packages. It's a challenge because constraint solving is NP-complete, but it works very well in practice.
[1]: https://pub.dartlang.org/
> realize the inherent problems with limiting the scope of dependency management to a single language rather than the full dependency graph and switch to using functional package managers.
The problem then is now your language has a dependency on an outside package manager, one which is often OS specific. Most modern languages need to support a variety of OSes.
Constraint solving is the status quo, and I am strongly opposed to it. Rather than saying "I want foo >= 1.0" in a package manager, you should have the build system test the environment for the features you need. This is what the Autotools and other build systems that have been around for decades do. This way, the project isn't tied to a single package manager and, in the case of free software, greatly eases the burden on package maintainers that want to add your software to their distribution.
Functional package management doesn't need constraint solving because every package in the system precisely describes itself: exactly which dependencies are needed for build-time and runtime (which precisely describe themselves, recursively, all the way down to libc), precisely which source code (tarball/directory/whatever + checksum), and the exact build script that turns that source code into a (hopefully bit-reproducible) binary. There's no constraint solving to determine what the dependency tree is, it's already been encoded in the package objects themselves. This is what any robust package manager should enable, but the only two that I know of that do this are Nix and GNU Guix.
>The problem then is now your language has a dependency on an outside package manager, one which is often OS specific. Most modern languages need to support a variety of OSes.
A programming language should not depend on any specific package manager. This is what build systems are for! More and more language communities conflate the two, but this is a huge mistake that we are paying for by making our software nearly impossible to reproduce from source code. [0] Everyone just uses pre-built binaries full of circular dependencies and reproducibility issues because things are so tangled that no one actually knows how to build anything from source anymore.
I think the value of language-specific package managers lies in providing an easy way to fetch pure (no native code) modules to faciliate simple code sharing and helping newcomers get bootstrapped quickly. However, for serious software development and deployment they are terrible and we desperately need better tools. I think functional package managers are the tools we need, as they have greatly simplified software building and deployment for me whilst also greatly increasing the reliability of the systems that use them thanks to transactional upgrades and rollbacks.
[0] http://www.vitavonni.de/blog/201503/2015031201-the-sad-state...
Is there a single non-language specific package manager which is cross platform, and supports a large percentage of packages for a given programming language?
Note that I'm not saying npm is excellent, but I'm not aware of a single successful language-independent package manager. If there are none, why?
One could argue that they aren't "successful" (in that they don't have all the packages you might want), but they are still quite new and are pretty different from traditional package managers. I believe that Nix/Guix are the future, but a future that will take some time to become widespread.
Not good enough, language package managers must work on Windows, with a regular command prompt.
Spack is build-from-source and takes a lot of inspiration from Nix and its descendants, but it adds support for things like swapping out compilers/MPI versions/etc., as well as a syntax for composing new builds of things on the fly That is, you can build something with a slightly different version of a dependency without ever editing a package file. Our users to be able to build a package with 6 different compilers and 3 different MPI library versions, and have their 18 versions coexist on the same system. This has been super helpful for testing production code at LLNL, because we have to run our stuff on other peoples' often very different clusters. For instance, at LANL, we prefer PGI compilers. At LLNL we tend to like Intel or Clang.
One reason languages tend to grow their own package managers is that there are all kinds of peculiarities about how different languages manage modules/extensions. Have you seen how many different ways there are to deploy Python modules? It's very hard to have multiple versions of things coexisting in the same Python env, and things like pkg_resources require editing client code [3]. Ew. Spack tries to address this by allowing different language packages to implement their own module activation/deactivation logic, so you can kind of have a language-specfic package manager for each install of the language. I'd rather have something more like Nix's profiles, though.
I wouldn't say that the project is huge yet, but we've had some interest from mostly other HPC sites. It would be cool if the project caught on in the "real" world, too.
[1] https://github.com/llnl/spack
[2] http://www.computer.org/csdl/proceedings/sc/2015/3723/00/280...
More importantly, part of the dependency graph, including compilers, is treated specially and essentialy considered outside of the scope of Spack. This seriously hinders reproducibility, as we tried to explain in https://hal.inria.fr/hal-01161771/en .
Ummmm... dpkg and yum? You know, the package managers that manage the software on everyone's production servers. Or do these not count because they don't work on Windows or something?
The nice thing about node is that it's quite small (3.4MB zipped). We don't have to ship npm with the app, and certainly an OS level package manager would be way too large!
The real solution to the sub-dependency-insanity of npm is ... use fewer dependencies. Don't use dependencies that have sub-dependencies. Write more code to directly solve your problem.
This is a viewpoint you won't often hear, because it comes from someone who doesn't write js or use node ... I've just had to fix the deployment of some node applications written by frontend devs a few times. I've seen npm trees with over 1000 modules to do some very trivial stuff. Node actually seems nice, and it has all the basics you need built in.
I typically use C or Python, which pretty much never use nested dependencies, and have never felt the desire to install/update/manage/debug exponentially more dependencies.
The issue comes when people create these huge "do-everything" modules. This is worth a read: https://github.com/sindresorhus/ama/issues/10#issuecomment-1...
That statement is true in theory, as long as everyone follows semantic versioning and dependencies never have unexpected interactions because of bugs.
Unfortunately, in the real world, neither of these conditions holds perfectly. The moment you depend on any module in your package.json that directly or indirectly depends on any other module where any version is not precisely fixed, you are immediately running uncontrolled and probably untested (by you) code. Moreover, if anything does go wrong, you may also have considerable difficulty identifying and reproducing the exact combination of versions of everything that caused the failure.
I have spent far too many days over the past few months tracking down exactly these kinds of issues, sometimes in some of the best known and most popular modules available via npm. For example, as the article itself mentions, what you wind up with in your node_modules directory can depend on install order under the new scheme. I don't think this is a good thing.
I therefore agree with those who advocate minimising dependencies, within reason of course. I also advocate strict version numbering and 100% reproducible builds, which is unfortunately quite hard to achieve once indirect dependencies start entering the picture with Node and npm, under either the old or the new scheme.
Lodash, Underscore, Ramda and other come to mind in particular here... It's really easy to wind up with at least two of those three. Part of why I wouldn't mind seeing a few "winners" even if slightly larger win out. We're at a point where we get to tear jQuery out of projects, but it's easy enough to wind up with bundles that are larger than if we'd just used jQuery or Zepto, or whatever.
I gotta say, as someone who never ever uses Windows but maintains a couple of open source packages, I'm really sick of the Windows only problems that crop up.
I write software for fun, I release it on github, and some people find it useful. If there are other people who don't, they can improve it for their own purposes, or they can find or write something better. I don't think anybody has a right to expect me to support Microsoft's product. That's insane.
True enough, though it's also fair to consider a language/platform/ecosystem inferior for some work if it relies on that kind of individual and often less portable contribution for its effectiveness. A lot of FOSS advocates will promote community support and a broad contributor base as advantages of that style of development, but there is another side to that coin, which is that sometimes you get just what you (didn't) pay for and you're on your own in terms of support or working around any problems.
Doesnt Microsoft have their own V8 or node.js fork now?
So now you get just a little, watered-down taste of using the non-preferred platform for something.
npmjs cares about the enterprise + windows.
should we not pick up capitalism's slack? capitalism sure isn't going to pick up its own slack, err... systemic method of restricting underprivileged/poor people's access to power.
Wow, this is incredibly wrong. The cost of education to learn Linux is living in a country where you are likely to be literate and have access to a computer. That is literally the cost to learn Linux, guy. There is nothing like an MSDN subscription for Linux documentation.
npm is also a bit different, in your objection: they're a company. Companies need users. Supporting more platforms to get more users seems like pretty much, well, business as usual.
For instance - OS X window managers suck. Do you think that a project like Spectacle should have never been built because they're just picking up Apple's slack?
Microsoft is very happy that Node, Java, Clojure, etc work on Windows. It helps them a lot. But they make little to no effort to help bridge the gap, they usually leave that to everyone else. That's my gripe, right there.
And to be fair, this is not a black and white situation. It's complex and Microsoft's role in all of this is complex too. And sometimes this issue does crop up on other OSes, for example Docker on OSX is sometimes a little painful.
OK, but that's also where you're wrong, right there. In general, but also particularly for this exact issue - Microsoft considered attempting to fix the file path limit for the first release of Windows 10. Maybe you don't understand, but it's going to take a herculean effort to fix this. They want to do it and if you need proof of that I can find posts of them talking about what I just said.
Why they can't fix .Net under the covers to use UNC in windows to avoid the class of problem I don't know, understand or comprehend... and that's just one very common platform for windows. I would say the same for npm/node and libuv for that matter.
I use bash a lot in windows (installed with msysgit), and that's got it's own set of problems. More problematic is the number of node modules that rely on bash scripts for builds, that don't work at all in windows.... which makes it very hard to contribute.
Microsoft should finally fix the MAX_PATH issue in Windows, but npm should just fix their software: it's too important to be that buggy.
Devs certainly aren't forced to "pay the Windows tax" in any way. Plenty of OSS projects completely ignore Windows. I'm sure you're aware though that Windows has the majority market share of desktop operating systems. If your software is interesting to the vast, vast population of Windows users, you might get some requests to support Windows. And since it's not an outlier in the market, other desktop OSes should obviously strive to be more like Windows ;)
I'm annoyed that Microsoft insists on going forward with a proprietary OS that does not conform to standards that pretty much every single other OS does. Sure, platform specific issues do happen for other OSes, but they are rare. They are extremely common for Windows.
For sure, I have no obligation to fix Windows specific issues in my projects. All of my projects are done in my spare time and given away for free. But Windows specific issues are still annoying and disappointing.
Another aspect of this that is annoying is that major, mission critical open source projects have to dedicate so many resources to Windows. For example this bug in ClojureScript: https://github.com/clojure/clojurescript/commit/80d46bdb7969...
Imagine if the Clojure team didn't have to worry about file separator differences, and the multitude of other Windows annoyances. How much extra energy, time and resources would they gain? I suspect a lot.
EDIT: And I would also say I have every right to complain about Microsoft. Ignoring standards and doing their own thing for their own financial gain at everyone else's expense is definitely worthy of complaint. Also see Internet Explorer.
I think you need to get out more. Or at least go to meetups that don't only meet at coffee shops. You're cutting out a huge chunk there, and there are still huge chunks that use Windows to do Java, C++, etc.
I mean, game development is a multi billion dollar industry that is almost exclusively Windows-but-not-.NET, outside of consoles, and even on consoles it's still Windows in a large number of cases.
>> I'm annoyed that Microsoft insists on going forward with a proprietary OS that does not conform to standards that pretty much every single other OS does.
This is different from Apple with OSX and iOS how? This is different from Google with Android how? This is different from Canonical with Unity how? This is different than RedHat and Debian and pretty much every other major Linux distro with systemd how?
And for crying out loud, file separator issues? That's not even a hard one.
I'll never run "npm install" or "gem install" or what have you on iOS, Android, etc. Proprietary OSes come in all shapes and sizes, all bringing their own pros and cons to the mix. I just happen to be focusing on a very large con of Windows at the moment.
As for OSX, Redhat, Debian, etc. Sure, they're all different, of course. But generally speaking, at least when it comes to Node and npm (and as far as I know, Ruby, Clojure, Python, etc, please correct me if I'm wrong), these OSes tend to get along pretty OK. Windows, does not.
>> And for crying out loud, file separator issues? That's not even a hard one.
And yet how many times has that problem been hit over the decades? I mean heck, look at the commit message, "Another Windows path issue". And yeah, it's not even a hard one, it just gets worse from there.
I came to that issue from here (which is my project): https://github.com/city41/reagent-breakout/issues/11
So for "not even a hard one", still took me 20 minutes of digging to find out what was going on. X minutes across all developers across how long Windows has been around really adds up. For just one of Windows's differences.
Any reasonable language has the ability to query all of the platform-specific stuff directly from the standard library. You can't even count on two Windows users having their home directories in the same place, or two Arch users having all of their config files in the same place. It's a necessity to be able to abstract all of these away even for a single operating system, which means you get it for free when moving to other operating systems.
I can't believe your defending OS X though when Apple makes you buy a whole computer from them to use it...meanwhile I can run a 6 month trial of Windows (over and over again) on any virtual machine. Honestly, it's way more work to support OS X than Windows if you really bother to look at it.
But I don't think that negates the fact that to develop anything at all for OS X I have to buy a whole computer from Apple, since it's damn near impossible to get a stable OS X experience by running it in a VM or directly on non-Apple hardware. Furthermore, remotely accessing Xcode on a Mac from a non-Mac computer is really painful since the only option is VNC - the bottom of the barrel, lowest common denominator of remote screen sharing protocols.
When developers are hostile torwards Windows, this is the result. This doesn't do anything to Microsoft, this just hurts other developers and users.
I've done far more dev targeting linux deployments the past few years, and much of it is far nicer on *nix than windows... but your viewpoint seems to be kind of arrogant. I haven't worked for a company that has more than 100 employees that doesn't do most development on windows, even if that isn't the target for deployment. That includes two major financial institutions, some large internet services and many other smaller companies over the years.
We used to value writing portable software as a skill, we used to value languages and libraries with robust specifications that could be used to write portable code, and portable code is also relatively future-proof code. I've worked on large projects that shipped on literally a dozen or more different platforms at any given time and were maintained for well over a decade with the significant variations in platforms that happen over that kind of time frame. Those projects built probably 95+% of the same source code for each platform, with platform-specific APIs and conventions carefully isolated.
That attitude and the related skills seem to have been much less valued in recent years, not least by the Linux community. (What, you want to build this C or C++ code with a tool chain other than GCC and friends?) If people carelessly scatter platform-specific code all over their projects, then of course they won't be easily portable, but more often than not such limitations are entirely artificial and could easily be avoided at negligible cost. In my experience, this is also true of a lot of libraries with the likes of Node and Python where native code is used in managed packages, and I think it's fair to consider that the resulting portability limitations are a potential disadvantage when choosing the language for a project.
That way you have the best of both worlds - dependencies don't collide and a single copy of a package version on the file system in node_modules.
Now I know that can't always be the case as it adds a lot of work to the development and maintenance, but it is a really nice thing when i see it.
Does this sound insane to anyone else?
EDIT: I understand not wanting to modify node's `require` semantics, but this is an unacceptable sacrifice of consistency for efficiency. Surely it would have been possible for an `npm install --save x` to put the `node_modules` directory in a state identical to `npm install --save x && rm -rf node_modules && npm install`. It might take a little longer to shuffle some directories around, but certainly not longer than a full `npm install`.
Why not always store packages at the top level, and create a directory for each version that's required?
For example: directory "A" contains two subdirectories: "v0.1.0" and "v0.1.1"
Changing the directory structure to encode versions is probably the Right Way in a grand sense, but it's a much more substantial change than just promoting packages by default.
It doesn't seem too big of a leap to do it the way I was suggesting.
Either bubble up and look for "node_packages", but this time store a symlink there, or add a dot file that tells you "this is where I found package.json".
I'm sure there was a reason they didn't go for either of those solutions because I've followed the team's discussions when they were iojs, and they have very smart people. I was just curious about the reasoning :)
I assumed the same team was in charge of both.
So resolving things at runtime is already happening.
I agree using package.json at runtime is not the best solution though.
A way to keep things clean and avoid using package.json is to use symlinks instead of downloading a fresh copy, which would make putting everything at the top level a possibility.
I'm sure that option was considered, but I'm curious about why it was not taken.
I suppose I should look at the discussion notes. Hope I'll remember to do that when I'm home!
It's terrible practice, but it's not unheard of for an NPM module to monkey patch its dependencies, since before this the library could assume it had sole ownership of its whole subtree.
Your node_modules will look like this:
- Package_A
- Package_B
- Package_C
It's only when A and B depend on different versions of C that cannot be resolved via semver as safe. - Package_A
-- node_modules
--- Package_C
- Package_B
-- node_modules
--- Package_C
I am pretty certain that monkey patching your dependencies is frowned upon in the Node world. It's best to fork the repo make your changes, and then depend on that. - Package_A
- Package_C_vX
- Package_B
-- node_modules
--- Package_C_vYIt even supports circular dependencies.
In JSPM/SystemJS, require()'ing/importing a module is still by name (as it supports NPM modules), but the package.json file has to be parsed in order to map module names to an installed module version. Note, this mapping is only done in the developer environment - once you build a bundle all the mapping is statically compiled into one file.
I would also assume for very large apps it may improve startup time because you don't have to initialize and retain multiple copies of the same module.
The allowed/unpredictable duplication can even cause very hard to identify bugs when a peer dependency relies on "instanceof" checks and there are multiple versions of this dependency. I've seen it happen with React and Backbone to name a couple.
If the `npm install` allowed control over install order (instead of just being alphabetical) and there was a way to be notified of incompatibilities that would cause potentially unnecessary duplication that would be at least something that could prevent problems like this from occurring.
I work on a client-side module loader StealJS[1] that implements the npm algorithm (2 and 3) in the browser and 3 makes things worse for us. With NPM 2 we could load a project without causing 404s (except in the cause of require("./folder")) but since NPM 3 says "install order matters" now it's no longer deterministic and 404s are more common.
It would be nice if NPM had some working group to discuss their algorithm and invited in other implementers for feedback.
Deleted comment
That's one of the reason bower is losing out.
Though, if there are problems, I wonder - can the flat dep resolution be disabled using some CLI flag? Or when installing deps, or in .npmrc, or during a shrinkwrap?
I guess I'm not sure what level of non-determinism they expect, but on this page: https://docs.npmjs.com/how-npm-works/npm3-nondet it appears to make the claim that the only effect is on tree structure, not the actual versions of packages that are picked up. And in fact in their example this IS the case. I think this is fine btw.
However, I have found edge cases where install order actually changes the versions of packages that are picked up, and in ways that make it very very difficult to work around (basically you will be forced to manually edit a shrink-wrap file -- so it is necessarily on the end user not the package writer).
Basically, if any package lists and absolute dependency (vs a semver range), it will affect ALL the packages alphabetically later than it and FORCE them to take the same dependency.
node_modules/[module_name]/[version]
Then it would be flat and support multiple versions of the same module in a way that is completely deterministic and also fully deduplicated.
This new system is unnecessarily complex.
I certainly agree that your suggestion simplifies file-system layouts. The tradeoff is that the complexity shifts to other parts of the system.
That said, I'm not a fan of the v3 approach. I'd have preferred a central package cache with a structure similar to your suggestion. I'd add that each package in the cache should have all of its dependencies resolved in its own /node_modules/ dir with symlinks. Unfortunately, I still can't see a nice way to handle peer dependencies. Peer dependencies require the ability to walk up the file system to resolve, which you can't do with symlinks.
Go on, try installing X packages at random using npm - did they install cleanly?
The baseline outcome for using a package installer should not be reams of errors, it should be a cleanly installed package. Installing packages works fine with other language ecosystems, why not with npm?
http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx...
https://en.wikipedia.org/wiki/Long_filename
Microsoft should finally fix such old limitations (-> update WinAPI), instead adding work arounds to third party projects like Nodejs.
https://github.com/Microsoft/nodejstools/issues/69
They could also improve the command line shell (cmd.exe) that also PowerShell relies on.