Skip to content

Make Node native addon packages an explicit unsupported compilePackages category #4961

Closed

Description

@JagritGumber

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-build
  • bindings
  • prebuilds/<platform>-<arch>/*.node
  • binding.gyp
  • package.json containing "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.compilePackages is 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 .node loader 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 compilePackages silently 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:

  • *.node
  • binding.gyp
  • prebuilds/
  • package.json containing "gypfile"
  • ideally, common loader dependencies or files such as node-gyp-build / bindings where 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.compilePackages is for
  • why Node native addons are different from JS packages
  • why .node files 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.md
  • docs/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 compilePackages package with binding.gyp
  • rejects a package with prebuilds/.../*.node
  • rejects a package with a direct *.node
  • rejects a package with "gypfile" in package.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, and perry.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.compilePackages fails early for packages that ship Node native addon markers.
  • The failure message is actionable and points to perry.nativeLibrary.
  • Existing perry.nativeLibrary packages still resolve normally.
  • Existing pure JS/TS compilePackages behavior 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 .node files.

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-pty compatibility 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions