doc/MIGRATION_GUIDE_NAPI.md
Since v10, Node.js supports an improved API for building native modules, known as N-API. N-API offers a clearer, more complete, and more stable API layer for writing Node.js plugins than previous Node versions did.
The Neon community has been hard at work porting the library to a new backend based on N-API.
Some key benefits of the new backend include:
Porting Neon to N-API has been mostly transparent, but it has required a few backwards-incompatible changes. This guide provides instructions on how to migrate existing apps to the new N-API backend.
Fortunately, the guaranteed stability of N-API means that once Neon users do this migration, we have increased confidence in the stability of Neon. We expect this to be the last major breaking change before reaching Neon 1.0.
If you have any trouble porting, please reach out to us with a Neon issue or on the community Slack! We want to help everyone upgrade as smoothly and seamlessly as possible.
The N-API backend of Neon requires a minimum Node version of 10.0.
To enable the N-API backend, you need to:
build.rs from the project directory and build = "build.rs" from the Cargo.toml. The N-API backend does not require a Cargo build script.default-features = false; andCargo.toml to select the N-API version you need support for (each N-API version N uses the feature flag "napi-N", for example "napi-4" for N-API version 4).As a rule, you should choose the oldest version of N-API that has the APIs you need. (We will be adding N-API version requirements to the Neon API docs to make this clearer in the future.) You can consult the official N-API feature matrix to see which N-API versions come with various versions of Node.
[dependencies.neon]
version = "0.9.1"
default-features = false
features = ["napi-4"]
Many methods that previously did not require context (e.g., JsString::size) now require a context. In many cases, this means adding an additional argument or using a convenience method on the Context trait.
Handle
is_adowncastHandle::downcast also requires a second type argument for the context type. This can usually be inferred, so you can typically use _.
Before:
value.downcast::<JsNumber>()
After:
value.downcast::<JsNumber, _>(&mut cx)
JsBoolean
valueJsNull
newJsString
sizevalueJsNumber
valueJsUndefined
newPropertyKey
get_fromset_fromHandles no longer implement Eq or PartialEq, which had underspecified behavior. Use Value::strict_equals instead to invoke the behavior of JavaScript's === operator.
The N-API backend introduces two categories of significant change:
declare_types! (i.e. classes) macro, but through a simpler primitive: the JsBox API.The declare_types! macro is deprecated and replaced by the JsBox type.
Rationale: The declare_types! macro provides a syntax for defining classes, but requires substantial boilerplate and is unergonomic for simple cases and tends to interact poorly with IDEs. It's also not flexible enough to express the full range of JavaScript classes syntax and semantics. With the JsBox type, it's easy to embed Rust data in JavaScript objects, which can then be nested inside of more feature-rich classes defined in pure JavaScript (or TypeScript).
Before:
struct User {
first_name: String,
last_name: String,
}
impl User {
fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
declare_types! {
class JsUser for User {
init(mut cx) {
let first_name = cx.argument::<JsString>(0)?;
let last_name = cx.argument::<JsString>(1)?;
Ok(User { first_name, last_name })
}
method full_name(mut cx) {
let this = cx.this();
let guard = cx.lock();
let user = this.borrow(&guard);
let full_name = user.full_name();
Ok(cx.string(full_name).upcast())
}
}
}
After:
On the Rust side, the wrapped type must implement the Finalize trait, but this comes with a default implementation so it can be implemented with an empty impl block:
struct User {
first_name: String,
last_name: String,
}
impl Finalize for User { }
The type can then be exposed to JavaScript with simple functions that wrap User in a JsBox:
fn create_user(mut cx: FunctionContext) -> JsResult<JsBox<User>> {
let first_name = cx.argument::<JsString>(0)?.value(&mut cx);
let last_name = cx.argument::<JsString>(1)?.value(&mut cx);
Ok(cx.boxed(User { first_name, last_name }))
}
fn user_full_name(mut cx: FunctionContext) -> JsResult<JsString> {
let user = cx.argument::<JsBox<User>>(0)?;
let full_name = user.full_name();
Ok(cx.string(full_name))
}
Finally, you can provide an idiomatic JavaScript interface to the type by wrapping the boxed type in a class:
class User {
constructor(firstName, lastName) {
this.boxed = addon.createUser(firstName, lastName);
}
fullName() {
return addon.userFullName(this.boxed);
}
}
The supported mechanism for concurrency is the Channel API (neon::event::Channel). This feature has not yet stabilized, so to use this API, you'll also need to enable the "channel-api" feature flag as well:
[dependencies.neon]
version = "0.9.1"
default-features = false
features = ["napi-6", "channel-api"]
The Task API (neon::task) is deprecated, and should in most cases be translated to using the Event Queue API.
Rationale: The Task API was built on top of the low-level libuv thread pool, which manages the concurrency of the Node.js system internals and should rarely be exposed to user-level programs. For most use cases, Neon users took advantage of this API as the only way to implement background, asynchronous computations. The Event Queue API is a more general-purpose, convenient, and safe way of achieving that purpose.
That said, if you believe you need access to the libuv thread pool, please file an issue in the Neon repository with a description of your use case to let us know about it. We don't believe this is commonly needed, but we don't want to leave you stuck!
Before:
With the Task API it was possible to define background computations off the main JavaScript thread, but these could only be run within the libuv thread pool--which runs all the system logic for the internals of Node.js. This gave Neon programmers a real power but forced them to contend with Node.js system tasks.
impl Task for MyTask {
type Output = i32;
type Error = String;
type JsEvent = JsNumber;
fn perform(&self) -> Result<Self::Output, Self::Error> {
// compute the result...
}
fn complete(self, mut cx: TaskContext, result: Result<Self::Output, Self::Error>) -> JsResult<Self::JsEvent> {
match result {
Ok(n) => {
Ok(cx.number(n))
}
Err(s) => {
cx.throw_error(s)
}
}
}
}
pub fn start_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let callback = cx.argument::<JsFunction>(0)?;
MyTask.schedule(callback);
Ok(cx.undefined())
}
After:
With the N-API backend, Neon programmers can use their own native threads and avoid competing with the Node.js system internals. This also brings some convenience since it doesn't require defining any custom trait implementations.
pub fn start_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
let queue = cx.queue();
std::thread::spawn(move || {
let result = // compute the result...
queue.send(move |mut cx| {
let callback = callback.into_inner(&mut cx);
let this = cx.undefined();
let args = match result {
Ok(n) => vec![
cx.null().upcast::<JsValue>(),
cx.number(result).upcast()
],
Err(msg) => vec![
cx.error(msg).upcast()
]
};
callback.call(&mut cx, this, args)?;
Ok(())
});
});
Ok(cx.undefined())
}
The Event Handler API (neon::event::EventHandler) is deprecated and should be replaced by the Event Queue API.
Rationale: The Event Handler API had multiple issues with safety, memory leaks, and ergonomics (1, 2).
Before:
pub fn start_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let callback = cx.argument::<JsFunction>(0)?;
let handler = EventHandler::new(callback);
thread::spawn(move || {
let result = // compute the result...
handler.schedule(move |cx| {
vec![cx.number(result).upcast()]
});
});
Ok(cx.undefined())
}
After:
pub fn start_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
let queue = cx.queue();
std::thread::spawn(move || {
let result = // compute the result...
queue.send(move |mut cx| {
let callback = callback.into_inner(&mut cx);
let this = cx.undefined();
let args = vec![
cx.null().upcast::<JsValue>(),
cx.number(result).upcast()
];
callback.call(&mut cx, this, args)?;
Ok(())
});
});
Ok(cx.undefined())
}