DelSkayn / rquickjs

High level bindings to the quickjs javascript engine
MIT License
504 stars 63 forks source link

How do I create nested objects? #166

Closed Sytten closed 1 year ago

Sytten commented 1 year ago

Hi! I am trying to create nested objects, but I can't seem to do it. The console inside the sdk is always undefined.

#[derive(IntoJs, FromJs)]
pub struct Console {}

impl Console {
    pub fn new() -> Self {
        Self {}
    }

    pub fn register(ctx: Ctx) -> Result<()> {
        Class::<Console>::register(ctx)?;
        Ok(())
    }

    fn log(&self, values: Rest<Value<'_>>) -> Result<()> {
        println!("Work!");
        Ok(())
    }
}
impl ClassDef for Console {
    const CLASS_NAME: &'static str = "Console";
    fn class_id() -> &'static ClassId {
        static CLASS_ID: ClassId = ClassId::new();
        &CLASS_ID
    }

    const HAS_PROTO: bool = true;
    fn init_proto<'js>(_ctx: Ctx<'js>, proto: &Object<'js>) -> Result<()> {
        proto.set("log", Func::from(Method(Self::log)))?;
        Ok(())
    }
}

#[derive(IntoJs, FromJs)]
pub struct SDK {}

impl SDK {
    pub fn new() -> Self {
        Self {}
    }

    pub fn register(ctx: Ctx) -> Result<()> {
        Class::<SDK>::register(ctx)?;
        Ok(())
    }
}
impl ClassDef for SDK {
    const CLASS_NAME: &'static str = "SDK";
    fn class_id() -> &'static ClassId {
        static CLASS_ID: ClassId = ClassId::new();
        &CLASS_ID
    }

    const HAS_PROTO: bool = true;
    fn init_proto<'js>(ctx: Ctx<'js>, proto: &Object<'js>) -> Result<()> {
        proto.set("console", Console::new("JsSDK", Formatter::default()))?;
        Ok(())
    }
}
 let runtime = rquickjs::Runtime::new()?;

    let context = rquickjs::Context::full(&runtime)?;

    context.with::<_, Result<_>>(|ctx| {
        Console::register(ctx)?;
        SDK::register(ctx)?;

        let glob = ctx.globals();
        glob.set("__SDK", SDK::new())?;
        glob.set("console", Console::new())?;

        if let Err(e) = ctx.eval::<(), _>(
            r#"
            let sdk = __SDK;
            console.log("test");
            sdk.console.log("test");
        "#,
        ) {
            let exception = ctx.catch().get::<Exception>()?;
            println!("Exception: {:?}", exception);
            return Err(e);
        }

        Ok(())
    })?;

I get the error:

Exception: Exception { object: Object(Object'('0x7fc25bc13c80')'), message: Some("cannot read property 'log' of undefined"), file: None, line: None, stack: Some("    at <eval> (eval_script:5)\n") }

I also tried to do:

proto.set(
            "console",
            Class::instance(ctx, Console::new())?,
        )?;

But it doesn't work, I am a bit lost... Thanks!

Sytten commented 1 year ago

Haaaaaa, I managed to do it with creating an instance of SDK:

 glob.set("__SDK", Class::instance(ctx, SDK::new())?)?;

It works no matter if I use Class::instance(ctx, Console::new()) or just Console::new() which I guess makes sense since the proto is static?

I think some best practices / examples would go a long way!

Sytten commented 1 year ago

🤦 Ok I realized the issue is with the #[derive(IntoJs]

So basically when it's autogenerated it will map the class into a JS class, but when you put:

impl<'js> IntoJs<'js> for SDK {
    fn into_js(self, ctx: Ctx<'js>) -> Result<Value<'js>> {
        self.into_js_obj(ctx)
    }
}

It will automatically instantiate the class when converting it to JS! Thats why you dont need to call Class::instance manually.

I hope this can help debug someone else...