Electron
Electron's main process is Node.js, so it loads the same Node-API addon crossbind builds for Node.js. The window has no Node.js: a preload script exposes the calls it needs, and the main process answers them over IPC.
Setup
Start from the template:
Or, in an existing app, add this config and build the addon:
C++ goes in src/native. The build writes this machine's addons, one per architecture, as dist/my-app.<platform>-<arch>.node, and the loader dist/my-app.native.cjs, which picks one at run time. -p darwin,linux,win32 builds all three desktop platforms.
The addon in the main process
Load the addon in the main process and answer the window over IPC:
The page calls await window.native.sample() and keeps Electron's sandbox. A prebuilt port works the same way: install its -standalone-napi package and import it in the main process, as on Node.js.
Nothing is rebuilt per Electron version. Node-API is ABI-stable, so the addon Node.js loads also loads in Electron, and electron-rebuild has nothing to do; a Windows addon looks Node-API up in the process that loads it, so node.exe and electron.exe load the same file. Values cross the boundary already converted, so JavaScript never holds an ArrayBuffer over native memory, which Electron's V8 memory cage refuses.
Packaging
A packaged app keeps crossbind's output outside its asar archive. Electron loads an addon from there as it is, and a library's native code reads its data, such as proj.db, with file APIs that cannot open a file inside the archive; in a packaged app the loader reads the data from app.asar.unpacked. With electron-builder:
directories.output moves electron-builder's own output out of dist, its default, where crossbind writes; dist/prebuilt holds the static archives the addon was linked from, which the app does not need. electron-builder --dir writes the app to out/, and electron-builder alone makes the installers. Package each platform with its addons in dist: the loader picks the one for the machine it runs on.
A -standalone-napi port keeps its data in its own dist/data, so unpack node_modules/@crossbind/** as well. Electron Forge takes packagerConfig.asar.unpackDir: 'dist', and under pnpm it also wants node-linker=hoisted.
Limits
- macOS addons build only on a Mac with Xcode's command line tools. Linux and Windows addons build in Docker on any host, a Mac included.
- One Node environment per process. The addon cannot load again in a
worker_threadsworker of the main process;initNative()there rejects with an error.
examples/desktop-electron is a complete app: the addon in the main process, the preload, electron-builder packaging and a Playwright test that opens the app from its sources and packaged. CI runs it with Electron 44 on macOS, Linux and Windows.