bytecodealliance / javy

JS to WebAssembly toolchain
Apache License 2.0
2.1k stars 101 forks source link

Expose Rust structs to JavaScript as classes #605

Closed rahul007-bit closed 3 months ago

rahul007-bit commented 5 months ago

I found a line in AWS LLRT and was wondering if we can do the same in Javy too, create a struct that JS can access as a class.

saulecabrera commented 5 months ago

Hi @rahul007-bit, even though a pattern like this doesn't exist today in Javy, in theory we could explose a class macro (or similar) that allows you to achieve a similar behavior. Do you have a specific use-case in mind?

rahul007-bit commented 5 months ago

I am working on a project that utilizes Jsonnet, but unfortunately, it is not compatible with QuickJS. Therefore, I am attempting to integrate it as a built-in module within the runtime environment. At present, I am implementing this integration using the eval_module and set_property functions.

Here is the Rust code snippet I am currently employing:

static JSONNET: &str = include_str!("./index.js");

pub(super) struct Jsonnet;

impl JSApiSet for Jsonnet {
  fn register(&self, runtime: &javy::Runtime, _config: &crate::APIConfig) -> anyhow::Result<()> {
    let context = runtime.context();
    let global = context.global_object()?;
    global.set_property(
       "__jsonnet_evaluate_snippet",
       context.wrap_callback(jsonnet_evaluate_snippet_callback())?,
    )?;

    if let Err(err) = context.eval_module("jsonnet", JSONNET) {
      eprintln!("Error loading the path shim: {:?}", err);
    }
    Ok(())
  }
}

And here is the JavaScript class definition that corresponds to the Rust implementation:

const __jsonnet_evaluate_snippet = globalThis.__jsonnet_evaluate_snippet;

class Jsonnet {
  evaluateSnippet(snippet) {
    return __jsonnet_evaluate_snippet(snippet);
  }
}
Reflect.deleteProperty(globalThis, "__jsonnet_evaluate_snippet");

export default Jsonnet;

However, when I attempt to import this module in JavaScript, the application encounters an error and crashes.

import Jsonnet from "jsonnet";

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  return new Response("Hello world", { status:  200 });
}

For further reference or testing, you may refer to this GitHub repository.

It would be very helpful if you could provide me with some guidance on how to resolve the import issue or suggest alternative approaches to integrating Jsonnet with QuickJS.

saulecabrera commented 5 months ago

Getting imports to work in the way you've defined your program is a bit tricky, but given that you've evaluated the index.js source as

  if let Err(err) = context.eval_module("jsonnet", JSONNET) {

I suspect that if you're not seeing an error there, you should be able to use Jsonnet.evaluateSnippet in your JS program, without having to import it. Have you tried removing the import and attaching your Jsonnet class to the global scope?

rahul007-bit commented 5 months ago

Thank you for your reply!

I tried using __jsonnet_evaluate_snippet without importing it, which worked as expected in this case. However, when I attempt to evaluate it as a module and then import it, the program crashes at context.eval_binary and this function is called here. Is there any way to achieve this behavior?

saulecabrera commented 5 months ago

Can you provide a bit more details about how is it crashing? It's likely that Jsonnet, uses or imports features that are not currently supported by Javy, that would be my first guess, but it's difficult to say without more details.

rahul007-bit commented 5 months ago

Yes, the issue was related to the jsonnet package that I was using in the API, and when I moved it to cli folder it worked perfectly, there was some issue with memory out of bound which I use in the wasm coredump.

in theory we could expose a class macro (or similar)

can you share anything that can help me to do this?

saulecabrera commented 5 months ago

A high-level view of how I envision this feature:

Note that the macro, is simply a suggestion to make the implementation of this trait easier on consumers, but not something to be considered critical for this to work IMO.

saulecabrera commented 3 months ago

With https://github.com/bytecodealliance/javy/pull/618 merged, this is now possible. I'm going to ahead and close this issue, but feel free to follow up with questions and / or comments.