diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-10-11 08:26:22 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-11 08:26:22 +1100 |
commit | a7baf5f2bbb50dc0cb571de141b800b9155faca7 (patch) | |
tree | 4bebaabd1d3ed4595e8a388e0fae559bb5558974 /cli/cache.rs | |
parent | 5a8a989b7815023f33a1e3183a55cc8999af5d98 (diff) |
refactor: integrate deno_graph into CLI (#12369)
Diffstat (limited to 'cli/cache.rs')
-rw-r--r-- | cli/cache.rs | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/cli/cache.rs b/cli/cache.rs new file mode 100644 index 000000000..2c94172b8 --- /dev/null +++ b/cli/cache.rs @@ -0,0 +1,349 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::disk_cache::DiskCache; +use crate::file_fetcher::FileFetcher; + +use deno_core::error::AnyError; +use deno_core::futures::future; +use deno_core::futures::FutureExt; +use deno_core::serde::Deserialize; +use deno_core::serde::Serialize; +use deno_core::serde_json; +use deno_core::ModuleSpecifier; +use deno_graph::source::CacheInfo; +use deno_graph::source::LoadFuture; +use deno_graph::source::LoadResponse; +use deno_graph::source::Loader; +use deno_runtime::permissions::Permissions; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Debug, Deserialize, Serialize)] +pub struct EmitMetadata { + pub version_hash: String, +} + +pub(crate) enum CacheType { + Declaration, + Emit, + SourceMap, + TypeScriptBuildInfo, + Version, +} + +/// A trait which provides a concise implementation to getting and setting +/// values in a cache. +pub(crate) trait Cacher { + /// Get a value from the cache. + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option<String>; + /// Set a value in the cache. + fn set( + &mut self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError>; +} + +/// Combines the cacher trait along with the deno_graph Loader trait to provide +/// a single interface to be able to load and cache modules when building a +/// graph. +pub(crate) trait CacherLoader: Cacher + Loader { + fn as_cacher(&self) -> &dyn Cacher; + fn as_mut_loader(&mut self) -> &mut dyn Loader; + fn as_mut_cacher(&mut self) -> &mut dyn Cacher; +} + +/// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides +/// a concise interface to the DENO_DIR when building module graphs. +pub(crate) struct FetchCacher { + disk_cache: DiskCache, + dynamic_permissions: Permissions, + file_fetcher: Arc<FileFetcher>, + root_permissions: Permissions, +} + +impl FetchCacher { + pub fn new( + disk_cache: DiskCache, + file_fetcher: FileFetcher, + root_permissions: Permissions, + dynamic_permissions: Permissions, + ) -> Self { + let file_fetcher = Arc::new(file_fetcher); + + Self { + disk_cache, + dynamic_permissions, + file_fetcher, + root_permissions, + } + } + + fn get_emit_metadata( + &self, + specifier: &ModuleSpecifier, + ) -> Option<EmitMetadata> { + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, "meta")?; + let bytes = self.disk_cache.get(&filename).ok()?; + serde_json::from_slice(&bytes).ok() + } + + fn set_emit_metadata( + &self, + specifier: &ModuleSpecifier, + data: EmitMetadata, + ) -> Result<(), AnyError> { + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, "meta") + .unwrap(); + let bytes = serde_json::to_vec(&data)?; + self.disk_cache.set(&filename, &bytes).map_err(|e| e.into()) + } +} + +impl Loader for FetchCacher { + fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> { + let local = self.file_fetcher.get_local_path(specifier)?; + if local.is_file() { + let location = &self.disk_cache.location; + let emit = self + .disk_cache + .get_cache_filename_with_extension(specifier, "js") + .map(|p| location.join(p)) + .filter(|p| p.is_file()); + let map = self + .disk_cache + .get_cache_filename_with_extension(specifier, "js.map") + .map(|p| location.join(p)) + .filter(|p| p.is_file()); + Some(CacheInfo { + local: Some(local), + emit, + map, + }) + } else { + None + } + } + + fn load( + &mut self, + specifier: &ModuleSpecifier, + is_dynamic: bool, + ) -> LoadFuture { + let specifier = specifier.clone(); + let mut permissions = if is_dynamic { + self.dynamic_permissions.clone() + } else { + self.root_permissions.clone() + }; + let file_fetcher = self.file_fetcher.clone(); + + async move { + let load_result = file_fetcher + .fetch(&specifier, &mut permissions) + .await + .map_or_else( + |err| { + if let Some(err) = err.downcast_ref::<std::io::Error>() { + if err.kind() == std::io::ErrorKind::NotFound { + return Ok(None); + } + } + Err(err) + }, + |file| { + Ok(Some(LoadResponse { + specifier: file.specifier, + maybe_headers: file.maybe_headers, + content: file.source, + })) + }, + ); + + (specifier, load_result) + } + .boxed() + } +} + +impl Cacher for FetchCacher { + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option<String> { + let extension = match cache_type { + CacheType::Declaration => "d.ts", + CacheType::Emit => "js", + CacheType::SourceMap => "js.map", + CacheType::TypeScriptBuildInfo => "buildinfo", + CacheType::Version => { + return self.get_emit_metadata(specifier).map(|d| d.version_hash) + } + }; + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, extension)?; + self + .disk_cache + .get(&filename) + .ok() + .map(|b| String::from_utf8(b).ok()) + .flatten() + } + + fn set( + &mut self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError> { + let extension = match cache_type { + CacheType::Declaration => "d.ts", + CacheType::Emit => "js", + CacheType::SourceMap => "js.map", + CacheType::TypeScriptBuildInfo => "buildinfo", + CacheType::Version => { + let data = if let Some(mut data) = self.get_emit_metadata(specifier) { + data.version_hash = value; + data + } else { + EmitMetadata { + version_hash: value, + } + }; + return self.set_emit_metadata(specifier, data); + } + }; + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, extension) + .unwrap(); + self + .disk_cache + .set(&filename, value.as_bytes()) + .map_err(|e| e.into()) + } +} + +impl CacherLoader for FetchCacher { + fn as_cacher(&self) -> &dyn Cacher { + self + } + + fn as_mut_loader(&mut self) -> &mut dyn Loader { + self + } + + fn as_mut_cacher(&mut self) -> &mut dyn Cacher { + self + } +} + +/// An in memory cache that is used by the runtime `Deno.emit()` API to provide +/// the same behavior as the disk cache when sources are provided. +#[derive(Debug)] +pub(crate) struct MemoryCacher { + sources: HashMap<String, Arc<String>>, + declarations: HashMap<ModuleSpecifier, String>, + emits: HashMap<ModuleSpecifier, String>, + maps: HashMap<ModuleSpecifier, String>, + build_infos: HashMap<ModuleSpecifier, String>, + versions: HashMap<ModuleSpecifier, String>, +} + +impl MemoryCacher { + pub fn new(sources: HashMap<String, Arc<String>>) -> Self { + Self { + sources, + declarations: HashMap::default(), + emits: HashMap::default(), + maps: HashMap::default(), + build_infos: HashMap::default(), + versions: HashMap::default(), + } + } +} + +impl Loader for MemoryCacher { + fn load( + &mut self, + specifier: &ModuleSpecifier, + _is_dynamic: bool, + ) -> LoadFuture { + let mut specifier_str = specifier.to_string(); + if !self.sources.contains_key(&specifier_str) { + specifier_str = specifier_str.replace("file:///", "/"); + if !self.sources.contains_key(&specifier_str) { + specifier_str = specifier_str[3..].to_string(); + } + } + let response = self.sources.get(&specifier_str).map(|c| LoadResponse { + specifier: specifier.clone(), + maybe_headers: None, + content: c.to_owned(), + }); + Box::pin(future::ready((specifier.clone(), Ok(response)))) + } +} + +impl Cacher for MemoryCacher { + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option<String> { + match cache_type { + CacheType::Declaration => self.declarations.get(specifier).cloned(), + CacheType::Emit => self.emits.get(specifier).cloned(), + CacheType::SourceMap => self.maps.get(specifier).cloned(), + CacheType::TypeScriptBuildInfo => { + self.build_infos.get(specifier).cloned() + } + CacheType::Version => self.versions.get(specifier).cloned(), + } + } + + fn set( + &mut self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError> { + match cache_type { + CacheType::Declaration => { + self.declarations.insert(specifier.clone(), value) + } + CacheType::Emit => self.emits.insert(specifier.clone(), value), + CacheType::SourceMap => self.maps.insert(specifier.clone(), value), + CacheType::TypeScriptBuildInfo => { + self.build_infos.insert(specifier.clone(), value) + } + CacheType::Version => self.versions.insert(specifier.clone(), value), + }; + Ok(()) + } +} + +impl CacherLoader for MemoryCacher { + fn as_cacher(&self) -> &dyn Cacher { + self + } + + fn as_mut_loader(&mut self) -> &mut dyn Loader { + self + } + + fn as_mut_cacher(&mut self) -> &mut dyn Cacher { + self + } +} |