What is Yarn PNP?
Like most ambitious pieces of software, Yarn PNP aims to find a local maxima that simultaneously solves multiple discrete goals:
- Faster installation of your dependencies, using less disk space. It’s a lot of work to unzip all of your dependencies’ packages and write them into the local `node_modules` folder. Wouldn’t it be nice to skip this step?
- Prevent use of transitive dependencies. With a traditional flat `node_modules` folder, there’s nothing to prevent your application code from importing from any arbitrary package. For example, say your package.json lists a dependency on `foo`, and `foo` has a dependency on `bar`. Then it’s now possible for your code to also import and use `bar`. This doesn’t seem like a problem until you upgrade `foo` by a patch version. Then, it brings along a copy of `bar` that’s a major version newer than the one your application was built with. It’s obvious that this has a high risk of introducing breakage, and that you should have intentionally pinned `bar` to the version you want to use. But, it’s easy to forget. PNP can enforce this.
These are both obviously laudable goals. Yarn PNP achieves them in a relatively novel way.
How does it work?
First, Yarn will resolve your dependencies and download their .Zip files into a local cache, skipping the unzip step and the construction of the `node_modules` folder in your project.
Are there any drawbacks?
Unfortunately, the monkey-patching of Node’s built-in facilities and the necessary logistics of ensuring these patches have been loaded at runtime, mean it doesn’t quite live up to its name, “Plug ‘n Play.”
The first thing to consider is that not all libraries are compatible with the illusion Yarn is presenting with PNP. Existing packages have had a long history of relying on the existence of the `node_modules` folder to accomplish a variety of things.
- Autoloading peer dependencies. E.g., your SQL library might test for the presence of the postgres driver and automatically load/configure it if it has been installed.
- Looking up a package’s own resources via the filesystem.
Naturally, these assumptions can break if PNP doesn’t present a perfect illusion. Further, some tools such as React Native’s Metro Bundler are fundamentally built around the assumption of being able to analyze your `node_modules`. Yarn’s gotten better over time, but you can’t patch every issue, and packages that you need to use may remain incompatible.
The second thing to consider is your editor’s integration and your local environment. In order for your editor’s static analysis and language integration to work, Yarn provides “sdks” that patch required functionality. The ones for VSCode are pretty good, but this may not matter if you or your team members use a different editor.
Even if you are using VSCode, the experience isn’t always problem-free. One example is that the ability to navigate to the source for your dependencies’ code depends on a plugin for reading Zip files. This plugin has, on at least one occasion, broken after a VSCode update.
Verdict: Should you use it?
When Yarn PNP works well, it’s amazing. Installation is fast, you use far less disk space, and you can’t accidentally depend on any transitive dependencies.
Unfortunately, my conclusion is that you end up spending a non-trivial amount of your novelty budget in order to make it work. And, this novelty budget is probably best spent elsewhere.