1use crate::ModuleSourceCode;
4use crate::error::CoreError;
5use crate::error::CoreErrorKind;
6use crate::module_specifier::ModuleSpecifier;
7use crate::modules::IntoModuleCodeString;
8use crate::modules::ModuleCodeString;
9use crate::modules::ModuleName;
10use crate::modules::ModuleSource;
11use crate::modules::ModuleSourceFuture;
12use crate::modules::ModuleType;
13use crate::modules::RequestedModuleType;
14use crate::modules::ResolutionKind;
15use crate::resolve_import;
16use deno_error::JsErrorBox;
17
18use futures::future::FutureExt;
19use std::borrow::Cow;
20use std::cell::RefCell;
21use std::collections::HashMap;
22use std::future::Future;
23use std::pin::Pin;
24use std::rc::Rc;
25
26use super::SourceCodeCacheInfo;
27
28pub type ModuleLoaderError = JsErrorBox;
29
30pub enum ModuleLoadResponse {
32 Sync(Result<ModuleSource, ModuleLoaderError>),
36
37 Async(Pin<Box<ModuleSourceFuture>>),
40}
41
42pub struct ModuleLoadOptions {
43 pub is_dynamic_import: bool,
44 pub is_synchronous: bool,
46 pub requested_module_type: RequestedModuleType,
47}
48
49#[derive(Debug, Clone)]
50pub struct ModuleLoadReferrer {
51 pub specifier: ModuleSpecifier,
52 pub line_number: i64,
54 pub column_number: i64,
56}
57
58pub trait ModuleLoader {
59 fn resolve(
70 &self,
71 specifier: &str,
72 referrer: &str,
73 kind: ResolutionKind,
74 ) -> Result<ModuleSpecifier, ModuleLoaderError>;
75
76 fn import_meta_resolve(
78 &self,
79 specifier: &str,
80 referrer: &str,
81 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
82 self.resolve(specifier, referrer, ResolutionKind::DynamicImport)
83 }
84
85 fn load(
90 &self,
91 module_specifier: &ModuleSpecifier,
92 maybe_referrer: Option<&ModuleLoadReferrer>,
93 options: ModuleLoadOptions,
94 ) -> ModuleLoadResponse;
95
96 fn prepare_load(
105 &self,
106 _module_specifier: &ModuleSpecifier,
107 _maybe_referrer: Option<String>,
108 _options: ModuleLoadOptions,
109 ) -> Pin<Box<dyn Future<Output = Result<(), ModuleLoaderError>>>> {
110 async { Ok(()) }.boxed_local()
111 }
112
113 fn finish_load(&self) {}
123
124 fn code_cache_ready(
129 &self,
130 _module_specifier: ModuleSpecifier,
131 _hash: u64,
132 _code_cache: &[u8],
133 ) -> Pin<Box<dyn Future<Output = ()>>> {
134 async {}.boxed_local()
135 }
136
137 fn purge_and_prevent_code_cache(&self, _module_specifier: &str) {}
143
144 fn get_source_map(&self, _file_name: &str) -> Option<Cow<'_, [u8]>> {
148 None
149 }
150
151 fn load_external_source_map(
153 &self,
154 _source_map_url: &str,
155 ) -> Option<Cow<'_, [u8]>> {
156 None
157 }
158
159 fn get_source_mapped_source_line(
160 &self,
161 _file_name: &str,
162 _line_number: usize,
163 ) -> Option<String> {
164 None
165 }
166
167 fn get_host_defined_options<'s, 'i>(
171 &self,
172 _scope: &mut v8::PinScope<'s, 'i>,
173 _name: &str,
174 ) -> Option<v8::Local<'s, v8::Data>> {
175 None
176 }
177}
178
179pub struct NoopModuleLoader;
182
183impl ModuleLoader for NoopModuleLoader {
184 fn resolve(
185 &self,
186 specifier: &str,
187 referrer: &str,
188 _kind: ResolutionKind,
189 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
190 resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
191 }
192
193 fn load(
194 &self,
195 _module_specifier: &ModuleSpecifier,
196 _maybe_referrer: Option<&ModuleLoadReferrer>,
197 _options: ModuleLoadOptions,
198 ) -> ModuleLoadResponse {
199 ModuleLoadResponse::Sync(Err(JsErrorBox::generic(
200 "Module loading is not supported.",
201 )))
202 }
203}
204
205pub trait ExtCodeCache {
206 fn get_code_cache_info(
207 &self,
208 specifier: &ModuleSpecifier,
209 code: &ModuleSourceCode,
210 esm: bool,
211 ) -> SourceCodeCacheInfo;
212
213 fn code_cache_ready(
214 &self,
215 specifier: ModuleSpecifier,
216 hash: u64,
217 code_cache: &[u8],
218 esm: bool,
219 );
220}
221
222pub(crate) struct ExtModuleLoader {
223 sources: RefCell<HashMap<ModuleName, ModuleCodeString>>,
224 ext_code_cache: Option<Rc<dyn ExtCodeCache>>,
225}
226
227impl ExtModuleLoader {
228 pub fn new(
229 loaded_sources: Vec<(ModuleName, ModuleCodeString)>,
230 ext_code_cache: Option<Rc<dyn ExtCodeCache>>,
231 ) -> Self {
232 let mut sources = HashMap::with_capacity(loaded_sources.len());
234 for source in loaded_sources {
235 sources.insert(source.0, source.1);
236 }
237 ExtModuleLoader {
238 sources: RefCell::new(sources),
239 ext_code_cache,
240 }
241 }
242
243 pub fn finalize(&self) -> Result<(), CoreError> {
244 let sources = self.sources.take();
245 let unused_modules: Vec<_> = sources.iter().collect();
246
247 if !unused_modules.is_empty() {
248 return Err(
249 CoreErrorKind::UnusedModules(
250 unused_modules
251 .into_iter()
252 .map(|(name, _)| name.to_string())
253 .collect::<Vec<_>>(),
254 )
255 .into_box(),
256 );
257 }
258
259 Ok(())
260 }
261}
262
263impl ModuleLoader for ExtModuleLoader {
264 fn resolve(
265 &self,
266 specifier: &str,
267 referrer: &str,
268 _kind: ResolutionKind,
269 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
270 if specifier.starts_with("../")
272 || specifier.starts_with("./")
273 || referrer.starts_with("ext:")
274 {
275 return crate::resolve_url(
277 &crate::resolve_url(referrer.replace("ext:", "ext:/").as_str())
278 .map_err(JsErrorBox::from_err)?
279 .join(specifier)
280 .map_err(crate::ModuleResolutionError::InvalidBaseUrl)
281 .map_err(JsErrorBox::from_err)?
282 .as_str()
283 .replace("ext:/", "ext:"),
285 )
286 .map_err(JsErrorBox::from_err);
287 }
288 resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
289 }
290
291 fn load(
292 &self,
293 specifier: &ModuleSpecifier,
294 _maybe_referrer: Option<&ModuleLoadReferrer>,
295 _options: ModuleLoadOptions,
296 ) -> ModuleLoadResponse {
297 let mut sources = self.sources.borrow_mut();
298 let source = match sources.remove(specifier.as_str()) {
299 Some(source) => source,
300 None => {
301 return ModuleLoadResponse::Sync(Err(JsErrorBox::generic(format!(
302 "Specifier \"{0}\" was not passed as an extension module and was not included in the snapshot.",
303 specifier
304 ))));
305 }
306 };
307 let code = ModuleSourceCode::String(source);
308 let code_cache = self
309 .ext_code_cache
310 .as_ref()
311 .map(|cache| cache.get_code_cache_info(specifier, &code, true));
312 ModuleLoadResponse::Sync(Ok(ModuleSource::new(
313 ModuleType::JavaScript,
314 code,
315 specifier,
316 code_cache,
317 )))
318 }
319
320 fn prepare_load(
321 &self,
322 _specifier: &ModuleSpecifier,
323 _maybe_referrer: Option<String>,
324 _options: ModuleLoadOptions,
325 ) -> Pin<Box<dyn Future<Output = Result<(), ModuleLoaderError>>>> {
326 async { Ok(()) }.boxed_local()
327 }
328
329 fn code_cache_ready(
330 &self,
331 module_specifier: ModuleSpecifier,
332 hash: u64,
333 code_cache: &[u8],
334 ) -> Pin<Box<dyn Future<Output = ()>>> {
335 if let Some(ext_code_cache) = &self.ext_code_cache {
336 ext_code_cache.code_cache_ready(module_specifier, hash, code_cache, true);
337 }
338 std::future::ready(()).boxed_local()
339 }
340}
341
342pub(crate) struct LazyEsmModuleLoader {
346 sources: Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
347}
348
349impl LazyEsmModuleLoader {
350 pub fn new(
351 sources: Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
352 ) -> Self {
353 LazyEsmModuleLoader { sources }
354 }
355}
356
357impl ModuleLoader for LazyEsmModuleLoader {
358 fn resolve(
359 &self,
360 specifier: &str,
361 referrer: &str,
362 _kind: ResolutionKind,
363 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
364 resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
365 }
366
367 fn load(
368 &self,
369 specifier: &ModuleSpecifier,
370 _maybe_referrer: Option<&ModuleLoadReferrer>,
371 _options: ModuleLoadOptions,
372 ) -> ModuleLoadResponse {
373 let mut sources = self.sources.borrow_mut();
374 let source = match sources.remove(specifier.as_str()) {
375 Some(source) => source,
376 None => {
377 return ModuleLoadResponse::Sync(Err(JsErrorBox::generic(format!(
378 "Specifier \"{0}\" cannot be lazy-loaded as it was not included in the binary.",
379 specifier
380 ))));
381 }
382 };
383 ModuleLoadResponse::Sync(Ok(ModuleSource::new(
384 ModuleType::JavaScript,
385 ModuleSourceCode::String(source),
386 specifier,
387 None,
388 )))
389 }
390
391 fn prepare_load(
392 &self,
393 _specifier: &ModuleSpecifier,
394 _maybe_referrer: Option<String>,
395 _options: ModuleLoadOptions,
396 ) -> Pin<Box<dyn Future<Output = Result<(), ModuleLoaderError>>>> {
397 async { Ok(()) }.boxed_local()
398 }
399}
400
401#[derive(Debug, thiserror::Error, deno_error::JsError)]
402#[class(inherit)]
403#[error("Failed to load {specifier}")]
404pub struct LoadFailedError {
405 specifier: ModuleSpecifier,
406 #[source]
407 #[inherit]
408 source: std::io::Error,
409}
410
411pub struct FsModuleLoader;
417
418impl ModuleLoader for FsModuleLoader {
419 fn resolve(
420 &self,
421 specifier: &str,
422 referrer: &str,
423 _kind: ResolutionKind,
424 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
425 resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
426 }
427
428 fn load(
429 &self,
430 module_specifier: &ModuleSpecifier,
431 _maybe_referrer: Option<&ModuleLoadReferrer>,
432 options: ModuleLoadOptions,
433 ) -> ModuleLoadResponse {
434 let module_specifier = module_specifier.clone();
435 let fut = async move {
436 let path = module_specifier.to_file_path().map_err(|_| {
437 JsErrorBox::generic(format!(
438 "Provided module specifier \"{module_specifier}\" is not a file URL."
439 ))
440 })?;
441 let module_type = if let Some(extension) = path.extension() {
442 let ext = extension.to_string_lossy().to_lowercase();
443 if ext == "json" {
447 ModuleType::Json
448 } else if ext == "wasm" {
449 ModuleType::Wasm
450 } else {
451 match &options.requested_module_type {
452 RequestedModuleType::Other(ty) => ModuleType::Other(ty.clone()),
453 RequestedModuleType::Text => ModuleType::Text,
454 RequestedModuleType::Bytes => ModuleType::Bytes,
455 _ => ModuleType::JavaScript,
456 }
457 }
458 } else {
459 ModuleType::JavaScript
460 };
461
462 if module_type == ModuleType::Json
465 && options.requested_module_type != RequestedModuleType::Json
466 {
467 return Err(JsErrorBox::generic("Attempted to load JSON module without specifying \"type\": \"json\" attribute in the import statement."));
468 }
469
470 let code = std::fs::read(path).map_err(|source| {
471 JsErrorBox::from_err(LoadFailedError {
472 specifier: module_specifier.clone(),
473 source,
474 })
475 })?;
476 let module = ModuleSource::new(
477 module_type,
478 ModuleSourceCode::Bytes(code.into_boxed_slice().into()),
479 &module_specifier,
480 None,
481 );
482 Ok(module)
483 }
484 .boxed_local();
485
486 ModuleLoadResponse::Async(fut)
487 }
488}
489
490#[derive(Default)]
493pub struct StaticModuleLoader {
494 map: HashMap<ModuleSpecifier, ModuleCodeString>,
495}
496
497impl StaticModuleLoader {
498 pub fn new(
500 from: impl IntoIterator<Item = (ModuleSpecifier, impl IntoModuleCodeString)>,
501 ) -> Self {
502 Self {
503 map: HashMap::from_iter(
504 from.into_iter().map(|(url, code)| {
505 (url, code.into_module_code().into_cheap_copy().0)
506 }),
507 ),
508 }
509 }
510
511 pub fn with(
513 specifier: ModuleSpecifier,
514 code: impl IntoModuleCodeString,
515 ) -> Self {
516 Self::new([(specifier, code)])
517 }
518}
519
520impl ModuleLoader for StaticModuleLoader {
521 fn resolve(
522 &self,
523 specifier: &str,
524 referrer: &str,
525 _kind: ResolutionKind,
526 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
527 resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
528 }
529
530 fn load(
531 &self,
532 module_specifier: &ModuleSpecifier,
533 _maybe_referrer: Option<&ModuleLoadReferrer>,
534 _options: ModuleLoadOptions,
535 ) -> ModuleLoadResponse {
536 let res = if let Some(code) = self.map.get(module_specifier) {
537 Ok(ModuleSource::new(
538 ModuleType::JavaScript,
539 ModuleSourceCode::String(code.try_clone().unwrap()),
540 module_specifier,
541 None,
542 ))
543 } else {
544 Err(JsErrorBox::generic("Module not found"))
545 };
546 ModuleLoadResponse::Sync(res)
547 }
548}
549
550#[cfg(test)]
553pub struct TestingModuleLoader<L: ModuleLoader> {
554 loader: L,
555 log: RefCell<Vec<ModuleSpecifier>>,
556 load_count: std::cell::Cell<usize>,
557 prepare_count: std::cell::Cell<usize>,
558 finish_count: std::cell::Cell<usize>,
559 resolve_count: std::cell::Cell<usize>,
560}
561
562#[cfg(test)]
563impl<L: ModuleLoader> TestingModuleLoader<L> {
564 pub fn new(loader: L) -> Self {
565 Self {
566 loader,
567 log: RefCell::new(vec![]),
568 load_count: Default::default(),
569 prepare_count: Default::default(),
570 finish_count: Default::default(),
571 resolve_count: Default::default(),
572 }
573 }
574
575 pub fn counts(&self) -> ModuleLoadEventCounts {
577 ModuleLoadEventCounts {
578 load: self.load_count.get(),
579 prepare: self.prepare_count.get(),
580 finish: self.finish_count.get(),
581 resolve: self.resolve_count.get(),
582 }
583 }
584}
585
586#[cfg(test)]
587impl<L: ModuleLoader> ModuleLoader for TestingModuleLoader<L> {
588 fn resolve(
589 &self,
590 specifier: &str,
591 referrer: &str,
592 kind: ResolutionKind,
593 ) -> Result<ModuleSpecifier, ModuleLoaderError> {
594 self.resolve_count.set(self.resolve_count.get() + 1);
595 self.loader.resolve(specifier, referrer, kind)
596 }
597
598 fn prepare_load(
599 &self,
600 module_specifier: &ModuleSpecifier,
601 maybe_referrer: Option<String>,
602 options: ModuleLoadOptions,
603 ) -> Pin<Box<dyn Future<Output = Result<(), ModuleLoaderError>>>> {
604 self.prepare_count.set(self.prepare_count.get() + 1);
605 self
606 .loader
607 .prepare_load(module_specifier, maybe_referrer, options)
608 }
609
610 fn finish_load(&self) {
611 self.finish_count.set(self.finish_count.get() + 1);
612 self.loader.finish_load();
613 }
614
615 fn load(
616 &self,
617 module_specifier: &ModuleSpecifier,
618 maybe_referrer: Option<&ModuleLoadReferrer>,
619 options: ModuleLoadOptions,
620 ) -> ModuleLoadResponse {
621 self.load_count.set(self.load_count.get() + 1);
622 self.log.borrow_mut().push(module_specifier.clone());
623 self.loader.load(module_specifier, maybe_referrer, options)
624 }
625
626 fn load_external_source_map(
627 &self,
628 source_map_url: &str,
629 ) -> Option<Cow<'_, [u8]>> {
630 self.loader.load_external_source_map(source_map_url)
631 }
632}
633
634#[cfg(test)]
635#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
636pub struct ModuleLoadEventCounts {
637 pub resolve: usize,
638 pub prepare: usize,
639 pub finish: usize,
640 pub load: usize,
641}
642
643#[cfg(test)]
644impl ModuleLoadEventCounts {
645 pub fn new(
646 resolve: usize,
647 prepare: usize,
648 finish: usize,
649 load: usize,
650 ) -> Self {
651 Self {
652 resolve,
653 prepare,
654 finish,
655 load,
656 }
657 }
658}