|
1 | | -use std::collections::HashMap; |
| 1 | +use std::collections::{HashMap, HashSet}; |
2 | 2 |
|
3 | | -use rquickjs::{loader::Loader, Ctx, Error, Module, Object, Result}; |
| 3 | +use rquickjs::{ |
| 4 | + module::{Module, ModuleDef}, |
| 5 | + Ctx, JsLifetime, Object, Result, |
| 6 | +}; |
4 | 7 |
|
5 | | -pub use self::builder::ModuleLoaderBuilder; |
6 | 8 | pub use self::global::GlobalInitializer; |
| 9 | +pub use self::loader::ModuleLoader; |
7 | 10 | pub use self::resolver::ModuleResolver; |
| 11 | +use crate::wrapper::{IntoModule, ModuleMeta}; |
8 | 12 |
|
9 | | -mod builder; |
10 | 13 | mod global; |
| 14 | +#[allow(clippy::module_inception)] |
| 15 | +mod loader; |
11 | 16 | mod resolver; |
12 | 17 |
|
13 | 18 | type GlobalLoadFn = Box<dyn for<'js> FnOnce(&Ctx<'js>, &Object<'js>) -> Result<()> + Send + Sync>; |
14 | 19 | type ModuleLoadFn = for<'js> fn(Ctx<'js>, Vec<u8>) -> Result<Module<'js>>; |
15 | 20 |
|
16 | | -/// Rquickjs [`Loader`](rquickjs::loader::Loader) for Rust modules |
17 | | -/// defined using [`ModuleDefExt`](crate::ModuleDefExt). |
| 21 | +fn load_module_func<D: ModuleDef>(ctx: Ctx<'_>, name: Vec<u8>) -> Result<Module<'_>> { |
| 22 | + Module::declare_def::<D, _>(ctx, name) |
| 23 | +} |
| 24 | + |
| 25 | +/// Builder to create a [`ModuleLoader`], [`ModuleResolver`] and [`GlobalInitializer`] |
| 26 | +/// |
| 27 | +/// # Example |
| 28 | +/// ```rust |
| 29 | +/// use rquickjs_module::{ModuleLoader, ModuleDefExt, ModuleImpl}; |
| 30 | +/// |
| 31 | +/// struct MyExtension; |
| 32 | +/// |
| 33 | +/// impl Extension for MyExtension { |
| 34 | +/// type Implementation = ModuleImpl<()>; |
18 | 35 | /// |
19 | | -/// See [`ModuleLoaderBuilder`] for usage. |
20 | | -pub struct ModuleLoader { |
| 36 | +/// fn implementation() -> &'static Self::Implementation { |
| 37 | +/// &ModuleImpl { |
| 38 | +/// declare: |decl| { |
| 39 | +/// decl.declare("hello")?; |
| 40 | +/// Ok(()) |
| 41 | +/// }, |
| 42 | +/// evaluate: |ctx, exports, options| { |
| 43 | +/// exports.export("hello", "world".to_string())?; |
| 44 | +/// Ok(()) |
| 45 | +/// }, |
| 46 | +/// name: "my-module", |
| 47 | +/// } |
| 48 | +/// } |
| 49 | +/// |
| 50 | +/// fn options(self) -> () {} |
| 51 | +/// } |
| 52 | +/// |
| 53 | +/// ``` |
| 54 | +#[derive(Default)] |
| 55 | +pub struct ExtensionBuilder { |
21 | 56 | modules: HashMap<&'static str, ModuleLoadFn>, |
| 57 | + globals: Vec<GlobalLoadFn>, |
| 58 | + names: HashSet<&'static str>, |
22 | 59 | } |
23 | 60 |
|
24 | | -impl ModuleLoader { |
25 | | - pub(crate) fn new(modules: HashMap<&'static str, ModuleLoadFn>) -> Self { |
26 | | - Self { modules } |
| 61 | +impl ExtensionBuilder { |
| 62 | + pub fn new() -> Self { |
| 63 | + Self::default() |
27 | 64 | } |
28 | 65 |
|
29 | | - pub fn builder() -> ModuleLoaderBuilder { |
30 | | - ModuleLoaderBuilder::default() |
| 66 | + #[must_use] |
| 67 | + pub fn with_extension<O, M, R>(mut self, extension: M) -> Self |
| 68 | + where |
| 69 | + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, |
| 70 | + R: ModuleDef + ModuleMeta, |
| 71 | + M: IntoModule<O, R>, |
| 72 | + { |
| 73 | + self.process_extension(extension, None); |
| 74 | + self |
| 75 | + } |
| 76 | + |
| 77 | + #[must_use] |
| 78 | + pub fn with_extension_named<O, M, R>(mut self, extension: M, name: &'static str) -> Self |
| 79 | + where |
| 80 | + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, |
| 81 | + R: ModuleDef + ModuleMeta, |
| 82 | + M: IntoModule<O, R>, |
| 83 | + { |
| 84 | + self.process_extension(extension, Some(name)); |
| 85 | + self |
31 | 86 | } |
32 | | -} |
33 | 87 |
|
34 | | -impl Loader for ModuleLoader { |
35 | | - fn load<'js>(&mut self, ctx: &Ctx<'js>, path: &str) -> Result<Module<'js>> { |
36 | | - let load = self |
37 | | - .modules |
38 | | - .remove(path) |
39 | | - .ok_or_else(|| Error::new_loading(path))?; |
| 88 | + pub fn add_extension<O, M, R>(&mut self, extension: M) -> &mut Self |
| 89 | + where |
| 90 | + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, |
| 91 | + R: ModuleDef + ModuleMeta, |
| 92 | + M: IntoModule<O, R>, |
| 93 | + { |
| 94 | + self.process_extension(extension, None) |
| 95 | + } |
| 96 | + |
| 97 | + pub fn add_extension_named<O, M, R>(&mut self, extension: M, name: &'static str) -> &mut Self |
| 98 | + where |
| 99 | + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, |
| 100 | + R: ModuleDef + ModuleMeta, |
| 101 | + M: IntoModule<O, R>, |
| 102 | + { |
| 103 | + self.process_extension(extension, Some(name)) |
| 104 | + } |
| 105 | + |
| 106 | + fn process_extension<O, M, R>(&mut self, extension: M, name: Option<&'static str>) -> &mut Self |
| 107 | + where |
| 108 | + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, |
| 109 | + R: ModuleDef + ModuleMeta, |
| 110 | + M: IntoModule<O, R>, |
| 111 | + { |
| 112 | + let o = extension.options(); |
| 113 | + |
| 114 | + // Create a new closure that explicitly captures 'js lifetime |
| 115 | + let globals_fn = move |ctx: &Ctx<'_>, globals: &Object<'_>| { |
| 116 | + let globals_fn = M::globals; |
| 117 | + globals_fn(globals, &o)?; |
| 118 | + let _ = ctx.store_userdata(o); |
| 119 | + Ok(()) |
| 120 | + }; |
| 121 | + |
| 122 | + // Box the closure with explicit lifetime bounds |
| 123 | + let boxed_globals: GlobalLoadFn = Box::new(globals_fn); |
| 124 | + |
| 125 | + if R::is_module() { |
| 126 | + let name = name.unwrap_or(R::name()); |
| 127 | + self.names.insert(name); |
| 128 | + self.modules.insert(name, load_module_func::<R>); |
| 129 | + } |
| 130 | + |
| 131 | + self.globals.push(boxed_globals); |
| 132 | + self |
| 133 | + } |
40 | 134 |
|
41 | | - (load)(ctx.clone(), Vec::from(path)) |
| 135 | + pub fn build(self) -> (ModuleLoader, ModuleResolver, GlobalInitializer) { |
| 136 | + ( |
| 137 | + ModuleLoader::new(self.modules), |
| 138 | + ModuleResolver::new(self.names), |
| 139 | + GlobalInitializer::new(self.globals), |
| 140 | + ) |
42 | 141 | } |
43 | 142 | } |
0 commit comments