pnpm: Fast, disk space efficient package manager for JavaScript
pnpm.io
pnpm.io
All I want is a package manager that is correct, causes very few day to day issues and is going to work the same way for many years. Yet everyone seems to be optimizing disk usage and speed without even getting the basics (make it work, make it right) fully covered.
I don't understand why people are optimizing for disk space at all tbh. Like, have you ever edited a video project, used docker, installed Xcode? I cannot imagine what you must be doing for all node_modules combined to take up more than maybe 100 GB on disk.
pnpm seems to be the lightest of the bunch, which is nice but why even mess with symlinks and confuse other tools? Just put all the files in there, nest them and duplicate them and everything. I'll happily live with my 10 GB node_modules folder that never breaks and sometimes gives me a nice coffee break.
Possibly I'm actually just salty that Metro doesn't support symlinks and would otherwise be on the pnpm love-train.
This way work well with other tools (e.g. I can force create-react-app to install packages with pnpm)
Booting a serverless function with 100s of mbs of modules takes an appreciable time - to the point where some folks webpack bundle their serverless code
The difference in boot time between 50kb and 500mb is quite significant.
Makes sense when you think about it I guess. Only used bundlers for front end stuff.
I've been using pnpm and rush in my projecrs, and going back to npm at my employer's every day is such a chore.
Such a breath of fresh air…
* Classic node_modules: Dependencies of dependencies that can't be satisfied by a shared hoisted version are nested as true copies (OSes may apply copy-on-write semantics on top of this, but from FS perspective, these are real files). Uses standard Node.js node_modules resolution [1].
* pnpm: ~1 real copy of each dependency version, and packages use symlinks in node_modules to point to their deps. Also uses standard resolution. Requires some compatibility work for packages that wrongly refer to transitive dependencies or to peer dependencies.
* pnp[2]: 1 real copy of each dependency version, but it's a zip file with Node.js and related ecosystem packages patched to read from zips and traverse dependency edges using a sort of import map. In addition to the compatibility work required from pnpm, this further requires compatibility work around the zip "filesystem" indirection.
In our real-world codebase, where we've done a modest but not exhaustive amount of package deduplication, pnpm confers around a 30% disk utilization savings, and pnp around a 80% savings.
Interestingly, the innovations on top of classic node_modules are so compelling that the package managers that originally implemented pnpm and pnp (pnpm and Yarn, respectively) have implemented each others' linking strategies as optional configs [3][4]. If MacOS had better FUSE ergonomics, I'd be counting down the days for another linking strategy based on that too.
[1] - https://nodejs.org/api/modules.html#loading-from-node_module... [2] - https://yarnpkg.com/features/pnp [3] - https://github.com/pnpm/pnpm/issues/2902 [4] - https://github.com/yarnpkg/berry/pull/3338
Also, realistically, with a large enough repo, you will need to unplug things that mess w/ file watching or node-gyp/c++ packages, so there is some amount of duplication and fiddling required.
npm showed me that I lack creativity, for I could not imagine anything worse than maven.
The ~/organization/project/release dir structure is the ONE detail maven got right. (This is the norm, the Obviously Correct Answer[tm], right?)
And npm just did whatever. Duplicate copies of dependencies. Because reasons.
Node does a different thing. It can coalesce two different versions into one if the two things are within a certain semver range, but there's nothing that enforces whether things within a semver range are actually compatible. The most prominent example is Typescript, which famously does not follow semver. Another notable example of how NPM itself does things wrong is that it considers anything in the `^0.x` range as compatible, whereas semver distinctly says the 0.x range is "anything goes".
I always shudder to think that different versions of packages live in node_modules and one library produces an object that somehow makes it to the other version of the library and... I'd rather not think of all these implications or I would go crazy.
No that’s never been the case. If you have conflicting versions of a dependency in your dependency graph, maven chooses the “nearest neighbour” version - it selects the version specified least far away from your project in the transitive dependencies graph.
Pinning a particular choice is easy too - you just declare the dependency and specify the version you want instead of relying on transitive deps.
I guess the reason they didn’t went the duplicative direction is that java has safe class loading semantics at runtime and due to valuing storage/memory capacity (which was frankly a sane choice, like 10x bigger java projects compile faster than a js project that pretty much just copies shit from one place to another?)
[1]: https://vercel.com/changelog/projects-using-pnpm-can-now-be-...
Some packages need one, some another, so I tried to switch to yarn (or yarn 2) for a package that I wanted to try out, but then other packages stopped working.
If there are clearly better algorithms, why not refactor npm and add them in experimental flags to npm and then setting them to default as they mature (with safe switching from one data structure or another)?
Edit: I just learned from another comment that PNPM also supports Plug'N'Play :) Thanks steven-xu!
Here is a feature comparison: https://pnpm.io/feature-comparison
I was OK to merge pnpm into npm in the past. They have never suggested me this opportunity. Instead, they decided to re-implement pnpm's algorithm into npm and call it "isolated mode".
Most of my problems were created by the material UI libraries (I wanted to use them with SvelteKit), but I just got rid of it as those libraries were making development harder instead of helping.
I still wish there would be a nice UI library for Svelte, but I guess that's the disadvantage of not going with the mainstream frontend toolkit.
I'm glad I'm not the only one who has had that experience.
Now you have to maintain three different code paths, two of which depend on the behaviour of external projects, so you're always playing catch up.
That's such a bad idea on so many levels.
While node_modules has many flaws, in the current ecosystem all modes have their own pros and cons, and there isn't a "clearly better" algorithm: node_modules has less friction, PnP is sounder, and pnpm's symlinks attempt to be kind of an in-between, offering half the benefits at half the "cost".
Like in many computer science things, it's tradeoffs all the way. Part of why Yarn implements all three.
But I think it is best to use Yarn for PnP and pnpm for the symlinked node_modules structure.
I tried pnpm and it didn't just work, so I gave up. I would revisit it, but npm works.
These days I don't really see a reason to use yarn (but would like to hear them).
Right now only pain-point with npm is that "npm link" can't be forced to install peer dependencies so I'm unable to easily test typesscript built libraries within other projects.
This still doesn't mean that one can install just any package, but it does make it much more difficult for it to do much harm. Breaking out of a container is not as trivial as it once was. That said, it is not a perfect solution, so I'd be happy to hear of better ones. Any suggestions?
I have zero trust in NPM.
Dependency install times went down by a huge amount, and all the strange issues we had with lerna and npm sometimes erroring out, requiring us to remove all node_modules folders and re-install everything, are just gone.
We even noticed that the size of some of our production bundles went down. Before some dependencies that were used in multiple packages were being duplicated in our webpack bundles needlessly, but the way pnpm symlinks dependencies instead of duplicating them fixed that as well.
The non-flat node_modules structure did break some things as well, since in some places we had imports pointing to packages that were transitive dependencies and not defined in package.json. I see this as a positive though, since all those cases were just bugs waiting to happen.
Though we're using git submodules + yarn workspaces right now. The submodules will likely go away eventually.
I guess their benchmarks cover both [0]. But I'm also curious about independent figures.
https://pnpm.io/npmrc#node-linker
With node-linker=hoisted, pnpm creates a traditional hoisted (aka flat) node_modules, without using symlinks.
But all in all I'm glad they are moving the JS/TS ecosystem forward and other managers are catching up to their innovations and likewise. Great monorepo support I feel is a big necessity for a manager and well-working symlinking to prevent the insane node_modules sizes.
And can I use pnpm when working in a team where other devs use npm?
You definitely should use the same manager (npm, yarn, pnpm, whatever) as your teammates' or you're going to run into problems, either with them or with your CI workflow.
(This is not an endorsement of or recommendation of pnpm which I personally don't use daily)
The whole peer dependencies story is another clusterfuck. Everyone simply ignored the invalid peer dependency warnings, and now npm itself will just install all of them in 'whatever works' version. To get real peer dependency resolution you need a --strict-peer-deps flag. Not exactly a "feature" in my book.
Peer dependencies added more problems than they solved in my eyes... Most peer dependencies warnings come because some maintainer forgot to update the package.json and not really because two packages don't work with each other...
What is it about peer dependencies that give the host more flexibility in choosing the specific version?
Real question, not a challenge! I really don't understand this stuff, I've always found javascript dependency management very confusing.
For example if you have a dependency with a peerDependency `something: 2.x.x` and you currently have `something: 1.0.0` installed as a direct dependency, it will fail rather than allowing multiple versions to be run.
The example used in this post[1] on the Node blog is plugin systems, which is a very common expression of this pattern.
> Real question, not a challenge! I really don't understand this stuff, I've always found javascript dependency management very confusing.
I appreciate the clarification but it was clear to me the question was sincere FWIW! And yeah, a lot of this was hard for me to get a solid grasp on for quite a while. I think this tends to show up more in the JS ecosystem because semver was so eagerly adopted there, but I suspect it’s of similar benefit where semver is used and dev/prod dependencies is a proscribed distinction generally.
Imagine this structure of packages:
your-app/
├── dep-a/
│ └── dep-c
├── dep-b
└── peer-dep
Very simplified: `dep-c` is dependency of `dep-a`, so it is installed in its node_modules, but `peer-dep` is peer dependency of `dep-a`, so it is in node_modules of `your-app`. `dep-b` could also define `peer-dep` as its peer dependency, so it is installed only once.
When npm switched to flat node_modules structure, peer deps become somewhat redundant, but not quite. Pnpm, which uses symlinks to achieve proper node_modules structure while avoiding long filenames, combined with auto install of peer deps would be ideal package manager.If foobar can be used standalone, then babel is a standard dependency, not a peer dept.
Anyway, in any case, auto installing peer deps solves both situations. There really is no reason to not auto install them.
Clearly there is an audience for the former.
I have some app:
{
"name": "some-app",
"dependencies": {
"foo": "^1.0.0"
}
}
foo specifies some peer dep: {
"name": "foo",
"peerDependencies": {
"bar": "^1.0.0"
}
}
Now some-app doesn't directly use bar, so I didn't add it to package.json. Npm@7 and newer will install everything: foo and bar. If I used package manager without auto installing peer dependencies, I would have to manually update my package.json: {
"name": "some-app",
"dependencies": {
"foo": "^1.0.0",
"bar": "^1.0.0"
}
}
But in both cases node_modules will contain foo and bar. There are no "extra packages you never asked for". Adding bar as dependency of some-app is completely redundant information.Now, it's possible that there are packages that don't really require some peer dependency installed, and therefore thery are installed needlessly. But that's problem of those poorly developed packages, not mine. Why should I waste time to manually specify what should and should not be installed?
Peer dependencies are for extensions and for plugins to an existing stack, as material-ui has a peer of react or io-ts on fp-ts
Some create scripts invoked via `pnpx create...` exit the terminal without showing prompts. No issues running those scripts when using npm or yarn. IIRC, that was happening on Windows + Windows Terminal.
The name is too close to npm. I'll inadvertently type `npm` and that starts downloading all dependencies again. Of course I press `Ctrl+C` which has led to corruption of the node_modules folder on a few occasions.
I would not recommend mixing pnpm and npm. pnpm uses its own lock file. There's no guarantee your app/library will be using the exact package versions everyone else is using. That leads to it-works-on-my-machine kind of bugs.
pnpm also helped us solve issues with packages in our monorepo importing dependencies declared in other packages because of node_modules hoisting behaviour with yarn. With yarn, all of your packages' direct and indirect dependencies can be resolved from any package in your monorepo. This makes it hard to isolate packages from each other. For example, this was causing issues when we wanted to generate docker images for some of our packages since we only wanted to copy specific directories into the image rather than the entire monorepo to avoid having to bring the entire monorepo to the image. pnpm also allows us to use features like git sparse-checkout because we can be confident there are no implicit dependencies between packages.
pnpm makes this possible by only exposing in the node_modules direct dependencies. In monorepos that don't use pnpm, you can remove a dependency and your monorepo can break in unexpected places because other packages in the repo implicitly depend on this dependency. This is more common than you think because people tend to rely on IDE auto-completion when writing imports and IDEs just tend to read node_modules instead of package.json so its very easy to take on an implicit dependency when all directs/indirects are hoisted into a root node_modules.
Yarn supposedly fixes this issue in their newer releases with Plug n Play but from from what I understand it basically monkey patches Node's require statements with its own behaviour. This just didn't feel good to me from a tooling compatibility perspective which is why I went with pnpm and I'm glad I did. I'm honestly puzzled at Yarn's popularity and why pnpm isn't as popular as it should be.
https://github.com/npm/rfcs/blob/main/accepted/0042-isolated...
Yarn has a PNPM mode now too:
https://dev.to/arcanis/yarn-31-corepack-esm-pnpm-optional-pa...
Seems this might not work with angular (yet)?
lerna has soo many issues, is slow & cumbersome. #2142, no way to update dependecies in monorepo subprojects? how is there a monorepo tool where projects cant update their deps? everything on pnpm's built-in monorepo support just works, nice & easy & fast.
If I were to pick for a new project today, I’d probably go back to Yarn. But I’m glad there’s PNPM as an option.
I wish there was something like that in PHP's Composer, where I have repeatedly hit situations where different packages have a dependency that was pinned to a different version (such as one package using Guzzle 6 and another Guzzle 7), and therefore my composer.json was un-buildable.
(I have less experience with NPM so I don't know if they have a different solution for this.)
In JavaScript the caller decide how a module should be used by importing it to symbol, thus different versions of the same library can exist simultaneously.
In PHP it is the callee that decides how to be imported because of namespaces.
One and only one class with that exact name and namespace can exist at a given time, thus different versions of the same library can’t be loaded simultaneously.
That is why modules are far superior over namespaces.
It appears to show several symlinks? Thanks for pnpm!
There's what, maybe a handful of people who can make that happen?
I fundamentally disagree with the blockade. this moves makes this project incompatible with open source licensing.
if anyone is interested in this sub thread, it diverge from the discussion around the tool itself, but note that all the project standing for Ukraine in this manner are: - breaking one of the fundamental principle of open source - pretty risky to use. what if your locality also becomes block-listed. - demonstrates a poor judgment from their authors, who more often than not are reluctant to debate/reconsider their position.
Reactions to extreme circumstances can understandably be emotional, it doesn't imply that logical criticisms are necessarily insensitive.
Fortunately for me as a developer in the USA, I guess most of them, in the Global south, aren't developers, or know they can't be successful as developers blocking traffic from the USA, no matter how many atrocities the US military or intelligence have committed in their countries. :(. I guess if they wanted to try, it's an interesting question if that would be some kind of effective action in changing US atrocious behavior. Probably not. :(
That said, this is basically a form of boycott. There does seem to have been some significant change of opinion around the value and ethics of the tool, from when the main example we had was the BDS movement called by Palestinian civil society against Israel. (Which by the way, does not in fact call for doing things like blocking all network access to those in Israel, what people are doing against Russia is way broader and less targetted than what the... more controversial(?) Palestinian-led BDS has done or called for against Israel. Which is interesting.)
In Russia there are some good people but they are in extreme minority. Extreme. Even the liberals in Russia are supporting the annexation of Crimea. Maybe the dictatorship is the reason, maybe the propaganda. I don't know and I don't care. This is how it is and I do what I can to exclude them from my life.
Actually america has been by far the biggest aggressor on the planrt in tge last decades, and it killed the most civilians outside Africa.
That's my problem with this massive anti russian hysteria in software.
You only appear careless, racist and naive. It's better to avoid politics in software and business, because if you don't you have to basically ban anyone.
Please don't use pnpm. Use Yarn or npm, those are better package managers for you.
OS and politics should not be mixed. Your actions do nothing but say that you will "weaponize" an open source library to match one goal "punishing Russians" but not punishing other perpetrators.
If every software maintainer started to apply its own political views and ban some users the entire ecosystem would implode.
Should muslim developers ban all chinese users for the uighur concentration camps, and myanmaris for their treatment of Rohingya?
Should I, a Polish developer, ban Ukrainians because they hail as a hero and title streets to a criminal like Bandera that killed my own polish family in eastern galicia during world war two?
Where and when would this hysteria end?
I have sympathy for your situation, but not for such solutions. They damage everyone, including OSS.
- Supporting an annexation that has already happened is not the same as supporting a war.
- That being said, even the liberals in the US supported the various invasions and occupations. Don't forget that Libya happened under Obama.
- Even the liberals in Turkey are supporting the annexation of northern Cyprus, even the liberals in China support the annexation of Tibet, etc. It is not something unique to Russia.
"In Russia there are some good people but they are in extreme minority"
Bush's approval rating was the highest when he declared the invasion of Afghanistan (90%) and when he declared the invasion of Iraq (70%).
In addition to that this is typical racist rhetoric, I have heard exactly the same thing about Mexicans, Blacks, Albanians, Jews, Roma, etc.
Anyway, this does not explain your plan to keep the restriction permanently. After all they will not be paying taxes that fund the war effort after the war is be over.
The US has and continues to do some pretty awful things around the world. Turning some parts of Pakistan into hellscapes where an invisible drone could kill you from the sky at any time and regularly does kill women and children with no warning, would be one example.
The vast majority of citizens in the US either pay no attention to this at all, or think it's a a good thing. There is a minority who think it's awful. (I suspect these proportions are pretty similar in Russia).
Since the US is theoretically a democracy, we citizens could, one would think, easily stop these things by voting for different people, you'd think people would hold us more accountable and be really mad at us. But somehow they don't, they're like, eh, most people in the US are good people I don't blame them for their government!
And to be fair, I wish I knew how to get my government to do something different -- or how to get more people in the country to pay attention to the really catastrophically criminal things my goverment does, I don't feel like I have much control over it either. (Although surely more than Russians do, in the really not a democracy of Russia?) I do what I can, I am politically involved where I can find the energy to be. It doesn't feel like enough. I'm not sure I or my country-mates deserve the dispensation to not be held responsible.
I think most governments do awful -- really horrendous, murderous -- things. I think tmost people are fundamentally good people, but many citizens of powerful countries doing bad things have these days been hoodwinked to ignore them or support them.
I wish I knew what to do about it. One thing I am personally sure of is that it starts from not judging people by their ethnicity or nationality -- that kind of thinking is what helps governments convince their residents that the violent things they are doing to someone else are ok. That's the problem not the solution.
But I'm not opposed to boycotting as a tactic. I do support BDS against Israel. The BDS organizers have been very careful (and learning from experience) at trying to figure out how to do it in a way that is ethical and maximizes effectiveness. (I think the BDS campaign has been effective, relative to anything else done to try to support Palestinians, although not nearly as effective as one would like, which goes without saying as Israel continues it's decades-long occupation). But reading what they have to say on the topic of boycotts against invaders, Israel, and Russia, is in my opinion worthwhile: https://bdsmovement.net/Hypocrisy
Russians that leave Russia are not blocked.
I am Hungarian by ethnicity. I will be probably also judged for the terrible policy of Orban. I feel deep shame for it. So I understand (a little) in what situation those "good" Russians are. And I know that those Russians support my decision and understand it.
Are you gonna ban Israeli for their apartheid, chinese for the uighurs concentration camps, myanmari for ethnic cleansings of Rohingya? What about americans and the 200k civilians killed in iraq in an illegal and unprovoked aggression?
See what's the point of doing lame politics like that? You end up declaring to the world that 800 villages burned and 50k civilians thrown in fire matter none to you because they aren't white.
I feel disgust at these double standards, at showing to the world that there are tier 1 and tier 2 victims.
Or do you just care a
Ironically, blocking Russian IP addresses could be seen as a form of non-violent protest against Russian web censorship.
https://en.wikipedia.org/wiki/List_of_websites_blocked_in_Ru...
"Disk space efficient": again, compared to what?
Javascript: ...oh