1use crate::error::generic_error;
3use crate::extensions::ExtensionFileSource;
4use crate::module_specifier::ModuleSpecifier;
5use crate::modules::IntoModuleCodeString;
6use crate::modules::ModuleCodeString;
7use crate::modules::ModuleName;
8use crate::modules::ModuleSource;
9use crate::modules::ModuleSourceFuture;
10use crate::modules::ModuleType;
11use crate::modules::RequestedModuleType;
12use crate::modules::ResolutionKind;
13use crate::resolve_import;
14use crate::ModuleSourceCode;
15
16use anyhow::anyhow;
17use anyhow::bail;
18use anyhow::Context;
19use anyhow::Error;
20use futures::future::FutureExt;
21use std::cell::RefCell;
22use std::collections::HashMap;
23use std::future::Future;
24use std::pin::Pin;
25use std::rc::Rc;
26
27pub enum ModuleLoadResponse {
29 Sync(Result<ModuleSource, Error>),
33
34 Async(Pin<Box<ModuleSourceFuture>>),
37}
38
39pub trait ModuleLoader {
40 fn resolve(
51 &self,
52 specifier: &str,
53 referrer: &str,
54 kind: ResolutionKind,
55 ) -> Result<ModuleSpecifier, Error>;
56
57 fn load(
62 &self,
63 module_specifier: &ModuleSpecifier,
64 maybe_referrer: Option<&ModuleSpecifier>,
65 is_dyn_import: bool,
66 requested_module_type: RequestedModuleType,
67 ) -> ModuleLoadResponse;
68
69 fn prepare_load(
78 &self,
79 _module_specifier: &ModuleSpecifier,
80 _maybe_referrer: Option<String>,
81 _is_dyn_import: bool,
82 ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
83 async { Ok(()) }.boxed_local()
84 }
85
86 fn code_cache_ready(
91 &self,
92 _module_specifier: &ModuleSpecifier,
93 _code_cache: &[u8],
94 ) -> Pin<Box<dyn Future<Output = ()>>> {
95 async {}.boxed_local()
96 }
97}
98
99pub struct NoopModuleLoader;
102
103impl ModuleLoader for NoopModuleLoader {
104 fn resolve(
105 &self,
106 specifier: &str,
107 referrer: &str,
108 _kind: ResolutionKind,
109 ) -> Result<ModuleSpecifier, Error> {
110 Ok(resolve_import(specifier, referrer)?)
111 }
112
113 fn load(
114 &self,
115 module_specifier: &ModuleSpecifier,
116 maybe_referrer: Option<&ModuleSpecifier>,
117 _is_dyn_import: bool,
118 _requested_module_type: RequestedModuleType,
119 ) -> ModuleLoadResponse {
120 let maybe_referrer = maybe_referrer
121 .map(|s| s.as_str())
122 .unwrap_or("(no referrer)");
123 let err = generic_error(
124 format!(
125 "Module loading is not supported; attempted to load: \"{module_specifier}\" from \"{maybe_referrer}\"",
126 )
127 );
128 ModuleLoadResponse::Sync(Err(err))
129 }
130}
131
132pub type ExtModuleLoaderCb =
135 Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCodeString, Error>>;
136
137pub(crate) struct ExtModuleLoader {
138 sources: RefCell<HashMap<ModuleName, ModuleCodeString>>,
139}
140
141impl ExtModuleLoader {
142 pub fn new(
143 loaded_sources: Vec<(ModuleName, ModuleCodeString)>,
144 ) -> Result<Self, Error> {
145 let mut sources = HashMap::with_capacity(loaded_sources.len());
147 for source in loaded_sources {
148 sources.insert(source.0, source.1);
149 }
150 Ok(ExtModuleLoader {
151 sources: RefCell::new(sources),
152 })
153 }
154
155 pub fn finalize(self) -> Result<(), Error> {
156 let sources = self.sources.take();
157 let unused_modules: Vec<_> = sources.iter().collect();
158
159 if !unused_modules.is_empty() {
160 let mut msg =
161 "Following modules were passed to ExtModuleLoader but never used:\n"
162 .to_string();
163 for m in unused_modules {
164 msg.push_str(" - ");
165 msg.push_str(m.0);
166 msg.push('\n');
167 }
168 bail!(msg);
169 }
170
171 Ok(())
172 }
173}
174
175impl ModuleLoader for ExtModuleLoader {
176 fn resolve(
177 &self,
178 specifier: &str,
179 referrer: &str,
180 _kind: ResolutionKind,
181 ) -> Result<ModuleSpecifier, Error> {
182 if specifier.starts_with("../")
184 || specifier.starts_with("./")
185 || referrer.starts_with("ext:")
186 {
187 return Ok(crate::resolve_url(
189 &crate::resolve_url(referrer.replace("ext:", "ext:/").as_str())?
190 .join(specifier)
191 .map_err(crate::ModuleResolutionError::InvalidBaseUrl)?
192 .as_str()
193 .replace("ext:/", "ext:"),
195 )?);
196 }
197 Ok(resolve_import(specifier, referrer)?)
198 }
199
200 fn load(
201 &self,
202 specifier: &ModuleSpecifier,
203 _maybe_referrer: Option<&ModuleSpecifier>,
204 _is_dyn_import: bool,
205 _requested_module_type: RequestedModuleType,
206 ) -> ModuleLoadResponse {
207 let mut sources = self.sources.borrow_mut();
208 let source = match sources.remove(specifier.as_str()) {
209 Some(source) => source,
210 None => return ModuleLoadResponse::Sync(Err(anyhow!("Specifier \"{}\" was not passed as an extension module and was not included in the snapshot.", specifier))),
211 };
212 ModuleLoadResponse::Sync(Ok(ModuleSource::new(
213 ModuleType::JavaScript,
214 ModuleSourceCode::String(source),
215 specifier,
216 None,
217 )))
218 }
219
220 fn prepare_load(
221 &self,
222 _specifier: &ModuleSpecifier,
223 _maybe_referrer: Option<String>,
224 _is_dyn_import: bool,
225 ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
226 async { Ok(()) }.boxed_local()
227 }
228}
229
230pub(crate) struct LazyEsmModuleLoader {
234 sources: Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
235}
236
237impl LazyEsmModuleLoader {
238 pub fn new(
239 sources: Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
240 ) -> Self {
241 LazyEsmModuleLoader { sources }
242 }
243}
244
245impl ModuleLoader for LazyEsmModuleLoader {
246 fn resolve(
247 &self,
248 specifier: &str,
249 referrer: &str,
250 _kind: ResolutionKind,
251 ) -> Result<ModuleSpecifier, Error> {
252 Ok(resolve_import(specifier, referrer)?)
253 }
254
255 fn load(
256 &self,
257 specifier: &ModuleSpecifier,
258 _maybe_referrer: Option<&ModuleSpecifier>,
259 _is_dyn_import: bool,
260 _requested_module_type: RequestedModuleType,
261 ) -> ModuleLoadResponse {
262 let mut sources = self.sources.borrow_mut();
263 let source = match sources.remove(specifier.as_str()) {
264 Some(source) => source,
265 None => return ModuleLoadResponse::Sync(Err(anyhow!("Specifier \"{}\" cannot be lazy-loaded as it was not included in the binary.", specifier))),
266 };
267 ModuleLoadResponse::Sync(Ok(ModuleSource::new(
268 ModuleType::JavaScript,
269 ModuleSourceCode::String(source),
270 specifier,
271 None,
272 )))
273 }
274
275 fn prepare_load(
276 &self,
277 _specifier: &ModuleSpecifier,
278 _maybe_referrer: Option<String>,
279 _is_dyn_import: bool,
280 ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
281 async { Ok(()) }.boxed_local()
282 }
283}
284
285pub struct FsModuleLoader;
291
292impl ModuleLoader for FsModuleLoader {
293 fn resolve(
294 &self,
295 specifier: &str,
296 referrer: &str,
297 _kind: ResolutionKind,
298 ) -> Result<ModuleSpecifier, Error> {
299 Ok(resolve_import(specifier, referrer)?)
300 }
301
302 fn load(
303 &self,
304 module_specifier: &ModuleSpecifier,
305 _maybe_referrer: Option<&ModuleSpecifier>,
306 _is_dynamic: bool,
307 requested_module_type: RequestedModuleType,
308 ) -> ModuleLoadResponse {
309 let module_specifier = module_specifier.clone();
310 let fut = async move {
311 let path = module_specifier.to_file_path().map_err(|_| {
312 generic_error(format!(
313 "Provided module specifier \"{module_specifier}\" is not a file URL."
314 ))
315 })?;
316 let module_type = if let Some(extension) = path.extension() {
317 let ext = extension.to_string_lossy().to_lowercase();
318 if ext == "json" {
322 ModuleType::Json
323 } else {
324 match &requested_module_type {
325 RequestedModuleType::Other(ty) => ModuleType::Other(ty.clone()),
326 _ => ModuleType::JavaScript,
327 }
328 }
329 } else {
330 ModuleType::JavaScript
331 };
332
333 if module_type == ModuleType::Json
336 && requested_module_type != RequestedModuleType::Json
337 {
338 return Err(generic_error("Attempted to load JSON module without specifying \"type\": \"json\" attribute in the import statement."));
339 }
340
341 let code = std::fs::read(path).with_context(|| {
342 format!("Failed to load {}", module_specifier.as_str())
343 })?;
344 let module = ModuleSource::new(
345 module_type,
346 ModuleSourceCode::Bytes(code.into_boxed_slice().into()),
347 &module_specifier,
348 None,
349 );
350 Ok(module)
351 }
352 .boxed_local();
353
354 ModuleLoadResponse::Async(fut)
355 }
356}
357
358#[derive(Default)]
361pub struct StaticModuleLoader {
362 map: HashMap<ModuleSpecifier, ModuleCodeString>,
363}
364
365impl StaticModuleLoader {
366 pub fn new(
368 from: impl IntoIterator<Item = (ModuleSpecifier, impl IntoModuleCodeString)>,
369 ) -> Self {
370 Self {
371 map: HashMap::from_iter(
372 from.into_iter().map(|(url, code)| {
373 (url, code.into_module_code().into_cheap_copy().0)
374 }),
375 ),
376 }
377 }
378
379 pub fn with(
381 specifier: ModuleSpecifier,
382 code: impl IntoModuleCodeString,
383 ) -> Self {
384 Self::new([(specifier, code)])
385 }
386}
387
388impl ModuleLoader for StaticModuleLoader {
389 fn resolve(
390 &self,
391 specifier: &str,
392 referrer: &str,
393 _kind: ResolutionKind,
394 ) -> Result<ModuleSpecifier, Error> {
395 Ok(resolve_import(specifier, referrer)?)
396 }
397
398 fn load(
399 &self,
400 module_specifier: &ModuleSpecifier,
401 _maybe_referrer: Option<&ModuleSpecifier>,
402 _is_dyn_import: bool,
403 _requested_module_type: RequestedModuleType,
404 ) -> ModuleLoadResponse {
405 let res = if let Some(code) = self.map.get(module_specifier) {
406 Ok(ModuleSource::new(
407 ModuleType::JavaScript,
408 ModuleSourceCode::String(code.try_clone().unwrap()),
409 module_specifier,
410 None,
411 ))
412 } else {
413 Err(generic_error("Module not found"))
414 };
415 ModuleLoadResponse::Sync(res)
416 }
417}
418
419#[cfg(test)]
422pub struct TestingModuleLoader<L: ModuleLoader> {
423 loader: L,
424 log: RefCell<Vec<ModuleSpecifier>>,
425 load_count: std::cell::Cell<usize>,
426 prepare_count: std::cell::Cell<usize>,
427 resolve_count: std::cell::Cell<usize>,
428}
429
430#[cfg(test)]
431impl<L: ModuleLoader> TestingModuleLoader<L> {
432 pub fn new(loader: L) -> Self {
433 Self {
434 loader,
435 log: RefCell::new(vec![]),
436 load_count: Default::default(),
437 prepare_count: Default::default(),
438 resolve_count: Default::default(),
439 }
440 }
441
442 pub fn counts(&self) -> ModuleLoadEventCounts {
444 ModuleLoadEventCounts {
445 load: self.load_count.get(),
446 prepare: self.prepare_count.get(),
447 resolve: self.resolve_count.get(),
448 }
449 }
450}
451
452#[cfg(test)]
453impl<L: ModuleLoader> ModuleLoader for TestingModuleLoader<L> {
454 fn resolve(
455 &self,
456 specifier: &str,
457 referrer: &str,
458 kind: ResolutionKind,
459 ) -> Result<ModuleSpecifier, Error> {
460 self.resolve_count.set(self.resolve_count.get() + 1);
461 self.loader.resolve(specifier, referrer, kind)
462 }
463
464 fn prepare_load(
465 &self,
466 module_specifier: &ModuleSpecifier,
467 maybe_referrer: Option<String>,
468 is_dyn_import: bool,
469 ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
470 self.prepare_count.set(self.prepare_count.get() + 1);
471 self
472 .loader
473 .prepare_load(module_specifier, maybe_referrer, is_dyn_import)
474 }
475
476 fn load(
477 &self,
478 module_specifier: &ModuleSpecifier,
479 maybe_referrer: Option<&ModuleSpecifier>,
480 is_dyn_import: bool,
481 requested_module_type: RequestedModuleType,
482 ) -> ModuleLoadResponse {
483 self.load_count.set(self.load_count.get() + 1);
484 self.log.borrow_mut().push(module_specifier.clone());
485 self.loader.load(
486 module_specifier,
487 maybe_referrer,
488 is_dyn_import,
489 requested_module_type,
490 )
491 }
492}
493
494#[cfg(test)]
495#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
496pub struct ModuleLoadEventCounts {
497 pub resolve: usize,
498 pub prepare: usize,
499 pub load: usize,
500}
501
502#[cfg(test)]
503impl ModuleLoadEventCounts {
504 pub fn new(resolve: usize, prepare: usize, load: usize) -> Self {
505 Self {
506 resolve,
507 prepare,
508 load,
509 }
510 }
511}