Using pnpm for all package management for custom nodes

In order to ship internal custom nodes to our users faster with smaller install size, we are trying out pnpm to replace npm in the context of NodeJS for Node-RED.

Although pnpm seems to be better than npm in terms of installation speed and size for NodeJS, we are not sure how that will turn out to be with Node-RED.

For one, I assume that pnpm will at least co-exist with npm, because community nodes are still installed using npm through Node-RED workspace which cannot be replaced without hacking NR internals, correct?

The scenarios where we want a drop-in replacement using pnpm for npm are roughly:

  • pnpm install -g node-red
  • under ~/.node-red, pnpm install /path/to/my_custom_node
  • under /path/to/my_custom_node: pnpm install

Will NR remain working if we made such a drop-in change?
Thanks!

First time I've heard of pnpm - so you are blazing the trail. You will have to rely on your own testing to ensure it is working as you need. Your feedback would be useful. (I can't see why it shouldn't work - just I have never seen/tested it)

Thanks for the input, @dceejay .
Guess trial-and-error is inevitable then.

btw, here is a comparison between pnpm, npm, and yarn.

I'm pretty sure this will break uibuilder. It relies heavily on npm and needs to know where modules actually live in some cases in order to be able to make them available as web endpoints. That is hard enough just with npm itself but it looks like pnpm uses an entirely different structure.

Also, uibuilder, like Node-RED itself, assumes that npm will always be available. Since npm does not provide a stable API, you have to call it from an exec.

@TotallyInformation That's unfortunate.
However, we will not remove npm but let it co-exist with pnpm. Will that work?

Our pain point:

  • all the custom nodes share some dependencies,
  • but we found no easy way to share those dependencies across nodes once they get registered into every package.json
  • When shipping all the nodes to customer, the duplication either makes installer bigger or make installation longer

Is there any best practice regarding sharing dependencies across nodes?

Did you already benchmark these topics?
I would assume the difference is neglectable - if you intend to ship a single node as an independent unit:

  • Each node needs it's environment to run, thus all dependencies have to be fulfilled - consequentially either made available online or offline. This is independent of the making of the package manager you use...
  • Once a package is installed, npm doesn't install it a second time. The check for availability consumes almost no time...

If you ship all your nodes in a combined package, the situation is similar - and you can save some bytes only in case you make your dependencies available offline.

Perhaps. Needs testing.

One idea I've had for the future - since uibuilder does have quite a few dependencies, is to find a way to reduce those to dev dependencies and have some kind of build process so that I didn't need "live" dependencies at all. Not quite clever/knowledgeable at present to have worked out exactly how to achieve that. But if it could be, that would be another way out of the issue potentially.

Either way, the use of pnpm is going to need very extensive testing. Unfortunately, not something I can take on right now.

Do you mean that they use actual dependencies, such as published modules on npm, or do they use similar js functions inside the node?

If you find yourself writing the same code at many different places, then just put that into its own module and publish it. The nodes can then require it as a dependency and you can reduce some of the duplication.

If the dependencies are 3rd party modules that you don't own, then as long as you are properly versioning the dependencies with ranges, there should not be multiple copies. Just don't be too explicit with requiring exact versions in each of your nodes, such as depency_a@3.1.4.

@kevinGodell

It's mainly about 3rd-party packages. If we have the following node structures

- node1
  - node_modules/
  - pacakge.json
  - package-lock.json
- node2
  - node_modules/
  - pacakge.json
  - package-lock.json
...

Very soon we'll find ourselves working with lots of node_modules containing duplicates.
Worse, our internal network basically can't reliably npm-install from the internet for security reasons. So we have to ship these node_modules to our uesrs, leading to a huge installer for every little increments.

One strategy we are currently trying is to install everything into ~/.node-red/node_modules. But this seems to force us to use absolute paths when require() dependencies. Not impossible, but very annoying, considering some dependencies of the node dependencies require() things to locate in their desired location such as global installation.

BTW, we've tried to hack module.paths with custom location, but that seems to brings about more troubles than benefits.

npm should try to flatten node_modules anyway - but that can occasionally cause problems - but mostly it seems to be fine in my experience (so far). Yarn is another alternative (that has a --flatten) option . see a comparison here Advanced package manager features for npm, Yarn, and pnpm - LogRocket Blog

The problem though with the alternatives is that they aren't universally used. So no matter what, npm has to be taken into account.

AFAIK, npm is supposed to flatten everything except where it causes a version conflict. So if two modules need different versions of the same dependent module, the first installation should be flat but the 2nd will install relative to the parent. That certainly seems to generally be the case.

Have you tried using the peer dependencies option in package.json? Use that with peer dependencies meta to specify optional false. If your nodes are private and only being used for your application, then you can easily know what the dependencies are and install them directly and make them available for your nodes to use.

npm should try to flatten node_modules anyway
What do you mean by "flatten" here? Do you mean identical dependencies won't duplicate?

Say if I do this

cd node1
npm install --save dependency1
npm install --save-dev dependency2
cd node2
npm install --save dependency1
npm install --save-dev dependency2

AFAIK, they will be installed to their own node_modules as two copies, correct?

@TotallyInformation
Good point. Hence we backed away from the idea of mixing yarn/pnpm with npm. Law of Critical Mass applies here.

That is a very different scenario. There you are adding modules to individual nodes and npm doesn't know about the existence of the other node.

But when a user comes to install the nodes and runs:

npm install node1
npm install node2

They will get those nodes installed under the same node_modules directory and the dependencies will be flattened.

Report:
npm's flattening behavior differs between Windows and macOS

@knolleary

I'm using:

  • Windows: NodeJS v14.17.5, bundled with npm 6.14.14, installed a year go in the LTS repo of NodeJS using an official installer
  • macOS: NodeJS v14.21.2, bundled with npm 6.14.17, installed through brew install node@14
  • node-red 2.0.5

UPDATE: I just upgraded my Windows NodeJS and NPM to the same version on macOS and the problem persists.

My test steps

  • Create a package MyPackage with its own dependencies in package.json
  • Insert this package into ~/.node-red/package.json
  • Run npm install under ~/.node-red

Observation

  • On Windows, MyPackage's dependencies get flattened into ~/.node-red/node_modules
  • On macOS, MyPackage's dependency gets installed into MyPackage/node_modules

Question

  • Is my NodeJS / NPM version too old or is this an expected behavior?
  • What are the recommended versions? From the Getting Started doc, I see the version in the example:
Node.js  version: v14.7.2

That's why I locked it up for my projects onto node@14.
Tips will be greatly appreciated!

The recommended version is documented here: Supported Node versions : Node-RED

How exactly? I assume you haven't published to npm, so what value did you put in package.json? Is it a relative file path? Absolute file path?

1 Like

Absolute paths.

I now have a workaround: just don't npm-install but instead, only npm-link MyPackage into ~/.node-red. This way MyPackage is not listed as a dependency in ~/.node-red/package.json and my node runs fine on both Windows and macOS. But I'm not sure if this is the best practice. It feels over-engineering to me.

I hope by upgrading to recommended NodeJS/NPM version things will improve a bit.
I'll report back when that happens. But any advice is welcome at this point!

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.