One of the limitations of native addons is that they must be compiled for each target platform and architecture. Without pre-built binaries, every user who installs your package must have a working C/C++ toolchain on their machine.
node-pre-gyp solves this by letting you build binaries ahead of time, upload them to a remote location, and have users download the right binary at install time - falling back to compiling from source only if a matching binary is not available.
Note that Node-API support was added to node-pre-gyp in version 0.8.0.
prebuild is an alternative tool that addresses the same problem.
This page describes the changes required to a Node-API addon to support node-pre-gyp.
By default, node-pre-gyp uploads binaries to Amazon S3.
The node-pre-gyp-github module adds support for publishing to GitHub Releases instead.
Before uploading you need:
- An Amazon Web Services account.
- An IAM user or role with permission to upload to S3.
- An S3 bucket to host the binaries.
Never store credentials in your repository. node-pre-gyp supports two common approaches for providing credentials during development:
-
A
~/.node_pre_gyprcfile:{ "accessKeyId": "xxx", "secretAccessKey": "xxx" } -
Environment variables:
export node_pre_gyp_accessKeyId=xxx export node_pre_gyp_secretAccessKey=xxx
For CI environments, prefer IAM roles or short-lived credentials rather than long-lived access keys. See the node-pre-gyp credentials documentation for additional options.
The package is now published under the @mapbox scope. Use aws-sdk as a dev dependency for the upload step.
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.0"
},
"devDependencies": {
"aws-sdk": "^2.0.0"
}The install script should invoke node-pre-gyp with --fallback-to-build so that users who don't have a pre-built binary available can still compile locally:
"scripts": {
"install": "node-pre-gyp install --fallback-to-build"
}The binary property tells node-pre-gyp which Node-API versions your addon supports and where to find/upload binaries:
"binary": {
"module_name": "your_module",
"module_path": "./lib/binding/napi-v{napi_build_version}",
"remote_path": "./{module_name}/v{version}/{configuration}/",
"package_name": "{platform}-{arch}-napi-v{napi_build_version}.tar.gz",
"host": "https://your_bucket.s3.us-west-1.amazonaws.com",
"napi_versions": [3]
}Set module_name to a valid C identifier. The napi_versions array lists which Node-API versions to build for; 3 is a reasonable minimum for most addons.
See the node-pre-gyp docs for a complete reference, including Node-API considerations.
Add a post-build target to copy the compiled binary to the path specified by module_path:
{
"target_name": "action_after_build",
"type": "none",
"dependencies": ["<(module_name)"],
"copies": [
{
"files": ["<(PRODUCT_DIR)/<(module_name).node"],
"destination": "<(module_path)"
}
]
}Include the Node-API version in the first target's defines so the header files configure themselves correctly:
"defines": [
"NAPI_VERSION=<(napi_build_version)"
]JavaScript code that loads the native binary must dynamically resolve the path to the correct .node file:
const binary = require('@mapbox/node-pre-gyp');
const path = require('path');
const bindingPath = binary.find(
path.resolve(path.join(__dirname, './package.json'))
);
const binding = require(bindingPath);Once everything is in place, build from source:
npm install --build-from-source./node_modules/.bin/node-pre-gyp package
./node_modules/.bin/node-pre-gyp publishUse GitHub Actions to build, test, and publish binaries for multiple platforms and architectures. A typical workflow matrix covers ubuntu-latest, macos-latest, and windows-latest, plus any architecture variants you need (e.g. x64, arm64). See the node-pre-gyp repository for example workflow configurations.