diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98d994081..59bc0f0ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: - name: Documentation env: DOCS_RS: 1 - run: cargo doc --features full-async,parallel,doc-cfg + run: cargo doc --no-deps --features full-async,parallel,doc-cfg - name: Upload docs uses: actions/upload-artifact@v3 with: diff --git a/core/src/class.rs b/core/src/class.rs index abf21d450..5e50d65d7 100644 --- a/core/src/class.rs +++ b/core/src/class.rs @@ -26,11 +26,14 @@ pub use trace::{Trace, Tracer}; #[doc(hidden)] pub mod impl_; +/// The trait which allows rust types to be used from javascript. pub trait JsClass<'js>: Trace<'js> { /// The name the constructor has in JavaScript const NAME: &'static str; /// Can the type be mutated while a JavaScript value. + /// + /// This should either be [`Readable`] or [`Writable`]. type Mutable: Mutability; /// A unique id for the class. @@ -112,7 +115,7 @@ impl<'js, C: JsClass<'js>> Class<'js, C> { )) } - /// Create a class from a Rust object with a given prototype + /// Create a class from a Rust object with a given prototype. pub fn instance_proto(value: C, proto: Object<'js>) -> Result> { if !Self::is_registered(proto.ctx()) { Self::register(proto.ctx())?; diff --git a/core/src/result.rs b/core/src/result.rs index ac2501d57..b2cbf1408 100644 --- a/core/src/result.rs +++ b/core/src/result.rs @@ -15,6 +15,9 @@ use crate::{ atom::PredefinedAtom, qjs, Context, Ctx, Exception, Object, StdResult, StdString, Type, Value, }; +#[cfg(feature = "array-buffer")] +use crate::value::array_buffer::AsSliceError; + /// Result type used throughout the library. pub type Result = StdResult; @@ -113,6 +116,9 @@ pub enum Error { name: StdString, message: Option, }, + + #[cfg(feature = "array-buffer")] + AsSlice(AsSliceError), /// Error when restoring a Persistent in a runtime other than the original runtime. UnrelatedRuntime, /// An error from QuickJS from which the specifics are unknown. @@ -276,6 +282,11 @@ impl Error { let message = self.to_cstring(); unsafe { qjs::JS_ThrowTypeError(ctx.as_ptr(), message.as_ptr()) } } + #[cfg(feature = "array-buffer")] + AsSlice(_) => { + let message = self.to_cstring(); + unsafe { qjs::JS_ThrowReferenceError(ctx.as_ptr(), message.as_ptr()) } + } #[cfg(feature = "loader")] Resolving { .. } | Loading { .. } => { let message = self.to_cstring(); @@ -417,6 +428,11 @@ impl Display for Error { "Error borrowing function: ".fmt(f)?; x.fmt(f)?; } + #[cfg(feature = "array-buffer")] + AsSlice(x) => { + "Could not convert array buffer to slice: ".fmt(f)?; + x.fmt(f)?; + } UnrelatedRuntime => "Restoring Persistent in an unrelated runtime".fmt(f)?, } Ok(()) @@ -448,6 +464,13 @@ impl From for Error { } } +#[cfg(feature = "array-buffer")] +impl From for Error { + fn from(value: AsSliceError) -> Self { + Error::AsSlice(value) + } +} + /// An error type containing possible thrown exception values. #[derive(Debug)] pub enum CaughtError<'js> { diff --git a/core/src/runtime.rs b/core/src/runtime.rs index 33eedec3a..730aced28 100644 --- a/core/src/runtime.rs +++ b/core/src/runtime.rs @@ -21,4 +21,5 @@ pub use r#async::{AsyncRuntime, AsyncWeakRuntime}; #[cfg(feature = "futures")] mod spawner; -pub use crate::qjs::JSMemoryUsage as MemoryUsage; +/// A struct with information about the runtimes memory usage. +pub type MemoryUsage = crate::qjs::JSMemoryUsage; diff --git a/core/src/runtime/async.rs b/core/src/runtime/async.rs index 42942af72..606d5ab67 100644 --- a/core/src/runtime/async.rs +++ b/core/src/runtime/async.rs @@ -49,6 +49,9 @@ impl Drop for InnerRuntime { #[cfg(feature = "parallel")] unsafe impl Send for InnerRuntime {} +/// A weak handle to the async runtime. +/// +/// Holding onto this struct does not prevent the runtime from being dropped. #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))] #[derive(Clone)] pub struct AsyncWeakRuntime { diff --git a/core/src/runtime/base.rs b/core/src/runtime/base.rs index c54033d6f..1395614fc 100644 --- a/core/src/runtime/base.rs +++ b/core/src/runtime/base.rs @@ -14,6 +14,8 @@ use super::{ }; /// A weak handle to the runtime. +/// +/// Holding onto this struct does not prevent the runtime from being dropped. #[derive(Clone)] #[repr(transparent)] pub struct WeakRuntime(Weak>); diff --git a/core/src/value.rs b/core/src/value.rs index ae361465e..451121423 100644 --- a/core/src/value.rs +++ b/core/src/value.rs @@ -12,11 +12,6 @@ pub mod object; mod string; mod symbol; -#[cfg(feature = "array-buffer")] -mod array_buffer; -#[cfg(feature = "array-buffer")] -mod typed_array; - pub use array::Array; pub use atom::Atom; pub use bigint::BigInt; @@ -28,6 +23,11 @@ pub use object::{Filter, Object}; pub use string::String; pub use symbol::Symbol; +#[cfg(feature = "array-buffer")] +pub mod array_buffer; +#[cfg(feature = "array-buffer")] +pub mod typed_array; + #[cfg(feature = "array-buffer")] pub use array_buffer::ArrayBuffer; #[cfg(feature = "array-buffer")] diff --git a/core/src/value/array_buffer.rs b/core/src/value/array_buffer.rs index e78fcc2b6..4c364d3e5 100644 --- a/core/src/value/array_buffer.rs +++ b/core/src/value/array_buffer.rs @@ -1,14 +1,39 @@ use crate::{qjs, Ctx, Error, FromJs, IntoJs, Object, Outlive, Result, Value}; +use core::fmt; use std::{ convert::TryInto, mem::{self, size_of, ManuallyDrop, MaybeUninit}, ops::Deref, os::raw::c_void, + ptr::NonNull, + result::Result as StdResult, slice, }; use super::typed_array::TypedArrayItem; +pub struct RawArrayBuffer { + pub len: usize, + pub ptr: NonNull, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum AsSliceError { + BufferUsed, + InvalidAlignment, +} + +impl fmt::Display for AsSliceError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AsSliceError::BufferUsed => write!(f, "Buffer was already used"), + AsSliceError::InvalidAlignment => { + write!(f, "Buffer had a different alignment than was requested") + } + } + } +} + /// Rust representation of a JavaScript object of class ArrayBuffer. /// #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "array-buffer")))] @@ -69,7 +94,7 @@ impl<'js> ArrayBuffer<'js> { /// Get the length of the array buffer in bytes. pub fn len(&self) -> usize { - Self::get_raw(&self.0).expect("Not an ArrayBuffer").0 + Self::get_raw(&self.0).expect("Not an ArrayBuffer").len } /// Returns whether an array buffer is empty. @@ -81,20 +106,19 @@ impl<'js> ArrayBuffer<'js> { /// /// Returns `None` if the array is detached. pub fn as_bytes(&self) -> Option<&[u8]> { - let (len, ptr) = Self::get_raw(self.as_value())?; - Some(unsafe { slice::from_raw_parts_mut(ptr, len) }) + let raw = Self::get_raw(self.as_value())?; + Some(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) }) } /// Returns a slice if the buffer underlying buffer is properly aligned for the type and the /// buffer is not detached. - pub fn as_slice(&self) -> Option<&[T]> { - let (len, ptr) = Self::get_raw(&self.0)?; - if ptr.align_offset(mem::align_of::()) != 0 { - return None; + pub fn as_slice(&self) -> StdResult<&[T], AsSliceError> { + let raw = Self::get_raw(&self.0).ok_or(AsSliceError::BufferUsed)?; + if raw.ptr.as_ptr().align_offset(mem::align_of::()) != 0 { + return Err(AsSliceError::InvalidAlignment); } - //assert!(len % size_of::() == 0); - let len = len / size_of::(); - Some(unsafe { slice::from_raw_parts(ptr as _, len) }) + let len = raw.len / size_of::(); + Ok(unsafe { slice::from_raw_parts(raw.ptr.as_ptr().cast(), len) }) } /// Detach array buffer @@ -140,19 +164,26 @@ impl<'js> ArrayBuffer<'js> { } } - pub(crate) fn get_raw(val: &Value<'js>) -> Option<(usize, *mut u8)> { + /// Returns a structure with data about the raw buffer which this object contains. + /// + /// Returns None if the buffer was already used. + pub fn as_raw(&self) -> Option { + Self::get_raw(self.as_value()) + } + + pub(crate) fn get_raw(val: &Value<'js>) -> Option { let ctx = val.ctx(); let val = val.as_js_value(); let mut size = MaybeUninit::::uninit(); let ptr = unsafe { qjs::JS_GetArrayBuffer(ctx.as_ptr(), size.as_mut_ptr(), val) }; - if ptr.is_null() { - None - } else { + if let Some(ptr) = NonNull::new(ptr) { let len = unsafe { size.assume_init() } .try_into() .expect(qjs::SIZE_T_ERROR); - Some((len, ptr)) + Some(RawArrayBuffer { len, ptr }) + } else { + None } } } diff --git a/core/src/value/function.rs b/core/src/value/function.rs index bcf0c5196..b86dfa646 100644 --- a/core/src/value/function.rs +++ b/core/src/value/function.rs @@ -183,6 +183,9 @@ impl<'js> Function<'js> { } } +/// A function which can be used as a constructor. +/// +/// Is a subtype of function. #[derive(Debug, Clone)] #[repr(transparent)] pub struct Constructor<'js>(pub(crate) Function<'js>); diff --git a/core/src/value/typed_array.rs b/core/src/value/typed_array.rs index 93ad86437..ddfea5b47 100644 --- a/core/src/value/typed_array.rs +++ b/core/src/value/typed_array.rs @@ -8,11 +8,11 @@ use std::{ marker::PhantomData, mem::{self, MaybeUninit}, ops::Deref, - ptr::null_mut, + ptr::{null_mut, NonNull}, slice, }; -use super::Constructor; +use super::{array_buffer::RawArrayBuffer, Constructor}; /// The trait which implements types which capable to be TypedArray items /// @@ -169,7 +169,12 @@ impl<'js, T> TypedArray<'js, T> { /// Returns `None` if the array is detached. pub fn as_bytes(&self) -> Option<&[u8]> { let (_, len, ptr) = Self::get_raw_bytes(self.as_value())?; - Some(unsafe { slice::from_raw_parts(ptr, len) }) + Some(unsafe { slice::from_raw_parts(ptr.as_ptr(), len) }) + } + + pub fn as_raw(&self) -> Option { + let (_, len, ptr) = Self::get_raw_bytes(self.as_value())?; + Some(RawArrayBuffer { len, ptr }) } /// Get underlying ArrayBuffer @@ -195,7 +200,7 @@ impl<'js, T> TypedArray<'js, T> { ctor.construct((arraybuffer,)) } - pub(crate) fn get_raw_bytes(val: &Value<'js>) -> Option<(usize, usize, *mut u8)> { + pub(crate) fn get_raw_bytes(val: &Value<'js>) -> Option<(usize, usize, NonNull)> { let ctx = &val.ctx; let val = val.as_js_value(); let mut off = MaybeUninit::::uninit(); @@ -221,20 +226,21 @@ impl<'js, T> TypedArray<'js, T> { let stp: usize = unsafe { stp.assume_init() } .try_into() .expect(qjs::SIZE_T_ERROR); - let (full_len, ptr) = ArrayBuffer::get_raw(&buf)?; - if (off + len) > full_len { + let raw = ArrayBuffer::get_raw(&buf)?; + if (off + len) > raw.len { return None; } - let ptr = unsafe { ptr.add(off) }; + // SAFETY: ptr was non-null and then we added an offset so it should still be non null + let ptr = unsafe { NonNull::new_unchecked(raw.ptr.as_ptr().add(off)) }; Some((stp, len, ptr)) } - pub(crate) fn get_raw(val: &Value<'js>) -> Option<(usize, *mut T)> { + pub(crate) fn get_raw(val: &Value<'js>) -> Option<(usize, NonNull)> { let (stp, len, ptr) = Self::get_raw_bytes(val)?; if stp != mem::size_of::() { return None; } - debug_assert_eq!(ptr.align_offset(mem::align_of::()), 0); + debug_assert_eq!(ptr.as_ptr().align_offset(mem::align_of::()), 0); let ptr = ptr.cast::(); Some((len / mem::size_of::(), ptr)) } @@ -244,7 +250,7 @@ impl<'js, T: TypedArrayItem> AsRef<[T]> for TypedArray<'js, T> { fn as_ref(&self) -> &[T] { let (len, ptr) = Self::get_raw(&self.0).unwrap_or_else(|| panic!("{}", T::CLASS_NAME.to_str())); - unsafe { slice::from_raw_parts(ptr as _, len) } + unsafe { slice::from_raw_parts(ptr.as_ptr().cast(), len) } } } diff --git a/macro/Cargo.toml b/macro/Cargo.toml index e95807656..a20b4120b 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -46,7 +46,7 @@ version = "2" [dev-dependencies.rquickjs] path = ".." version = "0.4.0-beta.4" -features = ["macro", "classes", "properties", "futures"] +features = ["macro", "classes", "properties", "futures","phf"] [dev-dependencies.async-std] version = "1" diff --git a/macro/my_module.js b/macro/my_module.js index e69de29bb..f69460c53 100644 --- a/macro/my_module.js +++ b/macro/my_module.js @@ -0,0 +1,3 @@ +export function foo(){ + return 1 + 1 +} diff --git a/macro/src/embed.rs b/macro/src/embed.rs index d13b41208..5355ed755 100644 --- a/macro/src/embed.rs +++ b/macro/src/embed.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::{env, path::Path}; use crate::common::crate_ident; use proc_macro2::TokenStream; @@ -55,16 +55,17 @@ pub fn embed(modules: EmbedModules) -> TokenStream { let path = Path::new(&path); let path = if path.is_relative() { - match Path::new(env!("CARGO_MANIFEST_DIR")) - .join(path) - .canonicalize() - { + let full_path = Path::new( + &env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR should be set"), + ) + .join(path); + match full_path.canonicalize() { Ok(x) => x, Err(e) => { abort!( f.name, "Error loading embedded js module from path `{}`: {}", - path.display(), + full_path.display(), e ); } diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 948bb7b53..7d01e8a34 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -26,6 +26,61 @@ mod methods; mod module; mod trace; +/// An attribute for implementing JsClass for a rust type. +/// +/// # Example +/// ``` +/// use rquickjs::{class::Trace, CatchResultExt, Class, Context, Object, Runtime}; +/// +/// /// Implement JsClass for TestClass. +/// /// This allows passing any instance of TestClass straight to javascript. +/// /// It is command to also add #[derive(Trace)] as all types which implement JsClass need to +/// /// also implement trace. +/// #[derive(Trace)] +/// #[rquickjs::class(rename_all = "camelCase")] +/// pub struct TestClass<'js> { +/// /// These attribute make the accessible from javascript with getters and setters. +/// /// As we used `rename_all = "camelCase"` in the attribute it will be called `innerObject` +/// /// on the javascript side. +/// #[qjs(get, set)] +/// inner_object: Object<'js>, +/// +/// /// This works for any value which implements `IntoJs` and `FromJs` and is clonable. +/// #[qjs(get, set)] +/// some_value: u32, +/// /// Make a field enumerable. +/// #[qjs(get, set, enumerable)] +/// another_value: u32, +/// } +/// +/// pub fn main() { +/// let rt = Runtime::new().unwrap(); +/// let ctx = Context::full(&rt).unwrap(); +/// +/// ctx.with(|ctx| { +/// /// Create an insance of a JsClass +/// let cls = Class::instance( +/// ctx.clone(), +/// TestClass { +/// inner_object: Object::new(ctx.clone()).unwrap(), +/// some_value: 1, +/// another_value: 2, +/// }, +/// ) +/// .unwrap(); +/// /// Pass it to javascript +/// ctx.globals().set("t", cls.clone()).unwrap(); +/// ctx.eval::<(), _>( +/// r#" +/// // use the actual value. +/// if(t.someValue !== 1){ +/// throw new Error(1) +/// }"# +/// ).unwrap(); +/// }) +/// } +/// ``` + #[proc_macro_attribute] #[proc_macro_error] pub fn class(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { @@ -34,6 +89,10 @@ pub fn class(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { TokenStream1::from(class::expand(options, item)) } +/// A attribute for implementing `IntoJsFunc` for a certain function. +/// +/// Using this attribute allows a wider range of functions to be used as callbacks from javascript +/// then when you use closures or straight functions. #[proc_macro_attribute] #[proc_macro_error] pub fn function(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { @@ -47,6 +106,109 @@ pub fn function(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { } } +/// A macro for implementing methods for a class. +/// +/// # Example +/// ``` +/// use rquickjs::{ +/// atom::PredefinedAtom, class::Trace, prelude::Func, CatchResultExt, Class, Context, Ctx, Object, +/// Result, Runtime, +/// }; +/// +/// #[derive(Trace)] +/// #[rquickjs::class] +/// pub struct TestClass { +/// value: u32, +/// another_value: u32, +/// } +/// +/// #[rquickjs::methods] +/// impl TestClass { +/// /// Marks a method as a constructor. +/// /// This method will be used when +/// #[qjs(constructor)] +/// pub fn new(value: u32) -> Self { +/// TestClass { +/// value, +/// another_value: value, +/// } +/// } +/// +/// /// Mark a function as a getter. +/// /// The value of this function can be accessed as a field. +/// /// This function is also renamed to value +/// #[qjs(get, rename = "value")] +/// pub fn get_value(&self) -> u32 { +/// self.value +/// } +/// +/// /// Mark a function as a setter. +/// /// The value of this function can be set as a field. +/// /// This function is also renamed to value +/// #[qjs(set, rename = "value")] +/// pub fn set_value(&mut self, v: u32) { +/// self.value = v +/// } +/// +/// /// Mark a function as a enumerable gettter. +/// #[qjs(get, rename = "anotherValue", enumerable)] +/// pub fn get_another_value(&self) -> u32 { +/// self.another_value +/// } +/// +/// #[qjs(set, rename = "anotherValue", enumerable)] +/// pub fn set_another_value(&mut self, v: u32) { +/// self.another_value = v +/// } +/// +/// /// Marks a function as static. It will be defined on the constructor object instead of the +/// /// Class prototype. +/// #[qjs(static)] +/// pub fn compare(a: &Self, b: &Self) -> bool { +/// a.value == b.value && a.another_value == b.another_value +/// } +/// +/// /// All functions declared in this impl block will be defined on the prototype of the +/// /// class. This attributes allows you to skip certain functions. +/// #[qjs(skip)] +/// pub fn inner_function(&self) {} +/// +/// /// Functions can also be renamed to specific symbols. This allows you to make an rust type +/// /// act like an iteratable value for example. +/// #[qjs(rename = PredefinedAtom::SymbolIterator)] +/// pub fn iterate<'js>(&self, ctx: Ctx<'js>) -> Result> { +/// let res = Object::new(ctx)?; +/// +/// res.set( +/// PredefinedAtom::Next, +/// Func::from(|ctx: Ctx<'js>| -> Result> { +/// let res = Object::new(ctx)?; +/// res.set(PredefinedAtom::Done, true)?; +/// Ok(res) +/// }), +/// )?; +/// Ok(res) +/// } +/// } +/// +/// pub fn main() { +/// let rt = Runtime::new().unwrap(); +/// let ctx = Context::full(&rt).unwrap(); +/// +/// ctx.with(|ctx| { +/// /// Define the class constructor on the globals object. +/// Class::::define(&ctx.globals()).unwrap(); +/// ctx.eval::<(), _>( +/// r#" +/// let nv = new TestClass(5); +/// if(nv.value !== 5){ +/// throw new Error('invalid value') +/// } +/// "#, +/// ).catch(&ctx).unwrap(); +/// }); +/// } +/// ``` #[proc_macro_attribute] #[proc_macro_error] pub fn methods(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { @@ -60,6 +222,126 @@ pub fn methods(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { } } +/// An attribute which generates code for exporting a module to rust. +/// ``` +/// +/// use rquickjs::{CatchResultExt, Context, Module, Runtime}; +/// +/// /// A class which will be exported from the module. +/// #[derive(rquickjs::class::Trace)] +/// #[rquickjs::class] +/// pub struct Test { +/// foo: u32, +/// } +/// +/// #[rquickjs::methods] +/// impl Test { +/// #[qjs(constructor)] +/// pub fn new() -> Test { +/// Test { foo: 3 } +/// } +/// } +/// +/// impl Default for Test { +/// fn default() -> Self { +/// Self::new() +/// } +/// } +/// +/// #[rquickjs::module(rename_vars = "camelCase")] +/// mod test_mod { +/// /// Imports and other declarations which aren't `pub` won't be exported. +/// use rquickjs::Ctx; +/// +/// /// You can even use `use` to export types from outside. +/// /// +/// /// Note that this tries to export the type, not the value, +/// /// So this won't work for functions. +/// pub use super::Test; +/// +/// /// A class which will be exported from the module under the name `FooBar`. +/// #[derive(rquickjs::class::Trace)] +/// #[rquickjs::class(rename = "FooBar")] +/// pub struct Test2 { +/// bar: u32, +/// } +/// +/// /// Implement methods for the class like normal. +/// #[rquickjs::methods] +/// impl Test2 { +/// /// A constructor is required for exporting types. +/// #[qjs(constructor)] +/// pub fn new() -> Test2 { +/// Test2 { bar: 3 } +/// } +/// } +/// +/// impl Default for Test2 { +/// fn default() -> Self { +/// Self::new() +/// } +/// } +/// +/// /// Two variables exported as `aConstValue` and `aStaticValue` because of the `rename_all` attr. +/// pub const A_CONST_VALUE: f32 = 2.0; +/// pub static A_STATIC_VALUE: f32 = 2.0; +/// +/// /// If your module doesn't quite fit with how this macro exports you can manually export from +/// /// the declare and evaluate functions. +/// #[qjs(declare)] +/// pub fn declare(declare: &mut rquickjs::module::Declarations) -> rquickjs::Result<()> { +/// declare.declare("aManuallyExportedValue")?; +/// Ok(()) +/// } +/// +/// #[qjs(evaluate)] +/// pub fn evaluate<'js>( +/// _ctx: &Ctx<'js>, +/// exports: &mut rquickjs::module::Exports<'js>, +/// ) -> rquickjs::Result<()> { +/// exports.export("aManuallyExportedValue", "Some Value")?; +/// Ok(()) +/// } +/// +/// /// You can also export functions. +/// #[rquickjs::function] +/// pub fn foo() -> u32 { +/// 1 + 1 +/// } +/// +/// /// You can make items public but not export them to javascript by adding the skip attribute. +/// #[qjs(skip)] +/// pub fn ignore_function() -> u32 { +/// 2 + 2 +/// } +/// } +/// +/// +/// fn main() { +/// assert_eq!(test_mod::ignore_function(), 4); +/// let rt = Runtime::new().unwrap(); +/// let ctx = Context::full(&rt).unwrap(); +/// +/// ctx.with(|ctx| { +/// // These modules are declared like normal with the declare_def function. +/// // The name of the module is js_ + the name of the module. This prefix can be changed +/// // by writing for example `#[rquickjs::module(prefix = "prefix_")]`. +/// Module::declare_def::(ctx.clone(), "test").unwrap(); +/// let _ = Module::evaluate( +/// ctx.clone(), +/// "test2", +/// r" +/// import { foo,aManuallyExportedValue, aConstValue, aStaticValue, FooBar } from 'test'; +/// if (foo() !== 2){ +/// throw new Error(1); +/// } +/// " +/// ) +/// .catch(&ctx) +/// .unwrap(); +/// }) +/// } +/// ``` #[proc_macro_attribute] #[proc_macro_error] pub fn module(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { @@ -73,6 +355,7 @@ pub fn module(attr: TokenStream1, item: TokenStream1) -> TokenStream1 { } } +/// A macro for auto deriving the trace trait. #[proc_macro_derive(Trace, attributes(qjs))] #[proc_macro_error] pub fn trace(stream: TokenStream1) -> TokenStream1 { @@ -80,6 +363,45 @@ pub fn trace(stream: TokenStream1) -> TokenStream1 { trace::expand(derive_input).into() } +/// A macro for embedding javascript code into a binary. +/// +/// Compiles a javascript module to bytecode and then compiles the resulting bytecode into the +/// binary. Each file loaded is turned into its own module. The macro takes a list of paths to +/// files to be compiled into a module with an option name. Module paths are relative to the crate +/// manifest file. +/// +/// # Usage +/// +/// ``` +/// use rquickjs::{embed, loader::Bundle, CatchResultExt, Context, Runtime}; +/// +/// /// load the `my_module.js` file and name it myModule +/// static BUNDLE: Bundle = embed! { +/// "myModule": "my_module.js", +/// }; +/// +/// fn main() { +/// let rt = Runtime::new().unwrap(); +/// let ctx = Context::full(&rt).unwrap(); +/// +/// rt.set_loader(BUNDLE, BUNDLE); +/// ctx.with(|ctx| { +/// let _ = ctx +/// .clone() +/// .compile( +/// "testModule", +/// r#" +/// import { foo } from 'myModule'; +/// if(foo() !== 2){ +/// throw new Error("Function didn't return the correct value"); +/// } +/// "#, +/// ) +/// .catch(&ctx) +/// .unwrap(); +/// }) +/// } +/// ``` #[proc_macro_error] #[proc_macro] pub fn embed(item: TokenStream1) -> TokenStream1 { diff --git a/src/lib.rs b/src/lib.rs index 359c10da5..c29b10e56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,10 @@ //! environment. Contexts of the same runtime can share JavaScript objects like in the browser between //! frames of the same origin. //! +//! As both [`Runtime`] and [`Context`] use a lock it is discourged to use them in a async +//! environment. Instead, when the `futures` feature is enabled this library also exposes +//! [`AsyncRuntime`] and [`AsyncContext`] which use a future aware lock. +//! //! # Converting Values //! //! This library has multiple traits for converting to and from JavaScript. The [`IntoJs`] trait is @@ -78,7 +82,7 @@ //! //! ## Extra types //! -//! This crate has support for conversion of many Rust types like [`Option`](std::option::Option), +//! This crate has support for conversion of many Rust types like [`Option`], //! [`Result`](std::result::Result), [`Vec`] and other collections. In addition an extra types //! support can be enabled via features: //!