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

WebAssembly API for JavaScript #208

Open
sunng87 opened this issue Mar 22, 2018 · 12 comments
Open

WebAssembly API for JavaScript #208

sunng87 opened this issue Mar 22, 2018 · 12 comments

Comments

@sunng87
Copy link
Owner

sunng87 commented Mar 22, 2018

Finally we have (wasm-bindgen)[https://github.com/alexcrichton/wasm-bindgen] to expose Rust struct to JavaScript, so we should be able to create Handlebars instance from JavaScript, and call any API from it.

This has made it possible to create a JavaScript interface for handlebars-rust. The next step will be allowing JavaScript implemented helpers and directives being registered in handlebars-rust.

@fabienjuif
Copy link

First things I saw that block this process is this issue from same-file, a dependency used by walkdir: BurntSushi/same-file#42

@sunng87
Copy link
Owner Author

sunng87 commented Dec 30, 2018

Yes, this is known and I have a feature no_dir_source that disable walkdir

@fabienjuif
Copy link

Ha nice 👍
Sorry then

@sunng87
Copy link
Owner Author

sunng87 commented Dec 30, 2018

Never mind. By the way, if you are interested in a WebAssembly/JavaScript API for handlebars, feel free to discuss here. Things I have been thinking about:

  • a JavaScript wrapper API for Handlebars
  • Defining helpers in JavaScript
  • document implement for handlebars Output API

@sunng87 sunng87 closed this as completed Jan 24, 2020
@kitwon
Copy link

kitwon commented Jun 1, 2022

In recent days I have tried to wrap register_helper using WASM. I have found two apis Function::new_no_args and Function::from in js-sys package. It is mostly like javascript eval function, so we can save javascript function template and call it like eval when calling rust helper. 🤔

Here is the code:

use handlebars::{
  Context, Handlebars as Registry, Helper, HelperDef, HelperResult, Output, RenderContext,
  RenderError,
};
use js_sys::Function;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct HelperOptions {
  template_ctx: JsValue,
  inverse_ctx: JsValue,
}

#[allow(clippy::new_without_default)]
#[wasm_bindgen]
impl HelperOptions {
  pub fn new() -> Self {
    HelperOptions {
      template_ctx: JsValue::undefined(),
      inverse_ctx: JsValue::undefined(),
    }
  }

  pub fn template(&mut self, ctx: JsValue) -> String {
    self.template_ctx = ctx;
    "$$TEMPLATE$$".to_string()
  }

  pub fn inverse(&mut self, ctx: JsValue) -> String {
    self.inverse_ctx = ctx;
    "$$INVERSE$$".to_string()
  }
}

pub struct JsHelper {
  pub js_fn_tpl: String,
}

impl HelperDef for JsHelper {
  fn call<'reg: 'rc, 'rc>(
    &self,
    h: &Helper<'reg, 'rc>,
    _: &'reg Registry<'reg>,
    _: &'rc Context,
    _: &mut RenderContext<'reg, 'rc>,
    out: &mut dyn Output,
  ) -> HelperResult {
    // Change function template to actual js function
    let js_fn = Function::new_no_args(&self.js_fn_tpl);
    let js_helper = Function::from(js_fn.call0(&JsValue::null()).unwrap());

    let value = h
      .param(0)
      .ok_or_else(|| RenderError::new("Param not found in js helper"))
      .unwrap();
    let data = JsValue::from_serde(value.value()).unwrap();

    let options = HelperOptions::new();
    let result = js_helper
      .call2(&JsValue::null(), &data, &options.into())
      .unwrap();
    let result = result.as_string().unwrap();
    out.write(result.as_str())?;
    Ok(())
  }
}

And we can hack the js callback param options like the struct HelperOptions. When calling the fn and inverse function, tag it in return string and save the call info. Parse the result string with the call info in rust after js_helper call finish.

😂 Not sure if it is the "best practices" or just a “trick”. Do y'all have a suggested workaround for this?
(repo here)

@sunng87
Copy link
Owner Author

sunng87 commented Jun 1, 2022

Perhaps we can use HelperOptions to hold enough information(render_context, registry and etc) and make template() and inverse() call that actually renders the template with that information.

@kitwon
Copy link

kitwon commented Jun 1, 2022

🤔 Ah, I have tried but it does not work, because the wasm_bindgen cannot have lifetime parameters.

Maybe we can use a static to hold that information, but for me it is another complicated workaround. Is it possible to ask you to provide more details about it?

@sunng87
Copy link
Owner Author

sunng87 commented Jun 1, 2022

I see. The data exchange between js and webassembly still requires some hack. I would prefer some result string/code for template() and inverse() over static holder, since it's more predictable and I tried best to avoid global state in handlebars.

@kitwon
Copy link

kitwon commented Jun 1, 2022

Yep, if you have plans to add this feature, let me know if you need a hand.

@sunng87
Copy link
Owner Author

sunng87 commented Jun 1, 2022

Thank you! This feature is welcomed. Do you have a full example of your current usage of this library via wasm and javascript? I'm just wondering the scenario of defining helpers via javascript.

@sunng87 sunng87 reopened this Jun 1, 2022
@kitwon
Copy link

kitwon commented Jun 2, 2022

Not yet.
I was using handlebars.js and doing some surveys about other more efficient template engines.

But I think the first step is to do some performance test for the js_helper, because the data exchange cost here is a black box. But I need to start doing this after 4 days and will comment here again if any questions arise.

@djahandarie
Copy link

We are also considering using this library as a (wasm) replacement for handlebars.js, as handlebars.js requires unsafe-eval and therefore will not be possible to use in MV3 extensions at least on firefox. But we need to have as-close-as-possible support for existing users and all their handlebars scripts, so cannot easily drop in another library. This would require being able to use our existing JS helpers, or reimplementing all of those in Rust, which might be difficult/impossible.

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

4 participants