Getting started
Start from @napi-rs/cli
The recommend way.
Install cli
yarn global add @napi-rs/cli
# or
npm install -g @napi-rs/cli
# or
pnpm add -g @napi-rs/cli
Create project
napi new
Package name
The name filed in package.json
.
Choose targets you want to support
Platforms you want support to.
Enable GitHub actions
Generate GitHub actions config for you.
Deep dive
Here it is recommended to distribute your package under npm scope (opens in a new tab) because @napi-rs/cli
by default appends the different platform suffixes to the npm package name as the package name for the different platform binary distribution. Using npm scope will reduce the case of package name was taken.
For example if you want publish package @cool/core
, with the macOS x64
, Windows x64
and Linux aarch64
supported, @napi-rs/cli
will create and publish four packages for you:
@cool/core
includes justJavaScript
codes, which actually load the native binary from per platforms.@cool/core-darwin-x64
formacOS x64
platform.@cool/core-win32-x64
forWindows x64
platform.@cool/core-linux-arm64-gnu
forLinux aarch64
platform.
In every platform binary package, there are cpu
and os
fields in there package.json
:
{
"name": "@cool/core-darwin-x64",
"version": "1.0.0",
"os": ["darwin"],
"cpu": ["x64"]
}
And @cool/core
using these native packages as optionalDependencies
:
{
"name": "@cool/core",
"version": "1.0.0",
"optionalDependencies": {
"@cool/core-darwin-x64": "^1.0.0",
"@cool/core-win32-x64": "^1.0.0",
"@cool/core-linux-arm64": "^1.0.0"
}
}
And your index.js
in @cool/core
will be this:
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let isMusl = false
let loadError = null
switch (platform) {
case 'darwin':
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'core.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./core.darwin-x64.node')
} else {
nativeBinding = require('@cool/core-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'core.darwin-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./core.darwin-arm64.node')
} else {
nativeBinding = require('@cool/core-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
// ...
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { plus100 } = nativeBinding
module.exports.plus100 = plus100
The generated index.js
file will help you to load the right binary file wherever you are. And the index.js
handle two cases:
Package installed in users node_modules
To load the correct binary, the index.js
function tries to load all possible packages for that platform (there may be multiple possible binary packages for a given system and CPU architecture), for example, on the Linux x64
platform, index.js
tries to load @cool/core-linux-x64-gnu
and @cool/core-linux-x64-musl
. The package @cool/core-linux-x64-gnu
will be loaded if the user is using an operating system like Ubuntu
Debian
with gnu libc
pre-installed. And if the user is using an operating system like Alpine
with musl libc
pre-installed, then @cool/core-linux-x64-musl
will be loaded.
Local development
The build
command in package.json in the project generated by the @napi-rs/cli
new command will generate the binary dynamic link library compiled from the Rust
code into the current directory for debugging purposes. index.js
will also try to load the corresponding binary from the current directory in this case. Again using Linux x64
as an example, the index.js
function will try to load the core.linux-x64-gnu.node
and core.linux-x64-musl.node
files in turn.
IDE support problem
If your IDE refuses to autocomplete/autosuggest code when using the #[napi]
macro, you can use the following setting to fix this:
For vscode in settings.json
:
{
"rust-analyzer.procMacro.ignored": { "napi-derive": ["napi"] }
}
For Neovim.
['rust-analyzer'] = {
procMacro = {
ignored = {
['napi-derive'] = { 'napi' },
},
},
},
This problem emits the following error in rust-analyser:
[ERROR proc_macro_api::msg] proc-macro tried to print : `napi` macro expand failed.
Start from GitHub template project
- Go to GitHub template project (opens in a new tab)
- Click Use this template.
- Clone your project.
- Run
yarn install
to install dependencies. - Run
npx napi rename
command under the project folder to rename your package.