DocsIntroductionGetting started

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 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 just JavaScript codes, which actually load the native binary from per platforms.
  • @cool/core-darwin-x64 for macOS x64 platform.
  • @cool/core-win32-x64 for Windows x64 platform.
  • @cool/core-linux-arm64-gnu for Linux aarch64 platform.

In every platform binary package, there are cpu and os fields in there package.json:

package.json
{
  "name": "@cool/core-darwin-x64",
  "version": "1.0.0",
  "os": ["darwin"],
  "cpu": ["x64"]
}

And @cool/core using these native packages as optionalDependencies:

package.json
{
  "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:

index.js
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

package-template

  1. Go to GitHub template project
  2. Click Use this template.
  3. Clone your project.
  4. Run yarn install to install dependencies.
  5. Run npx napi rename command under the project folder to rename your package.