rome / tools

Unified developer tools for JavaScript, TypeScript, and the web
https://docs.rome.tools/
MIT License
23.76k stars 663 forks source link

☂️ Node.js API #3008

Closed ematipico closed 1 year ago

ematipico commented 2 years ago

Overview

This umbrella issue serves as source to track tasks and experiments that the team has to work in order to achieve a valid architecture to start releasing Node.js API.

Plugins are out of scope.

At the moment we don’t have a simple way to run rome via API, other than WASM. An example of usage is our playground, and its ergonomic is really awful because wasm-bindgen from wasm-pack tends to compile rust structs to JavaScript classes, without the possibility to pass objects.

#[wasm-bindgen]
struct Foo {
    lorem: Option<String>,
    ipsum: Option<u32> 
}

#[wasm-bindgen]
impl Foot {
    #[wasm-bindgen(constructor)]
    fn new(lorem: Option<string>, ipsum: Option<u32>) -> Self {
        Self { lorem, ipsum }
    }
}
class Foo {
    constructor(lorem = undefined, ipsum = undefined) {
    }
}

Imagine using a struct as an hypothetic options object to pass to our formatter or linter.

There are different alternatives, and one of them requires a decision to make.

  1. Use WASM as baseline, but then create JavaScript APIs ourselves as man-in-the-middle. Users will use JavaScript APIs, and we will map all these info to call the WASM APIs.
  2. Do not use WASM as baseline. Create JavaScript APIs that will call our CLI directly. This solution requires an architectural decision. The CLI can be called in two different ways:
    1. Directly via child_process of Node.js. This a quick solution and it would involve heavy changes to our CLI, because it would require to map every possible JavaScript API that we support against the CLI. The formatter should be OK, but if we decided to expand the linter, we have to make sure support all options and arguments.
    2. Using a daemon. A daemon is essentially a small server, and a client can communicate with it using a specific protocol. While this will take more time to implement, it’s important to highlight that using a daemon is the ultimate goal for Rome. The daemon will be eventually be used also by the LSP. I think this is worth the implementation.
    3. Another alternative is to use the existing serde implementations for most of the structs exposed in the public API (diagnostics, code actions, formatter result) to bridge the gap between raw JS objects and Rust “plain old data structures”. There are two possible crates to achieve this: wasm-bindgen itself has a serde feature that relies on serializing the object to JSON on the JavaScript side, then using serde_json to parse it back into a Rust object. The other alternative is serde-wasm-bindgen that uses direct JS calls from the WASM side to read the incoming object as it gets deserialized. There isn’t any clear winner between these two methods, both can be better or worse depending on the shape of the underlying data. One clear downside compare to annotating the structs with #[wasm-bindgen] directly is that the generated bindings will not contain any type definition for the arguments or return values of these functions: since they receive a generic JsValue on the native side to be deserialized into Rust, these types get declared as any in the generated TS definitions (although I suspect it might actually be possible to use the “type hints” embedded in the serde::Deserialize implementations to generate those type definitions ourselves instead)

WebAssembly might be a good solution, but it's not definitely the only solution, and we already know that ultimately Rome will have an internal daemon, regardless of what's the best outcome of the Node.js API.

The team will experiment on which solution is best in terms of effort, adoption and performance. The APIs should be crafted in a way where an backend - WebAssembly for example - can be easily swapped with another - in this case the daemon.

Steps to incrementally achieve the goal

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 14 days with no activity.

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 14 days with no activity.