@devicefarmer/prebuilt-bindings v1.0.5
node-prebuilt-bindings
node-prebuilt-bindings is an alternative to node-pre-gyp. It allows your users to automatically fetch prebuilt native modules from a location you provide (e.g. GitHub Releases), avoiding messy compilation issues.
Benefits/philosophy
Pros
- Leave legacy cruft behind. Only support most recent Node in active Long-term Support (currently v4) and newer.
- Take advantage of ES2015 and later features (but don't require
--harmonyflag).
- Take advantage of ES2015 and later features (but don't require
- Configuration is code with sensible defaults.
- Defaults to using GitHub Releases.
- Easily implement features such as only building on specific platforms.
- Does not take over
node-gyp.- Allows you to run
node-gypdirectly if you wish. - Works great with windows-build-tools.
- Allows you to run
- Supports multiple bindings per module.
- Super easy nvm integration.
- Global installation not required!
- Minimal bloat-free implementation.
- Dependency free!
Cons
The current limitations are as follows.
- Not compatible with old Node.js versions.
- Your module won't be either.
- Does not yet support
http_proxyand friends. - Does not support overriding hosted binary location at install time.
- Does not support multiple files (e.g. generated artifacts) per binding.
If any of these is a deal-breaker, you might want to consider node-pre-gyp instead.
Installation
Do not install prebuilt-bindings globally. Do the following to add it to your package instead.
Using yarn:
yarn add prebuilt-bindingsUsing npm:
npm install --save prebuilt-bindingsNow, create a file called prebuilt-bindings.js in your module's root folder. The filename does not actually matter, but it's a good convention to keep it that way.
The contents of the file should look like this:
const prebuild = require('prebuilt-bindings')
module.exports = prebuild({
context: __dirname,
bindings: [{
name: 'put_the_name_of_your_module_here'
}]
})This simple config file is all you need to get going with GitHub Releases. The remote location is inferred from your package.json's repository field and does not need to be set.
Now, modify your package.json as follows to include an installation script:
"scripts": {
"install": "node ./prebuilt-bindings install",
"prebuilt-bindings": "node ./prebuilt-bindings"
}This replaces the built-in npm install script, which is just node-gyp rebuild by default.
Finally, you should have the following entries in your .gitignore and .npmignore:
/*.node.gz
/build/The first one makes sure that you won't accidentally commit or embed the prebuilt bindings, and the second one does the same for the build folder.
Usage
So, prebuilt-bindings is not installed globally and doesn't come with a binary. How exactly do you run it?
Thanks to the config we just set up, it's easy:
npm run prebuilt-bindings -- helpAll arguments after -- are passed to prebuilt-bindings.
Note that you can simply replace npm with yarn if you like.
Okay, but why go through npm? The reason is that npm manages a large collection of environment variables that are essential to building native bindings, such as http_proxy settings, the location of python on Windows when using tools like windows-build-tools, and that node-gyp is prepended to PATH for the build fallback. Duplicating all of these would add hundreds of lines of error-prone code. It's much better to just rely on npm or yarn to set them for us. It also ensures that you're building with the correct version of node-gyp.
Aside from building, you can also use node ./prebuilt-bindings.js if you prefer. However, try the recommended way if you run into any issues.
Now that we've got that out of the way, let's get to packing.
npm run prebuilt-bindings -- clean build packThis should create a properly named archive for you to attach to the corresponding GitHub release. Simply upload the files and any new installs of your module will fetch them.
Recommended release flow
- Tag a new release, preferably with
npm version <patch|minor|major>. - For each Node version you want to prebuild for, run
npm run prebuilt-bindings -- clean build pack. See nvm integration below for how to build for multiple versions. - Run
npm packto preview the archive you're about to publish. - Run
tar tf *.tgzand make sure you're not accidentally embedding native modules to the archive. Remove unwanted files and/or add them to.npmignore. Then go back to the previous step and check again. - Push your tag and branch to GitHub with
git push -u origin master --tags. - On the Releases tab in GitHub, select the new tag and create a release. Attach the prebuilt binding files to the release.
- Run
npm publish.
Please feel free to script or automate these steps.
nvm integration
With nvm exec, it's easy to build and pack bindings for each version you support. For example, you might do the following:
nvm exec 4 npm run prebuilt-bindings -- clean build pack
nvm exec 5 npm run prebuilt-bindings -- clean build pack
nvm exec 6 npm run prebuilt-bindings -- clean build pack
nvm exec 7 npm run prebuilt-bindings -- clean build packYou should now have a properly named archive for each version. Simply attach the files to the corresponding GitHub Release.
Only build for some platforms
This is a thing that isn't easily doable with plain node-gyp. Thanks to our configuration being actual code, it's easy to achieve with prebuilt-bindings:
const prebuild = require('prebuilt-bindings')
module.exports = prebuild({
context: __dirname,
bindings: (() => {
switch (process.platform) {
case 'darwin':
return [{
name: 'put_the_name_of_your_module_here'
}]
default:
return []
}
})()
})With this configuration, bindings only get downloaded and built on macOS. Other platforms simply do nothing since there are no bindings set.
Naturally, there are endless ways to write the configuration. You could do something like this to avoid even the require on other platforms:
switch (process.platform) {
case 'darwin': {
const prebuild = require('prebuilt-bindings')
module.exports = prebuild({
context: __dirname,
bindings: [{
name: 'put_the_name_of_your_module_here'
}]
})
break
}
}It isn't strictly necessary to export the returned promise, but it may assist in future tooling.
Gotchas
If building fails with Error: spawn node-gyp ENOENT, it can pretty much only mean that you tried to run node ./prebuilt-bindings.js directly. See Usage for an explanation of why that doesn't work when building.
License
See LICENSE.