Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAPI-RS 3.0 #1493

Open
Brooooooklyn opened this issue Feb 22, 2023 · 4 comments
Open

NAPI-RS 3.0 #1493

Brooooooklyn opened this issue Feb 22, 2023 · 4 comments

Comments

@Brooooooklyn
Copy link
Sponsor Member

Brooooooklyn commented Feb 22, 2023

Goals

The goal of NAPI-RS 2.0 was to make NAPI-RS easier to use, and I believe it has accomplished that goal, with more and more projects using NAPI-RS to build Node.js addons as the barrier to use becomes lower. However, as more and more projects use NAPI-RS, there are some issues that come to light. We hope to solve them in 3.0.

Some abstractions do not fit into the philosophy of Rust

ThreadsafeFunction

Should not have abort API.

The ThreasafeFunction should abort automatically in drop. This behavior should be tied to the concept of ownership in Rust instead of a explicit API. The fn abort(&self) in ThreadsafeFunction introduces a lot of complexity and introduces a lot of mental overhead into the use of ThreadsafeFunction.
We should remove fn abort(&self) in 3.0, and this would introduce a breaking change.

Buffer/TypedArray in fn params is not zero overhead

To provide a more convenient API, NAPI-RS functions automatically create references (napi_create_reference) to Buffer and TypedArray when they are passed in. This allows the data held by Buffer and TypedArray to be accessed correctly, both in async fn and in AsyncWork. But this behavior also introduces unnecessary overhead for the plain fn.
In 3.0, we should enforce our users to pass Reference<Buffer>/BufferRef in if they want to use the Buffer/TypedArray in the async scenario.

Buffer/TypedArray and &mut _ class in async fn is unsound

In fact, in NAPI-RS, any assumption that Rust code holds a mutable borrow of JavaScript values is unsound. Because the Rust compiler would not do the borrow check for your JavaScript codes. You don't know if there will be a mutation to the reference held in Rust happen in JavaScript. Fortunately, it wouldn't be a problem in synchronous functions, because JavaScript in Node.js runtime is running in a single thread, there won't be anything happening outside the Rust fn at the same time.
But it will be a problem if you are using &mut _ in async fn. There may be mutations happening in Node.js while the Rust part is suspended. We've enforced the unsafe keyword in this scenario: #1453. But we need more documentation and examples to describe this mental model to our users.

Support WebAssembly

We have had some discussions at #796. This feature is of exceptional importance to the entire community.
If we had a solution that allowed NAPI-RS package authors to easily distribute WebAssembly versions of their projects, more applications could run directly in browsers and other runtimes that support WebAssembly. For many applications in the Node.js/Web ecosystem, having a Playground that can run directly in the browser would be a huge boost.

We have done some experiments in the https://github.com/napi-rs/napi-rs/tree/emnapi branch; It basically verifies that it is possible to get NAPI-RS users to compile to WebAssembly at 0 cost via an external Node-API polyfill. But there are some TODOs that need to be done before 3.0:

Provide std::thread polyfill in NAPI-RS

Many projects that use NAPI-RS use std::thread to take advantage of multi-core processors. This is a part of Node.js itself that it is not good at. However, wasi/wasm threads are not yet available in Rust.
We need to provide a napi::thread module that provides std::thread in native binary and a polyfill in WebAssembly.

Integrate the WebAssembly package into our package distribution workflow

We already have a working very good multi-platform automated distribution workflow. We need to integrate the WebAssembly package into this workflow and maintain our competitive edge: Keep the cost of installing projects written in NAPI-RS to a minimum, both in terms of the number of files and package size.

Find an implementation that reduces the overhead of passing complex structures between Rust and JavaScript

We are not sure if this part will be part of NAPI-RS, but it is something that is really worthy of exploration.
This is the biggest challenge that many existing projects written in Rust face when trying to provide JavaScript plugins: swc-project/swc#2175.

There are several potentially effective ways to do this that are worth a try.

Lazy Serialize via a Proxy Object

Take the AST plugin as an example, not all plugins need to access or update the entire AST object. Plugins usually only need to access or update a small part of the entire AST, so we don't need to serialize the entire AST to return to JavaScript in the first place: luizperes/simdjson_nodejs#28 (comment). In simdjson package, they provide the valueForKeyPath("foo.bar[1]") API to access the real data in the large object, and serialize/deserialize it on demand. We can provide a more friendly way to do that with Proxy (like immer).

Serialize/Deserialize via WebAssembly in Node.js

From one of my tests in 2017: The overhead of creating JavaScript objects in WebAssembly is much smaller than in Native Addon. (I lost the original data, maybe we can re-test it with the latest Node.js and Rust). Plus, the StringRef proposal will make the serialization much faster in WebAssembly if used properly.

Stream integration

Like Promise, Stream is also the keystone of Node.js applications. Creating Stream and consuming Stream is really important in many scenarios, like SSR and large data processing apps.

Extendable CLI API

Rush stack is trying to integrate with NAPI-RS, and we want to adopt more integration solutions like Turoborepo, Nx in the future: microsoft/rushstack#3961.
We need to split things in CLI into an independent package to provide the same ability as in @napi-rs/cli.

Documents and articles

As developers discover that Rust can do many things that Node.js can't easily do, and that users don't have to deal with node-gyp as often as they do with C/C++ projects, more and more Node.js developers are experimenting with introducing Rust and NAPI-RS into their projects.
While NAPI-RS greatly reduces the mental burden of writing Rust and Node.js APIs, JavaScript is ultimately very different from Rust at the language level. We should definitely provide such materials: Adam Wathan's twitter

Enhace Publishing Strategy

This is not something that can be done in the 3.0 release, it requires a lot of projects in the community to work together.
In this part, we mainly need to help npm/rfcs#519 to land as soon as possible

Debug symbols

Native addon may crash during run time. When publishing to npm, the binary usually does not contain debug symbols in order to reduce the size of the installation. This makes it difficult to identify the issue when a crash occurs.
We should provide a solution to upload the separated debug symbols automatically and download them on demand.
Developers can use napi debug-symbols download PACKAGE_NAME and let the crash happen again, then they will get the correct stack trace and report it to package maintainers.

@yisibl
Copy link
Collaborator

yisibl commented Feb 22, 2023

Yes, I need symbols! In order to debug resvg-js in the server, I had to disabled strip and then build a new binary.

@timfish
Copy link
Sponsor Contributor

timfish commented Feb 22, 2023

then they will get the correct stack trace

Unless you attach a debugger, wont you only get stack traces for Rust panics?

I've used the crash-handler, minidumper and minidump-processor crates to get stack traces for crashes from unsafe code and non-Rust dependencies.

@Brooooooklyn Brooooooklyn pinned this issue Feb 23, 2023
@djd0723
Copy link

djd0723 commented Feb 25, 2023

Documents and articles

As developers discover that Rust can do many things that Node.js can't easily do, and that users don't have to deal with node-gyp as often as they do with C/C++ projects, more and more Node.js developers are experimenting with introducing Rust and NAPI-RS into their projects. While NAPI-RS greatly reduces the mental burden of writing Rust and Node.js APIs, JavaScript is ultimately very different from Rust at the language level. We should definitely provide such materials: Adam Wathan's twitter

Writing out some regular JavaScript code and then asking ChatGPT to rewrite it into a different programming language has quickly become my preferred way of getting started learning a new language.

@andymac4182
Copy link
Contributor

I am using the 3.0 release to write a new tool. I am curious if there is a list of tasks to complete 3.0 and get it released? I would prefer to not depend on a pre-release when I release my tool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants