macro_rules! js_class {
(
class $class_name: ident $(as $class_js_name: literal)? {
$(
$(#[$field_attr: meta])*
public $field_name: ident
( $( $field_arg: ident: $field_arg_type: ty ),* $(,)? ) -> $field_ty: ty
$field_body: block
)*
$(
$(#[$field_prop_attr: meta])*
property $field_prop_name: ident $(as $field_prop_js_name: literal)? {
$(
$(#[$field_prop_get_attr: meta])*
$(fn)? get( $( $field_prop_get_arg: ident: $field_prop_get_arg_type: ty ),* $(,)? ) -> $field_prop_get_ty: ty
$field_prop_get_body: block
)?
$(
$(#[$field_prop_set_attr: meta])*
$(fn)? set( $( $field_prop_set_arg: ident: $field_prop_set_arg_type: ty ),* $(,)? )
$( -> $field_prop_set_ty: ty )?
$field_prop_set_body: block
)?
}
)*
$(#[$constructor_attr: meta])*
constructor( $( $ctor_arg: ident: $ctor_arg_ty: ty ),* $(,)? )
$constructor_body: block
$(
$(#[$init_attr: meta])*
init($init_class_builder_name: ident : &mut ClassBuilder $(,)?) -> JsResult<()>
$init_body: block
)?
$(
$(#[$method_attr: meta])*
fn $method_name: ident $( as $method_js_name: literal )?
( $( $fn_arg: ident: $fn_arg_type: ty ),* $(,)? )
$(-> $result_type: ty)?
$method_body: block
)*
}
) => { ... };
}Expand description
Declare a JavaScript class, in a simpler way.
This can make declaration of JavaScript classes easier by using a hybrid declarative approach. The class itself follows a closer syntax to JavaScript while the method arguments/results and bodies are written in Rust.
This only declares the Boa interop parts of the class. The actual type must be declared separately as a Rust type, along with necessary derives and traits.
§Allowed declarations (in order):
§Any number of JS fields
public <field_name>(<field_args>) -> <field_ty> { <field_body> }Declare public fields on the JavaScript prototype at construction. This is optional. Those fields can be overwritten on the object itself.
§Any number of properties
property <field_getset_name> [as "<js_field_name>"] {
get(<field_getset_get_args>) -> <field_getset_get_ty> { <field_getset_get_body> }
set(<field_getset_set_args>) [-> JsResult<()>] { <field_getset_set_body> }
}Declare a getter and/or a setter on a JavaScript class property. This is optional.
Both get and set are optional, but at least one must be present. The set method
must either return the unit type or a JsResult<...>. The value returned will be
ignored, only errors will be used.
Using the as keyword, you can set the name of the property in JavaScript that
would otherwise not be possible in Rust.
§Required JavaScript Constructor
constructor(<ctor_args>) { <constructor_body> }Declares the JS constructor for the class. This is required, but could throw if creating
the object fails.
The body MUST return JsResult<Self>.
§An optional init function
fn init(class: &mut ClassBuilder) -> JsResult<()> { <init_body> }Declare a block of code to add at the end of the implementation’s init function.
§Any number of methods
fn <method_name> [as <js_method_name>](<fn_args>) -> <result_type> { <method_body> }Declare methods on the class. This is optional.
Using the as keyword, you can set the name of the property in JavaScript that
would otherwise not be possible in Rust.
§Example
Here’s an example using the animal class declared in boa_engine::class:
use boa_interop::{js_class, Ignore, JsClass};
#[derive(Clone, Trace, Finalize, JsData)]
pub enum Animal {
Cat,
Dog,
Other,
}
js_class! {
// Implement [`Class`] trait for the `Animal` enum.
class Animal {
// This sets a field on the JavaScript object. The arguments to
// `init` are the arguments passed to the constructor. This
// function MUST return the value to be set on the field. If this
// returns a `JsResult`, it will be unwrapped and error out during
// construction of the object.
public age(_name: Ignore, age: i32) -> i32 {
age
}
// This is called when a new instance of the class is created in
// JavaScript, e.g. `new Animal("cat")`.
// This method is mandatory and MUST return `JsResult<Self>`.
constructor(name: String) {
match name.as_str() {
"cat" => Ok(Animal::Cat),
"dog" => Ok(Animal::Dog),
_ => Ok(Animal::Other),
}
}
// Declare a function on the class itself.
// There is a current limitation using `self` in methods, so the
// instance must be accessed using an actual argument.
fn speak(this: JsClass<Animal>) -> JsString {
match *this.borrow() {
Animal::Cat => js_string!("meow"),
Animal::Dog => js_string!("woof"),
Animal::Other => js_string!(r"¯\_(ツ)_/¯"),
}
}
}
}
fn main() {
let mut context = Context::default();
context.register_global_class::<Animal>().unwrap();
let result = context.eval(Source::from_bytes(r#"
let pet = new Animal("dog", 3);
`My pet is ${pet.age} years old. Right, buddy? - ${pet.speak()}!`
"#)).expect("Could not evaluate script");
assert_eq!(
result.as_string().unwrap(),
&js_str!("My pet is 3 years old. Right, buddy? - woof!")
);
}