rquickjs_extension/loader/
mod.rs

1use std::collections::{HashMap, HashSet};
2
3use rquickjs::{
4    module::{Module, ModuleDef},
5    Ctx, JsLifetime, Object, Result,
6};
7
8pub use self::global::GlobalInitializer;
9pub use self::loader::ModuleLoader;
10pub use self::resolver::ModuleResolver;
11use crate::wrapper::{IntoModule, ModuleMeta};
12
13mod global;
14#[allow(clippy::module_inception)]
15mod loader;
16mod resolver;
17
18type GlobalLoadFn = Box<dyn for<'js> FnOnce(&Ctx<'js>, &Object<'js>) -> Result<()> + Send + Sync>;
19type ModuleLoadFn = for<'js> fn(Ctx<'js>, Vec<u8>) -> Result<Module<'js>>;
20
21fn 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_extension::{Extension, ModuleImpl};
30///
31/// struct MyExtension;
32///
33/// impl Extension for MyExtension {
34///     type Implementation = ModuleImpl<()>;
35///
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)]
55pub struct ExtensionBuilder {
56    modules: HashMap<&'static str, ModuleLoadFn>,
57    globals: Vec<GlobalLoadFn>,
58    names: HashSet<&'static str>,
59}
60
61impl ExtensionBuilder {
62    pub fn new() -> Self {
63        Self::default()
64    }
65
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
86    }
87
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    }
134
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        )
141    }
142}