perf(resolver): use system canonicalize to reduce total number of path hashes (#902)

This commit is contained in:
Boshen 2023-09-12 18:01:39 +08:00 committed by GitHub
parent 2e99af3f67
commit 95cae98e2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 70 deletions

View file

@ -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>(

View file

@ -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)
}
}

View file

@ -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())
}

View file

@ -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"))
}
}

View file

@ -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());