I'm developing npm package (my_package) that is using domain-specific C/C++ libraries (c-library) via Node-API. Such setup has a reason, because this C/C++ library is being well maintained and rewriting it in JS/TS would not be feasible.

The problem I'm currently facing is: how do I distribute prebuilt modules of that library?

Normally I would make use of node-gyp, but another constraint I'm having is that my npm package will be distributed via third-party platform, which installs additional modules/plugins by exec'ing npm install <my_package> (and later using some magic to load them as UI and functionality addons). It is not guaranteed that node-gyp is available on this third-party platform, or maybe some of their dependencies are missing (like python3) since there are different docker images of this "platform" with different builds and tools available.

Furthermore, building C/C++ library could take substantial amount of time on small VPS (where the platform is hosted), and as I would like installation process to be as straightforward as possible, I would like to avoid users having to spend their time here. The binary itself is pretty big (15-20MB) and including all prebuilt binaries for all architectures into my_package would also not be a good solution IMO.

Bottom line is: my npm package loads binary lib and it is being installed by running npm install <my_package>, and I cannot influence that. I also cannot influence environment in which my package is installed therefore I cannot rely on node-gyp. How do I deliver pre-compiled binary modules of the library to run on: linux-x64-glibc, linux-x64-musl, linux-arm64-glibc, linux-arm64-musl, darwin-x64, darwin-arm64?

I thought of different options:

  1. Publish arch-specific packages of my_package (i.e. my_package-linux-x64-glibc, my_package-arm64-musl etc). Include "hard-coded" arch-specific dependency to c-library into package.json of my_package.
package.json (of `my_package-linux-x64-glibc`)
{

...
dependencies: {
    `c-library-linux-x64-glibc`: "*"
}
...
}

I do not like it, because it makes installation process more complicated for the user: they need to be aware where their platform is hosted and install exact "variation" of my_package that will run there. Kind of complicated.

  1. Publish my_package as one "generic" package. Include arch-specific dependency to c-library in package.json based on some conditioning which will be checked when npm install my_package is executed.

User would not have to deal with all these process/architecture things, but rather simply install one "facade" my_package, that evaluates environment at runtime and pulls in prebuilt c-library (for example c-library-linux-x64-musl) depending on conditions.

package.json (of `my_package`)
{
...
dependencies: {
    'linux-x64': {
        `c-library-linux-x64-glibc`: "*"
    }
}
...
}

Problem: I'm not able to include dependencies that are based on conditions in package.json of my_package.

  1. Publish my_package as one "generic" package. Use postinstall.js lifecycle script to do runtime evaluation of environment and then exec "npm install c-library-" + process.platform + "-" + process.arch" to fetch proper prebuilds.

This was a breakthrough idea, but apparently, because npm install c-library-... is executed from within the npm install my_package command's "scope", it is removing all the dependencies of my_package. Due to the fact that npm install my_package is updating package-lock.json file after postinstall scripts have been executed. Postinstall is run on line 95, dependencies are saved/added starting from line 110:

95 info run [email protected] postinstall node_modules/my_package node postinstall.js install
96 info run [email protected] postinstall { code: 0, signal: null }
97 timing build:run:postinstall:node_modules/my_package Completed in 61ms
98 timing build:run:postinstall Completed in 61ms
99 timing build:deps Completed in 63ms
100 timing build Completed in 63ms
101 timing reify:build Completed in 63ms
102 timing reify:trash Completed in 0ms
103 timing reify:save Completed in 3ms
104 http fetch POST 200 http://0.0.0.0:4873/-/npm/v1/security/advisories/bulk 464ms
105 timing auditReport:getReport Completed in 468ms
106 silly audit report {}
107 timing auditReport:init Completed in 0ms
108 timing reify:audit Completed in 470ms
109 timing reify Completed in 2007ms
110 silly ADD node_modules/ms
111 silly ADD node_modules/typedi
  1. Use node-pre-gyp that would try to find prebuilt version of c-library somewhere in aws s3, if it is not found - fallback to compilation in node-gyp way.

I have not tried that, and I'm a bit sceptical here because of different platforms/architectures and libc variants that need to be supported. I would also need to include source code of c-library as git submodule of my_package which is maybe not the best way to organise components. Additionally, it does not look that node-pre-gyp is being regularly maintained.

I would appreciate if someone could share their idea on the above and perhaps open my eyes on some other option that I have not still discovered.

0

There are 0 best solutions below