Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

perf: reduce hash while resolving package.json #319

Merged
merged 1 commit into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 29 additions & 30 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,30 @@ impl<Fs: FileSystem> Cache<Fs> {
#[derive(Clone)]
pub struct CachedPath(Arc<CachedPathImpl>);

pub struct CachedPathImpl {
hash: u64,
path: Box<Path>,
parent: Option<CachedPath>,
meta: OnceLock<Option<FileMetadata>>,
canonicalized: OnceLock<Option<CachedPath>>,
node_modules: OnceLock<Option<CachedPath>>,
package_json: OnceLock<Option<(CachedPath, Arc<PackageJson>)>>,
}

impl CachedPathImpl {
const fn new(hash: u64, path: Box<Path>, parent: Option<CachedPath>) -> Self {
Self {
hash,
path,
parent,
meta: OnceLock::new(),
canonicalized: OnceLock::new(),
node_modules: OnceLock::new(),
package_json: OnceLock::new(),
}
}
}

impl Hash for CachedPath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash.hash(state);
Expand Down Expand Up @@ -220,7 +244,7 @@ impl CachedPath {
options: &ResolveOptions,
cache: &Cache<Fs>,
ctx: &mut Ctx,
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError> {
// Change to `std::sync::OnceLock::get_or_try_init` when it is stable.
let result = self
.package_json
Expand All @@ -235,14 +259,13 @@ impl CachedPath {
package_json_path.clone()
};
PackageJson::parse(package_json_path.clone(), real_path, &package_json_string)
.map(Arc::new)
.map(Some)
.map(|package_json| Some((self.clone(), (Arc::new(package_json)))))
.map_err(|error| ResolveError::from_serde_json_error(package_json_path, &error))
})
.cloned();
// https://github.com/webpack/enhanced-resolve/blob/58464fc7cb56673c9aa849e68e6300239601e615/lib/DescriptionFileUtils.js#L68-L82
match &result {
Ok(Some(package_json)) => {
Ok(Some((_, package_json))) => {
ctx.add_file_dependency(&package_json.path);
}
Ok(None) => {
Expand Down Expand Up @@ -270,7 +293,7 @@ impl CachedPath {
options: &ResolveOptions,
cache: &Cache<Fs>,
ctx: &mut Ctx,
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError> {
let mut cache_value = self;
// Go up directories when the querying path is not a directory
while !cache_value.is_dir(&cache.fs, ctx) {
Expand All @@ -283,7 +306,7 @@ impl CachedPath {
let mut cache_value = Some(cache_value);
while let Some(cv) = cache_value {
if let Some(package_json) = cv.package_json(options, cache, ctx)? {
return Ok(Some(Arc::clone(&package_json)));
return Ok(Some(package_json));
}
cache_value = cv.parent.as_ref();
}
Expand Down Expand Up @@ -357,30 +380,6 @@ impl CachedPath {
}
}

pub struct CachedPathImpl {
hash: u64,
path: Box<Path>,
parent: Option<CachedPath>,
meta: OnceLock<Option<FileMetadata>>,
canonicalized: OnceLock<Option<CachedPath>>,
node_modules: OnceLock<Option<CachedPath>>,
package_json: OnceLock<Option<Arc<PackageJson>>>,
}

impl CachedPathImpl {
const fn new(hash: u64, path: Box<Path>, parent: Option<CachedPath>) -> Self {
Self {
hash,
path,
parent,
meta: OnceLock::new(),
canonicalized: OnceLock::new(),
node_modules: OnceLock::new(),
package_json: OnceLock::new(),
}
}
}

/// Memoized cache key, code adapted from <https://stackoverflow.com/a/50478038>.
trait CacheKey {
fn tuple(&self) -> (u64, &Path);
Expand Down
30 changes: 17 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,15 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
// enhanced-resolve: restrictions
self.check_restrictions(&path)?;
let package_json = cached_path.find_package_json(&self.options, &self.cache, ctx)?;
if let Some(package_json) = &package_json {
if let Some((_, package_json)) = &package_json {
// path must be inside the package.
debug_assert!(path.starts_with(package_json.directory()));
}
Ok(Resolution {
path,
query: ctx.query.take(),
fragment: ctx.fragment.take(),
package_json,
package_json: package_json.map(|(_, p)| p),
})
}

Expand Down Expand Up @@ -499,7 +499,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
) -> ResolveResult {
// 1. Find the closest package scope SCOPE to DIR.
// 2. If no scope was found, return.
let Some(package_json) = cached_path.find_package_json(&self.options, &self.cache, ctx)?
let Some((_, package_json)) =
cached_path.find_package_json(&self.options, &self.cache, ctx)?
else {
return Ok(None);
};
Expand Down Expand Up @@ -538,7 +539,9 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
// 1. If X/package.json is a file,
if !self.options.description_files.is_empty() {
// a. Parse X/package.json, and look for "main" field.
if let Some(package_json) = cached_path.package_json(&self.options, &self.cache, ctx)? {
if let Some((_, package_json)) =
cached_path.package_json(&self.options, &self.cache, ctx)?
{
// b. If "main" is a falsy value, GOTO 2.
for main_field in package_json.main_fields(&self.options.main_fields) {
// c. let M = X + (json main field)
Expand Down Expand Up @@ -657,11 +660,11 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {

fn load_alias_or_file(&self, cached_path: &CachedPath, ctx: &mut Ctx) -> ResolveResult {
if !self.options.alias_fields.is_empty() {
if let Some(package_json) =
if let Some((package_url, package_json)) =
cached_path.find_package_json(&self.options, &self.cache, ctx)?
{
if let Some(path) =
self.load_browser_field(cached_path, None, &package_json, ctx)?
self.load_browser_field(cached_path, None, &package_url, &package_json, ctx)?
{
return Ok(Some(path));
}
Expand Down Expand Up @@ -815,7 +818,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
) -> ResolveResult {
// 2. If X does not match this pattern or DIR/NAME/package.json is not a file,
// return.
let Some(package_json) = cached_path.package_json(&self.options, &self.cache, ctx)? else {
let Some((_, package_json)) = cached_path.package_json(&self.options, &self.cache, ctx)?
else {
return Ok(None);
};
// 3. Parse DIR/NAME/package.json, and look for "exports" field.
Expand All @@ -842,7 +846,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
) -> ResolveResult {
// 1. Find the closest package scope SCOPE to DIR.
// 2. If no scope was found, return.
let Some(package_json) = cached_path.find_package_json(&self.options, &self.cache, ctx)?
let Some((package_url, package_json)) =
cached_path.find_package_json(&self.options, &self.cache, ctx)?
else {
return Ok(None);
};
Expand All @@ -856,7 +861,6 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
// 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
// "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
// defined in the ESM resolver.
let package_url = self.cache.value(package_json.directory());
// Note: The subpath is not prepended with a dot on purpose
// because `package_exports_resolve` matches subpath without the leading dot.
for exports in package_json.exports_fields(&self.options.exports_fields) {
Expand All @@ -871,7 +875,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
}
}
self.load_browser_field(cached_path, Some(specifier), &package_json, ctx)
self.load_browser_field(cached_path, Some(specifier), &package_url, &package_json, ctx)
}

/// RESOLVE_ESM_MATCH(MATCH)
Expand All @@ -897,6 +901,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
&self,
cached_path: &CachedPath,
module_specifier: Option<&str>,
package_url: &CachedPath,
package_json: &PackageJson,
ctx: &mut Ctx,
) -> ResolveResult {
Expand Down Expand Up @@ -926,8 +931,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
ctx.with_resolving_alias(new_specifier.to_string());
ctx.with_fully_specified(false);
let cached_path = self.cache.value(package_json.directory());
self.require(&cached_path, new_specifier, ctx).map(Some)
self.require(package_url, new_specifier, ctx).map(Some)
}

/// enhanced-resolve: AliasPlugin for [ResolveOptions::alias] and [ResolveOptions::fallback].
Expand Down Expand Up @@ -1258,7 +1262,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
// 1. Continue the next loop iteration.
if cached_path.is_dir(&self.cache.fs, ctx) {
// 4. Let pjson be the result of READ_PACKAGE_JSON(packageURL).
if let Some(package_json) =
if let Some((_, package_json)) =
cached_path.package_json(&self.options, &self.cache, ctx)?
{
// 5. If pjson is not null and pjson.exports is not null or undefined, then
Expand Down