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.
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.
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.
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:
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.
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.
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.
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
fromwasm-pack
tends to compile ruststruct
s to JavaScriptclass
es, without the possibility to pass objects.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.
child_process
ofNode.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.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 aserde
feature that relies on serializing the object to JSON on the JavaScript side, then usingserde_json
to parse it back into a Rust object. The other alternative isserde-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 genericJsValue
on the native side to be deserialized into Rust, these types get declared asany
in the generated TS definitions (although I suspect it might actually be possible to use the “type hints” embedded in theserde::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 internaldaemon
, 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