1use alloc::boxed::Box;
4use alloc::string::String;
5use core::{ffi::CStr, ptr};
6
7use crate::{module::Declared, qjs, Ctx, Module, Result};
8
9mod builtin_loader;
10mod builtin_resolver;
11pub mod bundle;
12mod compile;
13#[cfg(feature = "std")]
14mod file_resolver;
15mod module_loader;
16mod script_loader;
17mod util;
18
19#[cfg(feature = "dyn-load")]
20mod native_loader;
21
22pub use builtin_loader::BuiltinLoader;
23pub use builtin_resolver::BuiltinResolver;
24pub use compile::Compile;
25#[cfg(feature = "std")]
26pub use file_resolver::FileResolver;
27pub use module_loader::ModuleLoader;
28pub use script_loader::ScriptLoader;
29
30#[cfg(feature = "dyn-load")]
31#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "dyn-load")))]
32pub use native_loader::NativeLoader;
33
34#[cfg(feature = "phf")]
35pub type Bundle = bundle::Bundle<bundle::PhfBundleData<&'static [u8]>>;
37
38#[cfg(not(feature = "phf"))]
39pub type Bundle = bundle::Bundle<bundle::ScaBundleData<&'static [u8]>>;
41
42#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
44pub trait Resolver {
45 fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> Result<String>;
65}
66
67#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
69pub trait Loader {
70 fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result<Module<'js, Declared>>;
72}
73
74struct LoaderOpaque {
75 resolver: Box<dyn Resolver>,
76 loader: Box<dyn Loader>,
77}
78
79#[derive(Debug)]
80#[repr(transparent)]
81pub(crate) struct LoaderHolder(*mut LoaderOpaque);
82
83impl Drop for LoaderHolder {
84 fn drop(&mut self) {
85 let _opaque = unsafe { Box::from_raw(self.0) };
86 }
87}
88
89impl LoaderHolder {
90 pub fn new<R, L>(resolver: R, loader: L) -> Self
91 where
92 R: Resolver + 'static,
93 L: Loader + 'static,
94 {
95 Self(Box::into_raw(Box::new(LoaderOpaque {
96 resolver: Box::new(resolver),
97 loader: Box::new(loader),
98 })))
99 }
100
101 pub(crate) fn set_to_runtime(&self, rt: *mut qjs::JSRuntime) {
102 unsafe {
103 qjs::JS_SetModuleLoaderFunc(
104 rt,
105 Some(Self::normalize_raw),
106 Some(Self::load_raw),
107 self.0 as _,
108 );
109 }
110 }
111
112 #[inline]
113 fn normalize<'js>(
114 opaque: &mut LoaderOpaque,
115 ctx: &Ctx<'js>,
116 base: &CStr,
117 name: &CStr,
118 ) -> Result<*mut qjs::c_char> {
119 let base = base.to_str()?;
120 let name = name.to_str()?;
121
122 let name = opaque.resolver.resolve(ctx, base, name)?;
123
124 Ok(unsafe { qjs::js_strndup(ctx.as_ptr(), name.as_ptr() as _, name.len() as _) })
126 }
127
128 unsafe extern "C" fn normalize_raw(
129 ctx: *mut qjs::JSContext,
130 base: *const qjs::c_char,
131 name: *const qjs::c_char,
132 opaque: *mut qjs::c_void,
133 ) -> *mut qjs::c_char {
134 let ctx = Ctx::from_ptr(ctx);
135 let base = CStr::from_ptr(base);
136 let name = CStr::from_ptr(name);
137 let loader = &mut *(opaque as *mut LoaderOpaque);
138
139 Self::normalize(loader, &ctx, base, name).unwrap_or_else(|error| {
140 error.throw(&ctx);
141 ptr::null_mut()
142 })
143 }
144
145 #[inline]
146 unsafe fn load<'js>(
147 opaque: &mut LoaderOpaque,
148 ctx: &Ctx<'js>,
149 name: &CStr,
150 ) -> Result<*mut qjs::JSModuleDef> {
151 let name = name.to_str()?;
152
153 Ok(opaque.loader.load(ctx, name)?.as_ptr())
154 }
155
156 unsafe extern "C" fn load_raw(
157 ctx: *mut qjs::JSContext,
158 name: *const qjs::c_char,
159 opaque: *mut qjs::c_void,
160 ) -> *mut qjs::JSModuleDef {
161 let ctx = Ctx::from_ptr(ctx);
162 let name = CStr::from_ptr(name);
163 let loader = &mut *(opaque as *mut LoaderOpaque);
164
165 Self::load(loader, &ctx, name).unwrap_or_else(|error| {
166 error.throw(&ctx);
167 ptr::null_mut()
168 })
169 }
170}
171
172macro_rules! loader_impls {
173 ($($t:ident)*) => {
174 loader_impls!(@sub @mark $($t)*);
175 };
176 (@sub $($lead:ident)* @mark $head:ident $($rest:ident)*) => {
177 loader_impls!(@impl $($lead)*);
178 loader_impls!(@sub $($lead)* $head @mark $($rest)*);
179 };
180 (@sub $($lead:ident)* @mark) => {
181 loader_impls!(@impl $($lead)*);
182 };
183 (@impl $($t:ident)*) => {
184 impl<$($t,)*> Resolver for ($($t,)*)
185 where
186 $($t: Resolver,)*
187 {
188 #[allow(non_snake_case)]
189 #[allow(unused_mut)]
190 fn resolve<'js>(&mut self, _ctx: &Ctx<'js>, base: &str, name: &str) -> Result<String> {
191 let mut messages = alloc::vec::Vec::<alloc::string::String>::new();
192 let ($($t,)*) = self;
193 $(
194 match $t.resolve(_ctx, base, name) {
195 Err($crate::Error::Resolving { message, .. }) => {
197 message.map(|message| messages.push(message));
198 },
199 result => return result,
200 }
201 )*
202 Err(if messages.is_empty() {
204 $crate::Error::new_resolving(base, name)
205 } else {
206 $crate::Error::new_resolving_message(base, name, messages.join("\n"))
207 })
208 }
209 }
210
211 impl< $($t,)*> $crate::loader::Loader for ($($t,)*)
212 where
213 $($t: $crate::loader::Loader,)*
214 {
215 #[allow(non_snake_case)]
216 #[allow(unused_mut)]
217 fn load<'js>(&mut self, _ctx: &Ctx<'js>, name: &str) -> Result<Module<'js, Declared>> {
218 let mut messages = alloc::vec::Vec::<alloc::string::String>::new();
219 let ($($t,)*) = self;
220 $(
221 match $t.load(_ctx, name) {
222 Err($crate::Error::Loading { message, .. }) => {
224 message.map(|message| messages.push(message));
225 },
226 result => return result,
227 }
228 )*
229 Err(if messages.is_empty() {
231 $crate::Error::new_loading(name)
232 } else {
233 $crate::Error::new_loading_message(name, messages.join("\n"))
234 })
235 }
236 }
237 };
238}
239loader_impls!(A B C D E F G H);
240
241#[cfg(test)]
242mod test {
243 use crate::{CatchResultExt, Context, Ctx, Error, Module, Result, Runtime};
244
245 use super::{Loader, Resolver};
246
247 struct TestResolver;
248
249 impl Resolver for TestResolver {
250 fn resolve<'js>(&mut self, _ctx: &Ctx<'js>, base: &str, name: &str) -> Result<String> {
251 if base == "loader" && name == "test" {
252 Ok(name.into())
253 } else {
254 Err(Error::new_resolving_message(
255 base,
256 name,
257 "unable to resolve",
258 ))
259 }
260 }
261 }
262
263 struct TestLoader;
264
265 impl Loader for TestLoader {
266 fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result<Module<'js>> {
267 if name == "test" {
268 Module::declare(
269 ctx.clone(),
270 "test",
271 r#"
272 export const n = 123;
273 export const s = "abc";
274 "#,
275 )
276 } else {
277 Err(Error::new_loading_message(name, "unable to load"))
278 }
279 }
280 }
281
282 #[test]
283 fn custom_loader() {
284 let rt = Runtime::new().unwrap();
285 let ctx = Context::full(&rt).unwrap();
286 rt.set_loader(TestResolver, TestLoader);
287 ctx.with(|ctx| {
288 Module::evaluate(
289 ctx,
290 "loader",
291 r#"
292 import { n, s } from "test";
293 export default [n, s];
294 "#,
295 )
296 .unwrap()
297 .finish::<()>()
298 .unwrap();
299 })
300 }
301
302 #[test]
303 #[should_panic(expected = "Error resolving module")]
304 fn resolving_error() {
305 let rt = Runtime::new().unwrap();
306 let ctx = Context::full(&rt).unwrap();
307 rt.set_loader(TestResolver, TestLoader);
308 ctx.with(|ctx| {
309 Module::evaluate(
310 ctx.clone(),
311 "loader",
312 r#"
313 import { n, s } from "test_";
314 "#,
315 )
316 .catch(&ctx)
317 .unwrap()
318 .finish::<()>()
319 .catch(&ctx)
320 .expect("Unable to resolve");
321 })
322 }
323}