Next.js, Prisma & TRPC: Is it Worth Migrating to Pnpm?
Is Pnpm Worth It?
It’s common sense to use npm
or yarn
since they are not just stable, but also widely popular (not without downsides of course). But there are always other options.
There is a small project written in Next.js with Prisma, TRPC with addition of some other APIs (everything is following t3 stack).
The package manager chosen initially was npm
. Maybe partially due to a shorter cli command than yarn
(subjective due to human laziness).
But let’s put the stability approach to a side and pay attention to the lots of hype about pnpm package manager. It’s possible that we might benefit from migrating this small project to pnpm
.
Typing pnpm in The Cli is pain
Based on multiple user feedback typing pnpm
has never been comfortable, and it’s hard to say if it would ever be, but let’s get over this painful moment of painnpm
.
Worst case worse we’ll map it to an alias p
(such an acceptable terrible idea):
# ~/.zshrc | ~/.bashrc
alias p=pnpm
Does Pnpm work in Webstorm / Idea?
The initial assumption has been that IntelliJ Idea integration would be an issue, but it actually worked well. There was a possibility to change Node.js package manager to pnpm in Settings. Run/Debug configuration had this option as well.
Pnpm Issues with Prisma
This project is using Prisma ORM, and as soon as it was switched to pnpm
it started showing issues with the local client that’s supposed to be located in node_modules/.prisma
directory, which was not there.
After further research it turned out pnpm’s main feature is restructuring (hoisting) directories under node_modules
folder in order to optimize caching and faster access to already downloaded modules. This resulted to the sitation that Prisma simply couldn’t find it. It has been discussed online that this has been a poor choice of Prisma design, which I partially agree with.
Nevertheless, it was possible to work around the issue and add prisma
package to an exception list and disallow pnpm to hoist it.
As a result, .npmrc
file with the following contents has been created:
# .npmrc
public-hoist-pattern[]=*prisma*
node-linker=hoisted
pnpm i
succeeded!
Pnpm issues with node-gyp
When the time came to benefit from the caching and run pnpm i
for the second time, it failed with an issue of compiling a native package bufferutil
with node-gyp.
Research shown pnpm
has an ongoing opened Github issue with node-gyp. This issue has been opened since Nov 6, 2019(!).
❯ pnpm i
Lockfile is up to date, resolution step is skipped
Packages: +645
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
node_modules/@prisma/client: Running postinstall script...
node_modules/bufferutil: Running install script, failed in 327ms
node_modules/bufferutil install$ node-gyp-build
│ node:internal/modules/cjs/loader:998
│ throw err;
│ ^
│ Error: Cannot find module '/Users/dmitry/dev/nutcorp/node-gyp-build@4.6.0/node_modules/node-gyp-
│ at Module._resolveFilename (node:internal/modules/cjs/loader:995:15)
│ at Module._load (node:internal/modules/cjs/loader:841:27)
│ at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
│ at node:internal/main/run_main_module:23:47 {
│ code: 'MODULE_NOT_FOUND',
│ requireStack: []
│ }
│ Node.js v18.12.1
└─ Failed in 327ms at /Users/dmitry/dev/nutcorp/squirrelspace/node_modules/bufferutil
node_modules/@prisma/engines: Running postinstall script...
node_modules/esbuild: Running postinstall script...
node_modules/keccak: Running install script...
ELIFECYCLE Command failed with exit code 1.
node_modules/prisma: Running preinstall script...
We can see that pnpm is trying to find node-gyp-build somewhere else.
There seemed to be workarounds, and one of them applied: locking down node-gyp
to an older version just for bufferctl
package:
// package.json
"pnpm": {
"packageExtensions": {
"better-sqlite3": {
"dependencies": {
"node-gyp": "7"
}
}
}
}
It didn’t yield much results, as another issue came up with a different package, after which it was hard to rationalize the time required for fixing current issues, as npm speed was still sufficient.
The exploration of pnpm
had to be stopped, but hopefully some day everything would work with minor issues.
Per another suggestion on Twitter an additional tweak was added to .npmrc
:
The issue suggests to set the child-concurrency=1 setting in .npmrc. That doesn't help?
— zoltan 🇺🇦 (@ZoltanKochan) June 17, 2023
It has been set in addition to all other parameters. The final file looked like this:
public-hoist-pattern[]=*prisma*
node-linker=hoisted
child-concurrency=1
Which didn’t yield any different results and a recurring pnpm i
resulted into the same error of not finding node-gyp
Conclusion
This migration attempt has been performed for an internal project with only one person on the dev team, but even then it was quite cumbersome and it’s hard to imagine supporting an extensive list of exclusions long-term.
Objectively, npm packages on this project (and other internal projects, to be fair) are not installed so often and there seems to be very little benefit from a faster resolution/caching logic (unless it’s related to CI, of course). So as a result, it’s not worth chasing the performance as much. It’s entirely possible that this idea is biased (and possibly completely wrong, so it would be great to hear more opinions).
As a conclusion, pnpm
is a smart improvement over npm or yarn in terms of storing the packages, but it’s hard to justify moving to it yet and should not be recommended as a primary package manager for mainstream projects.