feat(resolver): expose raw package_json value; improve print debug (#738)

This commit is contained in:
Boshen 2023-08-14 11:57:17 +08:00 committed by GitHub
parent 99a7ad4319
commit 7c3e29d421
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 19 deletions

View file

@ -13,23 +13,25 @@ rust-version.workspace = true
categories.workspace = true
[dependencies]
tracing = { workspace = true }
dashmap = { workspace = true }
serde = { workspace = true, features = ["derive"] } # derive for Deserialize from package.json
serde_json = { workspace = true, features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
tracing = { workspace = true }
dashmap = { workspace = true }
serde = { workspace = true, features = ["derive"] } # derive for Deserialize from package.json
serde_json = { workspace = true, features = [
"preserve_order",
] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
rustc-hash = { workspace = true }
indexmap = { workspace = true, features = ["serde"] } # serde for Deserialize from package.json
dunce = "1.0.4"
indexmap = { workspace = true, features = ["serde"] } # serde for Deserialize from package.json
dunce = "1.0.4"
# Use `std::sync::OnceLock::get_or_try_init` when it is stable.
once_cell = "1.18.0"
[dev-dependencies]
static_assertions = { workspace = true }
criterion = { workspace = true }
rayon = { workspace = true } # for benchmark
nodejs-resolver = "0.0.88" # for benchmark
vfs = "0.9.0" # for testing with in memory file system
rayon = { workspace = true } # for benchmark
static_assertions = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
vfs = "0.9.0" # for testing with in memory file system
[target.'cfg(not(target_env = "msvc"))'.dev-dependencies]
jemallocator = { workspace = true }

View file

@ -44,6 +44,10 @@ impl<Fs: FileSystem> Cache<Fs> {
Self { fs, ..Self::default() }
}
pub fn clear(&self) {
self.cache.clear();
}
pub fn value(&self, path: &Path) -> CachedPath {
let hash = {
let mut hasher = FxHasher::default();

View file

@ -32,14 +32,16 @@ use std::{
cell::RefCell,
cmp::Ordering,
ffi::OsStr,
fmt,
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
};
use crate::{
cache::{Cache, CachedPath},
file_system::FileSystemOs,
package_json::{ExportsField, ExportsKey, MatchObject, PackageJson},
package_json::{ExportsField, ExportsKey, MatchObject},
path::PathUtil,
specifier::{Specifier, SpecifierKind},
};
@ -47,6 +49,7 @@ pub use crate::{
error::{JSONError, ResolveError},
file_system::{FileMetadata, FileSystem},
options::{Alias, AliasValue, EnforceExtension, ResolveOptions, Restriction},
package_json::PackageJson,
resolution::Resolution,
};
@ -56,7 +59,13 @@ pub type Resolver = ResolverGeneric<FileSystemOs>;
/// Generic implementation of the resolver, can be configured by the [FileSystem] trait.
pub struct ResolverGeneric<Fs> {
options: ResolveOptions,
cache: Cache<Fs>,
cache: Arc<Cache<Fs>>,
}
impl<Fs> fmt::Debug for ResolverGeneric<Fs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.options.fmt(f)
}
}
type ResolveState = Result<Option<CachedPath>, ResolveError>;
@ -118,11 +127,24 @@ impl<Fs: FileSystem> Default for ResolverGeneric<Fs> {
impl<Fs: FileSystem> ResolverGeneric<Fs> {
pub fn new(options: ResolveOptions) -> Self {
Self { options: options.sanitize(), cache: Cache::default() }
Self { options: options.sanitize(), cache: Arc::new(Cache::default()) }
}
pub fn new_with_file_system(file_system: Fs, options: ResolveOptions) -> Self {
Self { cache: Cache::new(file_system), ..Self::new(options) }
Self { cache: Arc::new(Cache::new(file_system)), ..Self::new(options) }
}
#[must_use]
pub fn clone_with_options(&self, options: ResolveOptions) -> Self {
Self { options: options.sanitize(), cache: Arc::clone(&self.cache) }
}
pub fn options(&self) -> &ResolveOptions {
&self.options
}
pub fn clear_cache(&self) {
self.cache.clear();
}
/// Resolve `specifier` at `path`
@ -156,7 +178,12 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
// enhanced-resolve: restrictions
self.check_restrictions(&path)?;
let mut ctx = ctx.borrow_mut();
Ok(Resolution { path, query: ctx.query.take(), fragment: ctx.fragment.take() })
Ok(Resolution {
path,
query: ctx.query.take(),
fragment: ctx.fragment.take(),
package_json: cached_path.find_package_json(&self.cache.fs, &self.options)?,
})
}
/// require(X) from module at path Y
@ -698,7 +725,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
let new_specifier = Specifier::parse(&new_specifier)
.map_err(ResolveError::Specifier)?;
ctx.with_fully_specified(false);
// Override query and fragment from the alias
// Alias may contain `?query`, pass it along.
ctx.with_query_fragment(specifier.query, specifier.fragment);
match self.require(cached_path, &new_specifier, ctx) {
Err(ResolveError::NotFound(_)) => { /* noop */ }
@ -708,7 +735,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
}
AliasValue::Ignore => {
return Err(ResolveError::Ignored(cached_path.path().join(alias_key)));
let path = cached_path.path().normalize_with(alias_key);
return Err(ResolveError::Ignored(path));
}
}
}

View file

@ -144,7 +144,7 @@ impl EnforceExtension {
pub type Alias = Vec<(String, Vec<AliasValue>)>;
/// Alias Value for [ResolveOptions::alias] and [ResolveOptions::fallback].
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum AliasValue {
/// The path value
Path(String),

View file

@ -4,6 +4,7 @@
use std::{
hash::BuildHasherDefault,
path::{Path, PathBuf},
sync::Arc,
};
use indexmap::IndexMap;
@ -20,6 +21,10 @@ pub struct PackageJson {
#[serde(skip)]
path: PathBuf,
#[serde(skip)]
#[serde(default)]
raw_json: Arc<serde_json::Value>,
/// The "name" field defines your package's name.
/// The "name" field can be used in addition to the "exports" field to self-reference a package using its name.
///
@ -166,6 +171,7 @@ impl PackageJson {
}
package_json.path = path;
package_json.raw_json = Arc::new(package_json_value);
Ok(package_json)
}
@ -188,6 +194,15 @@ impl PackageJson {
}
/// Directory to `package.json`
pub fn raw_json(&self) -> &serde_json::Value {
self.raw_json.as_ref()
}
/// Directory to `package.json`
///
/// # Panics
///
/// * When the package.json path is misconfigured.
pub fn directory(&self) -> &Path {
debug_assert!(self.path.file_name().is_some_and(|x| x == "package.json"));
self.path.parent().unwrap()

View file

@ -1,7 +1,12 @@
use std::path::{Path, PathBuf};
use crate::package_json::PackageJson;
use std::{
fmt,
path::{Path, PathBuf},
sync::Arc,
};
/// The final path resolution with optional `?query` and `#fragment`.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Clone)]
pub struct Resolution {
pub(crate) path: PathBuf,
@ -10,8 +15,27 @@ pub struct Resolution {
/// path fragment `#query`, contains `#`.
pub(crate) fragment: Option<String>,
pub(crate) package_json: Option<Arc<PackageJson>>,
}
impl fmt::Debug for Resolution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Resolution")
.field("path", &self.path)
.field("query", &self.query)
.field("fragment", &self.fragment)
.finish()
}
}
impl PartialEq for Resolution {
fn eq(&self, other: &Self) -> bool {
self.path == other.path && self.query == other.query && self.fragment == other.fragment
}
}
impl Eq for Resolution {}
impl Resolution {
/// Returns the path without query and fragment
pub fn path(&self) -> &Path {
@ -33,6 +57,10 @@ impl Resolution {
self.fragment.as_deref()
}
pub fn package_json(&self) -> Option<&Arc<PackageJson>> {
self.package_json.as_ref()
}
/// Returns the full path with query and fragment
pub fn full_path(&self) -> PathBuf {
let mut path = self.path.clone().into_os_string();
@ -52,6 +80,7 @@ fn test() {
path: PathBuf::from("foo"),
query: Some("?query".to_string()),
fragment: Some("#fragment".to_string()),
package_json: None,
};
assert_eq!(resolution.path(), Path::new("foo"));
assert_eq!(resolution.query(), Some("?query"));