diff options
author | Matt Mastracci <matthew@mastracci.com> | 2023-06-13 17:22:49 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-13 23:22:49 +0000 |
commit | d451abfc9154e02f39c08d10c185d1618b2ef6d3 (patch) | |
tree | 8b3254be07c4de2d13871c283513562823e60da6 /core/modules/loaders.rs | |
parent | 60bf79c18410fd332b6b9b7c222e6d3d62bfe3f8 (diff) |
chore(core): Split modules.rs into multiple files (no code changes) (#19486)
A simple refactoring to make it easier to understand. No code changes.
Diffstat (limited to 'core/modules/loaders.rs')
-rw-r--r-- | core/modules/loaders.rs | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/core/modules/loaders.rs b/core/modules/loaders.rs new file mode 100644 index 000000000..d4dbf1ec2 --- /dev/null +++ b/core/modules/loaders.rs @@ -0,0 +1,254 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::error::generic_error; +use crate::error::AnyError; +use crate::extensions::ExtensionFileSource; +use crate::module_specifier::ModuleSpecifier; +use crate::modules::ModuleCode; +use crate::modules::ModuleSource; +use crate::modules::ModuleSourceFuture; +use crate::modules::ModuleType; +use crate::modules::ResolutionKind; +use crate::resolve_import; +use crate::Extension; +use anyhow::anyhow; +use anyhow::Error; +use futures::future::FutureExt; +use std::cell::RefCell; +use std::collections::HashMap; +use std::collections::HashSet; +use std::future::Future; +use std::pin::Pin; +use std::rc::Rc; + +pub trait ModuleLoader { + /// Returns an absolute URL. + /// When implementing an spec-complaint VM, this should be exactly the + /// algorithm described here: + /// <https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier> + /// + /// `is_main` can be used to resolve from current working directory or + /// apply import map for child imports. + /// + /// `is_dyn_import` can be used to check permissions or deny + /// dynamic imports altogether. + fn resolve( + &self, + specifier: &str, + referrer: &str, + kind: ResolutionKind, + ) -> Result<ModuleSpecifier, Error>; + + /// Given ModuleSpecifier, load its source code. + /// + /// `is_dyn_import` can be used to check permissions or deny + /// dynamic imports altogether. + fn load( + &self, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, + is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>>; + + /// This hook can be used by implementors to do some preparation + /// work before starting loading of modules. + /// + /// For example implementor might download multiple modules in + /// parallel and transpile them to final JS sources before + /// yielding control back to the runtime. + /// + /// It's not required to implement this method. + fn prepare_load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option<String>, + _is_dyn_import: bool, + ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> { + async { Ok(()) }.boxed_local() + } +} + +/// Placeholder structure used when creating +/// a runtime that doesn't support module loading. +pub struct NoopModuleLoader; + +impl ModuleLoader for NoopModuleLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _kind: ResolutionKind, + ) -> Result<ModuleSpecifier, Error> { + Err(generic_error( + format!("Module loading is not supported; attempted to resolve: \"{specifier}\" from \"{referrer}\"") + )) + } + + fn load( + &self, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option<&ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + let err = generic_error( + format!( + "Module loading is not supported; attempted to load: \"{module_specifier}\" from \"{maybe_referrer:?}\"", + ) + ); + async move { Err(err) }.boxed_local() + } +} + +/// Function that can be passed to the `ExtModuleLoader` that allows to +/// transpile sources before passing to V8. +pub type ExtModuleLoaderCb = + Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>; + +pub(crate) struct ExtModuleLoader { + maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>, + sources: RefCell<HashMap<String, ExtensionFileSource>>, + used_specifiers: RefCell<HashSet<String>>, +} + +impl ExtModuleLoader { + pub fn new( + extensions: &[Extension], + maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>, + ) -> Self { + let mut sources = HashMap::new(); + sources.extend( + extensions + .iter() + .flat_map(|e| e.get_esm_sources()) + .flatten() + .map(|s| (s.specifier.to_string(), s.clone())), + ); + ExtModuleLoader { + maybe_load_callback, + sources: RefCell::new(sources), + used_specifiers: Default::default(), + } + } +} + +impl ModuleLoader for ExtModuleLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _kind: ResolutionKind, + ) -> Result<ModuleSpecifier, Error> { + Ok(resolve_import(specifier, referrer)?) + } + + fn load( + &self, + specifier: &ModuleSpecifier, + _maybe_referrer: Option<&ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + let sources = self.sources.borrow(); + let source = match sources.get(specifier.as_str()) { + Some(source) => source, + None => return futures::future::err(anyhow!("Specifier \"{}\" was not passed as an extension module and was not included in the snapshot.", specifier)).boxed_local(), + }; + self + .used_specifiers + .borrow_mut() + .insert(specifier.to_string()); + let result = if let Some(load_callback) = &self.maybe_load_callback { + load_callback(source) + } else { + source.load() + }; + match result { + Ok(code) => { + let res = ModuleSource::new(ModuleType::JavaScript, code, specifier); + return futures::future::ok(res).boxed_local(); + } + Err(err) => return futures::future::err(err).boxed_local(), + } + } + + fn prepare_load( + &self, + _specifier: &ModuleSpecifier, + _maybe_referrer: Option<String>, + _is_dyn_import: bool, + ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> { + async { Ok(()) }.boxed_local() + } +} + +impl Drop for ExtModuleLoader { + fn drop(&mut self) { + let sources = self.sources.get_mut(); + let used_specifiers = self.used_specifiers.get_mut(); + let unused_modules: Vec<_> = sources + .iter() + .filter(|(k, _)| !used_specifiers.contains(k.as_str())) + .collect(); + + if !unused_modules.is_empty() { + let mut msg = + "Following modules were passed to ExtModuleLoader but never used:\n" + .to_string(); + for m in unused_modules { + msg.push_str(" - "); + msg.push_str(m.0); + msg.push('\n'); + } + panic!("{}", msg); + } + } +} + +/// Basic file system module loader. +/// +/// Note that this loader will **block** event loop +/// when loading file as it uses synchronous FS API +/// from standard library. +pub struct FsModuleLoader; + +impl ModuleLoader for FsModuleLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _kind: ResolutionKind, + ) -> Result<ModuleSpecifier, Error> { + Ok(resolve_import(specifier, referrer)?) + } + + fn load( + &self, + module_specifier: &ModuleSpecifier, + _maybe_referrer: Option<&ModuleSpecifier>, + _is_dynamic: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + fn load( + module_specifier: &ModuleSpecifier, + ) -> Result<ModuleSource, AnyError> { + let path = module_specifier.to_file_path().map_err(|_| { + generic_error(format!( + "Provided module specifier \"{module_specifier}\" is not a file URL." + )) + })?; + let module_type = if let Some(extension) = path.extension() { + let ext = extension.to_string_lossy().to_lowercase(); + if ext == "json" { + ModuleType::Json + } else { + ModuleType::JavaScript + } + } else { + ModuleType::JavaScript + }; + + let code = std::fs::read_to_string(path)?.into(); + let module = ModuleSource::new(module_type, code, module_specifier); + Ok(module) + } + + futures::future::ready(load(module_specifier)).boxed_local() + } +} |