mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 13:18:59 +00:00
perf(resolver): use system canonicalize to reduce total number of path hashes (#902)
This commit is contained in:
parent
2e99af3f67
commit
95cae98e2b
5 changed files with 29 additions and 70 deletions
|
|
@ -1,12 +1,11 @@
|
|||
use once_cell::sync::OnceCell as OnceLock;
|
||||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
collections::VecDeque,
|
||||
convert::AsRef,
|
||||
hash::{BuildHasherDefault, Hash, Hasher},
|
||||
io,
|
||||
ops::Deref,
|
||||
path::{Component, Path, PathBuf},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
|
@ -14,7 +13,8 @@ use dashmap::{DashMap, DashSet};
|
|||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::{
|
||||
package_json::PackageJson, FileMetadata, FileSystem, ResolveError, ResolveOptions, TsConfig,
|
||||
package_json::PackageJson, path::PathUtil, FileMetadata, FileSystem, ResolveError,
|
||||
ResolveOptions, TsConfig,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -79,56 +79,6 @@ impl<Fs: FileSystem> Cache<Fs> {
|
|||
})
|
||||
.map(|r| Arc::clone(r.value()))
|
||||
}
|
||||
|
||||
// Code copied from parcel
|
||||
// <https://github.com/parcel-bundler/parcel/blob/cd0edbccaafeacd2203a34e34570f45e2a10f028/packages/utils/node-resolver-rs/src/path.rs#L64>
|
||||
fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
|
||||
let mut ret = PathBuf::new();
|
||||
let mut seen_links = 0;
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(path.to_path_buf());
|
||||
while let Some(cur_path) = queue.pop_front() {
|
||||
let mut components = cur_path.components();
|
||||
for component in &mut components {
|
||||
match component {
|
||||
Component::Prefix(c) => ret.push(c.as_os_str()),
|
||||
Component::RootDir => {
|
||||
ret.push(component.as_os_str());
|
||||
}
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
ret.pop();
|
||||
}
|
||||
Component::Normal(c) => {
|
||||
ret.push(c);
|
||||
let cached_path = self.value(&ret);
|
||||
let Some(link) = cached_path.symlink(&self.fs)? else {
|
||||
continue;
|
||||
};
|
||||
seen_links += 1;
|
||||
if seen_links > 32 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Too many symlinks",
|
||||
));
|
||||
}
|
||||
if link.is_absolute() {
|
||||
ret = PathBuf::new();
|
||||
} else {
|
||||
ret.pop();
|
||||
}
|
||||
let remaining = components.as_path();
|
||||
if !remaining.as_os_str().is_empty() {
|
||||
queue.push_front(remaining.to_path_buf());
|
||||
}
|
||||
queue.push_front(link);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -179,7 +129,7 @@ pub struct CachedPathImpl {
|
|||
parent: Option<CachedPath>,
|
||||
meta: OnceLock<Option<FileMetadata>>,
|
||||
symlink: OnceLock<Option<PathBuf>>,
|
||||
canonicalized: OnceLock<PathBuf>,
|
||||
canonicalized: OnceLock<Option<PathBuf>>,
|
||||
node_modules: OnceLock<Option<CachedPath>>,
|
||||
package_json: OnceLock<Option<Arc<PackageJson>>>,
|
||||
}
|
||||
|
|
@ -227,7 +177,7 @@ impl CachedPathImpl {
|
|||
.get_or_try_init(|| {
|
||||
if let Ok(symlink_metadata) = fs.symlink_metadata(&self.path) {
|
||||
if symlink_metadata.is_symlink {
|
||||
return fs.read_link(self.path()).map(Some);
|
||||
return fs.canonicalize(self.path()).map(Some);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
|
|
@ -235,8 +185,22 @@ impl CachedPathImpl {
|
|||
.cloned()
|
||||
}
|
||||
|
||||
pub fn canonicalize<Fs: FileSystem>(&self, cache: &Cache<Fs>) -> io::Result<PathBuf> {
|
||||
self.canonicalized.get_or_try_init(|| cache.canonicalize(&self.path)).cloned()
|
||||
pub fn realpath<Fs: FileSystem>(&self, fs: &Fs) -> io::Result<PathBuf> {
|
||||
self.canonicalized
|
||||
.get_or_try_init(|| {
|
||||
if let Some(link) = self.symlink(fs)? {
|
||||
return Ok(Some(link));
|
||||
}
|
||||
if let Some(parent) = self.parent() {
|
||||
let parent_path = parent.realpath(fs)?;
|
||||
return Ok(Some(
|
||||
parent_path.normalize_with(self.path.strip_prefix(&parent.path).unwrap()),
|
||||
));
|
||||
};
|
||||
Ok(None)
|
||||
})
|
||||
.cloned()
|
||||
.map(|r| r.unwrap_or_else(|| self.path.clone().to_path_buf()))
|
||||
}
|
||||
|
||||
pub fn module_directory<Fs: FileSystem>(
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ pub trait FileSystem: Default + Send + Sync {
|
|||
/// See [std::fs::symlink_metadata]
|
||||
fn symlink_metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<FileMetadata>;
|
||||
|
||||
/// See [std::fs::read_link]
|
||||
/// See [std::fs::canonicalize]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// See [std::fs::read_link]
|
||||
fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
|
||||
fn canonicalize<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
|
||||
}
|
||||
|
||||
/// Metadata information about a file.
|
||||
|
|
@ -71,7 +71,7 @@ impl FileSystem for FileSystemOs {
|
|||
fs::symlink_metadata(path).map(FileMetadata::from)
|
||||
}
|
||||
|
||||
fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
|
||||
fs::read_link(path).map(|p| dunce::simplified(&p).to_path_buf())
|
||||
fn canonicalize<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
|
||||
dunce::canonicalize(path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -511,7 +511,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
|
|||
|
||||
fn load_realpath(&self, cached_path: &CachedPath) -> Result<PathBuf, ResolveError> {
|
||||
if self.options.symlinks {
|
||||
cached_path.canonicalize(&self.cache).map_err(ResolveError::from)
|
||||
cached_path.realpath(&self.cache.fs).map_err(ResolveError::from)
|
||||
} else {
|
||||
Ok(cached_path.to_path_buf())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ impl FileSystem for MemoryFS {
|
|||
self.metadata(path)
|
||||
}
|
||||
|
||||
fn read_link<P: AsRef<Path>>(&self, _path: P) -> io::Result<PathBuf> {
|
||||
fn canonicalize<P: AsRef<Path>>(&self, _path: P) -> io::Result<PathBuf> {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not a symlink"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,13 +109,8 @@ fn test() -> io::Result<()> {
|
|||
];
|
||||
|
||||
for (comment, path, request) in pass {
|
||||
let filename = resolver_with_symlinks.resolve(&path, request).map_or_else(
|
||||
|err| {
|
||||
panic!("{err:?} {comment} {path:?} {request}");
|
||||
},
|
||||
|r| r.full_path(),
|
||||
);
|
||||
assert_eq!(filename, root.join("lib/index.js"));
|
||||
let filename = resolver_with_symlinks.resolve(&path, request).map(|r| r.full_path());
|
||||
assert_eq!(filename, Ok(root.join("lib/index.js")), "{comment:?}");
|
||||
|
||||
let resolved_path =
|
||||
resolver_without_symlinks.resolve(&path, request).map(|r| r.full_path());
|
||||
|
|
|
|||
Loading…
Reference in a new issue