1use std::{ffi::CStr, ptr};
4
5use crate::{module::Declared, qjs, Ctx, Module, Result};
6
7mod builtin_loader;
8mod builtin_resolver;
9pub mod bundle;
10mod compile;
11mod file_resolver;
12mod module_loader;
13mod script_loader;
14mod util;
15
16#[cfg(feature = "dyn-load")]
17mod native_loader;
18
19pub use builtin_loader::BuiltinLoader;
20pub use builtin_resolver::BuiltinResolver;
21pub use compile::Compile;
22pub use file_resolver::FileResolver;
23pub use module_loader::ModuleLoader;
24pub use script_loader::ScriptLoader;
25
26#[cfg(feature = "dyn-load")]
27#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "dyn-load")))]
28pub use native_loader::NativeLoader;
29
30#[cfg(feature = "phf")]
31pub type Bundle = bundle::Bundle<bundle::PhfBundleData<&'static [u8]>>;
33
34#[cfg(not(feature = "phf"))]
35pub type Bundle = bundle::Bundle<bundle::ScaBundleData<&'static [u8]>>;
37
38#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
40pub trait Resolver {
41 fn resolve<'js>(&mut self, ctx: &Ctx<'js>, base: &str, name: &str) -> Result<String>;
61}
62
63#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
65pub trait Loader {
66 fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result<Module<'js, Declared>>;
68}
69
70struct LoaderOpaque {
71 resolver: Box<dyn Resolver>,
72 loader: Box<dyn Loader>,
73}
74
75#[derive(Debug)]
76#[repr(transparent)]
77pub(crate) struct LoaderHolder(*mut LoaderOpaque);
78
79impl Drop for LoaderHolder {
80 fn drop(&mut self) {
81 let _opaque = unsafe { Box::from_raw(self.0) };
82 }
83}
84
85impl LoaderHolder {
86 pub fn new<R, L>(resolver: R, loader: L) -> Self
87 where
88 R: Resolver + 'static,
89 L: Loader + 'static,
90 {
91 Self(Box::into_raw(Box::new(LoaderOpaque {
92 resolver: Box::new(resolver),
93 loader: Box::new(loader),
94 })))
95 }
96
97 pub(crate) fn set_to_runtime(&self, rt: *mut qjs::JSRuntime) {
98 unsafe {
99 qjs::JS_SetModuleLoaderFunc(
100 rt,
101 Some(Self::normalize_raw),
102 Some(Self::load_raw),
103 self.0 as _,
104 );
105 }
106 }
107
108 #[inline]
109 fn normalize<'js>(
110 opaque: &mut LoaderOpaque,
111 ctx: &Ctx<'js>,
112 base: &CStr,
113 name: &CStr,
114 ) -> Result<*mut qjs::c_char> {
115 let base = base.to_str()?;
116 let name = name.to_str()?;
117
118 let name = opaque.resolver.resolve(ctx, base, name)?;
119
120 Ok(
122 unsafe {
123 qjs::js_strndup(ctx.as_ptr(), name.as_ptr() as _, name.as_bytes().len() as _)
124 },
125 )
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 = Vec::<std::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 = Vec::<std::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}