Repository navigation
Make Node native addon packages an explicit unsupported compilePackages category #4961
Description
The problem
perry.compilePackages is currently easy to misuse with npm packages that ship Node native addons.
A package can look like ordinary JavaScript from the entry point, but internally load a native .node binary through patterns such as:
require("./build/Release/addon.node")node-gyp-buildbindingsprebuilds/<platform>-<arch>/*.nodebinding.gyppackage.jsoncontaining"gypfile": true
Perry's porting docs already classify these packages as "don't port" candidates, but the compiler does not currently enforce that boundary. If one of these packages is added to perry.compilePackages, Perry can compile the JavaScript wrapper and then fail later with a misleading native-module load, dynamic require, link, or runtime error.
Example failure class:
Error: Failed to load native module: .../prebuilds/win32-x64/pty.node
require() is not supported: ./prebuilds/win32-x64/pty.node
This is not just a missing require() lowering. A .node addon is compiled against Node's native addon ABI: Node-API/N-API, NAN, V8, libuv, or Node internals. Perry cannot safely support those binaries by source-compiling the surrounding JavaScript package.
Proposed solution
Make Node native addon packages an explicit unsupported category for perry.compilePackages.
If a package is explicitly listed in perry.compilePackages and Perry detects that the package ships a Node native addon, compilation should stop during module collection with a clear diagnostic.
The diagnostic should say:
perry.compilePackagesis for JavaScript / TypeScript packages.- Node native addon packages cannot be compiled by wrapping their JS entry point.
- Perry does not currently host arbitrary
.node/ N-API / NAN / V8 addons. - The supported replacement path is a thin Perry native binding using
perry.nativeLibrary. - The package should be removed from
perry.compilePackages, replaced with a Perry-compatible wrapper, or left unsupported for native compilation.
This should be treated as a compiler / docs improvement, not as a package-specific workaround.
Non-goals
- Do not implement a generic
.nodeloader in this issue. - Do not implement a generic N-API host in this issue.
- Do not rewrite npm packages wholesale in Rust.
- Do not make
compilePackagessilently ignore native addon files. - Do not special-case one application or one dependency as the whole solution.
The intended long-term model is:
- normal JS/TS packages:
perry.compilePackages - native boundary packages:
perry.nativeLibrary - unsupported Node native addons: clear compile-time error
- selected high-value packages: thin Perry-native compatibility wrappers
Requirements
Detection
When collecting modules for a package that is in perry.compilePackages, Perry should detect common native-addon markers under that package root:
*.nodebinding.gypprebuilds/package.jsoncontaining"gypfile"- ideally, common loader dependencies or files such as
node-gyp-build/bindingswhere cheap and reliable
Detection should avoid walking unrelated dependency trees deeply enough to become expensive. It is enough to scan the target package root with bounded depth and skip nested node_modules / .git.
Behavior
If a native-addon marker is found inside a compilePackages target, Perry should fail before codegen/link/runtime.
The error should include:
- package name
- detected marker
- path where it was found
- explanation that Node native addons are unsupported through
compilePackages - suggested fix using
perry.nativeLibrary/ perry-ffi
Example shape:
Error: package `node-pty` is in `perry.compilePackages` but uses a Node native addon (binding.gyp) at <path>.
`perry.compilePackages` can compile JavaScript / TypeScript packages, but it cannot load Node `.node` / N-API addons inside a native Perry binary.
Remove `node-pty` from `perry.compilePackages`, use a JS-runtime fallback, or replace it with a Perry native binding (`perry.nativeLibrary` / perry-ffi).
Preserve Perry-native packages
Packages that declare perry.nativeLibrary must not be rejected by this check merely because they are native in Perry's own supported way.
The distinction should stay clear:
- Node native addon: unsupported through
compilePackages - Perry native binding: supported through
perry.nativeLibrary, subject to the existing allowlist
Documentation
Add or update docs covering:
- what
perry.compilePackagesis for - why Node native addons are different from JS packages
- why
.nodefiles cannot be loaded by Perry today - when to use
perry.nativeLibrary - how to think about thin replacement wrappers
- examples of packages in this category:
node-pty,sharp,better-sqlite3,sqlite3, etc. - target availability guidance: unsupported platforms should be declared clearly, not fail mysteriously
Suggested docs locations:
docs/src/packages/porting.mddocs/src/cli/allow-perry-features.md- native bindings overview / authoring guide
Also check whether any stale references to a JS runtime / QuickJS fallback should be updated or qualified, since current compiler comments indicate JS-runtime fallback paths have changed.
Tests
Add focused tests for module collection / compile package resolution:
- rejects a
compilePackagespackage withbinding.gyp - rejects a package with
prebuilds/.../*.node - rejects a package with a direct
*.node - rejects a package with
"gypfile"inpackage.json - allows a normal JS/TS package in
compilePackages - does not reject a package that declares
perry.nativeLibrary - error message mentions the package name, marker,
perry.compilePackages, andperry.nativeLibrary
If practical, add a small fixture modeled after node-pty because it exercises a real package shape without requiring the actual dependency.
Alternatives considered
Let runtime fail
This is the current behavior for some packages. It produces late and misleading errors, especially when the JS wrapper compiled successfully but the native addon load fails later.
Add generic .node loading
Loading the shared library is not enough. Node addons expect Node's native addon ABI and runtime objects. A generic loader would require hosting enough of Node-API/N-API, NAN, V8, libuv, error handling, callback scopes, thread-safe functions, cleanup hooks, and memory ownership semantics to be meaningful.
That is future work at best and should not be implied by compilePackages.
Rewrite packages in Rust
This does not scale and risks creating a separate Perry ecosystem that behaves differently from Node. The better policy is to replace only the native boundary when a package is important enough to support.
For example, a node-pty compatibility package should expose the public node-pty API but implement only the PTY native boundary through perry.nativeLibrary.
Scope
- Large - cross-cutting compiler diagnostic, package scanning, docs, and regression tests
Acceptance criteria
perry.compilePackagesfails early for packages that ship Node native addon markers.- The failure message is actionable and points to
perry.nativeLibrary. - Existing
perry.nativeLibrarypackages still resolve normally. - Existing pure JS/TS
compilePackagesbehavior is unchanged. - Docs explain the limitation and replacement path.
- Tests cover the main marker types and the native-library exception.
- No attempt is made to load arbitrary
.nodefiles.
Prior art / context
Node documents native addons as dynamically-linked shared objects loaded by require() and implemented through Node-API, NAN, or direct V8/libuv/Node APIs.
Node-API is the stable subset, but it is still a host ABI with opaque values, environments, async work, promises, thread-safe callbacks, memory accounting, and event-loop integration.
Bun supports .node files by implementing most of Node-API from scratch. That is useful prior art, but it also shows that generic .node support is a runtime compatibility project, not a compilePackages fix.
Deno similarly treats Node-API addons as a special compatibility path tied to node_modules behavior.
Follow-up work
This issue should establish the policy and diagnostics. Follow-ups can add package-specific thin wrappers where useful.
Possible follow-ups:
node-ptycompatibility package backed by a Perry native library- docs/examples for replacing a native addon boundary
- better detection of common addon loaders
- future research issue for partial N-API hosting, if Perry ever wants to explore it
Activity