Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you pack multiple native modules into the same NPM package?

I've written a cross-platform nodejs native module in C using cmake, musl, plus a few abstractions and carefully placed #ifdefs.

It is built into a module using cmake-js and I have 3 build servers: Windows, Mac and Linux doing the buliding so I have 3 native modules that work fine on all the platforms.

We have an internal NPM repository that I could go ahead and publish to so that other teams within the company can simply npm install module to get my module.

The thing is, it's kind of messy that the client would need to pick a module based on the platform they're on (i.e. I would deploy all 3 modules from each of the build servers) so I would rather deploy a single module by somehow merging together the 3 platform specific modules.

Is this possible and how do I structure the internals of such a module?*

Does, for example, bindings figure out the platform and find the appropriate .node file?

*I don't want to include the code and have the client compile it as that puts a pretty large burden on the client (they would be expecting to be able to write a simple script using Javascript, for example, and use my module - it would be a shock that they need to set up C build chains, worry about the platform their script is running on etc, etc, etc).

like image 617
kmp Avatar asked Dec 29 '17 08:12

kmp


1 Answers

The most simple solution that comes to my mind - if you packed your code into modules for different platforms then you've got package like:

  • fancy-module-linux
  • fancy-module-windows
  • fancy-module-mac

You can also provide a common module let's say fancy-module.

In a most basic scenario:

fancy-module is an empty module with just a README

You provide custom install script in the package.json

{
  ...
  "scripts": {
     "install": "node ./install_platform_dep.js"
  }
}

Then when installing fancy-module the install_platform_dep.js script will be executed. Inside install_platform_dep.js you place:

// For Windows...
if(process.platform === 'win32') {
    // Trigger Windows module installation
    exec('npm install fancy-module-windows', (err, stdout, stderr) => {
         // Some error handling...
    }
} else if ... // Process other OS'es

You don't have to even publish anything separately! Alternatively you can just put everything into fancy-module:

package.json
README.md
src
  |- Windows
  |  # Here you've got just copied contents of `fancy-module-windows`
  |- Linux
  |  # This folder have same contents as `fancy-module-linux`
  \- Mac
     # Same

And do exec('npm install ./src/Windows' /* ... */)

And you get one nice single multiplatform bundle.

There's no other way to achieve multiplatform module than doing what I described.

Anyway it's rather ugly to provide installer-like module just to install stuff but afik that's the most straightforward option.

Many packages (built around Electron and paltform dependent stuff) on npm are just installers to install other packages, so it's a good way to go.

Edit:

For os detection you can use modules like: is-linux or is-windows etc. and not worry about process.platform

Read more about is-linux
Read more about is-windows
Read more about process.paltform
Read more about install hook in package.json

Edit #2:

Alternatively you can use already made package node-pre-gyp as described here:

Cross platform addon using node-pre-gyp

This package does in reality something to the described approach.

like image 85
Piotr Styczyński Avatar answered Oct 12 '22 03:10

Piotr Styczyński