Skip to content

Commit a67f304

Browse files
committed
perf: cache all package.json resolutions for faster package.json lookup (#853)
Previously `find_package_json` needs to traverse up path parents, which involves a lot of `once_cell.get_or_try_init()` calls.
1 parent 15e1481 commit a67f304

File tree

3 files changed

+62
-49
lines changed

3 files changed

+62
-49
lines changed

src/cache/cache_impl.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,74 @@ impl<Fs: FileSystem> Cache<Fs> {
103103
)
104104
}
105105

106+
/// Get package.json of a path of `path`.
107+
///
108+
/// # Errors
109+
///
110+
/// * [ResolveError::Json]
106111
pub(crate) fn get_package_json(
107112
&self,
108113
path: &CachedPath,
109114
options: &ResolveOptions,
110115
ctx: &mut Ctx,
111116
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
117+
self.find_package_json(path, options, ctx).map(|option_package_json| {
118+
option_package_json.filter(|package_json| {
119+
package_json
120+
.path()
121+
.parent()
122+
.is_some_and(|p| p.as_os_str() == path.path().as_os_str())
123+
})
124+
})
125+
}
126+
127+
/// Find package.json of a path by traversing parent directories.
128+
///
129+
/// # Errors
130+
///
131+
/// * [ResolveError::Json]
132+
pub(crate) fn find_package_json(
133+
&self,
134+
path: &CachedPath,
135+
options: &ResolveOptions,
136+
ctx: &mut Ctx,
137+
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
138+
let mut path = path.clone();
139+
// Go up directories when the querying path is not a directory
140+
while !self.is_dir(&path, ctx) {
141+
if let Some(cv) = path.parent() {
142+
path = cv;
143+
} else {
144+
break;
145+
}
146+
}
147+
self.find_package_json_impl(&path, options, ctx).map(|option_index| {
148+
option_index.and_then(|index| self.package_jsons.read().get(index).cloned())
149+
})
150+
}
151+
152+
/// Find package.json of a path by traversing parent directories.
153+
///
154+
/// # Errors
155+
///
156+
/// * [ResolveError::Json]
157+
fn find_package_json_impl(
158+
&self,
159+
path: &CachedPath,
160+
options: &ResolveOptions,
161+
ctx: &mut Ctx,
162+
) -> Result<Option<PackageJsonIndex>, ResolveError> {
112163
// Change to `std::sync::OnceLock::get_or_try_init` when it is stable.
113-
let result = path
114-
.package_json
164+
path.package_json
115165
.get_or_try_init(|| {
116166
let package_json_path = path.path.join("package.json");
117167
let Ok(package_json_bytes) = self.fs.read(&package_json_path) else {
118168
if let Some(deps) = &mut ctx.missing_dependencies {
119169
deps.push(package_json_path);
120170
}
121-
return Ok(None);
171+
return path.parent().map_or(Ok(None), |parent| {
172+
self.find_package_json_impl(&parent, options, ctx)
173+
});
122174
};
123175
let real_path = if options.symlinks {
124176
self.canonicalize(path)?.join("package.json")
@@ -152,11 +204,7 @@ impl<Fs: FileSystem> Cache<Fs> {
152204
}
153205
})
154206
})
155-
.cloned();
156-
157-
result.map(|option_index| {
158-
option_index.and_then(|index| self.package_jsons.read().get(index).cloned())
159-
})
207+
.cloned()
160208
}
161209

162210
pub(crate) fn get_tsconfig<F: FnOnce(&mut TsConfig) -> Result<(), ResolveError>>(

src/cache/cached_path.rs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ use once_cell::sync::OnceCell as OnceLock;
1313
use super::cache_impl::Cache;
1414
use super::cache_impl::PackageJsonIndex;
1515
use super::thread_local::SCRATCH_PATH;
16-
use crate::{
17-
FileSystem, PackageJson, ResolveError, ResolveOptions, TsConfig, context::ResolveContext as Ctx,
18-
};
16+
use crate::{FileSystem, TsConfig, context::ResolveContext as Ctx};
1917

2018
#[derive(Clone)]
2119
pub struct CachedPath(pub Arc<CachedPathImpl>);
@@ -108,36 +106,6 @@ impl CachedPath {
108106
.and_then(|weak| weak.upgrade().map(CachedPath))
109107
}
110108

111-
/// Find package.json of a path by traversing parent directories.
112-
///
113-
/// # Errors
114-
///
115-
/// * [ResolveError::Json]
116-
pub(crate) fn find_package_json<Fs: FileSystem>(
117-
&self,
118-
options: &ResolveOptions,
119-
cache: &Cache<Fs>,
120-
ctx: &mut Ctx,
121-
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
122-
let mut cache_value = self.clone();
123-
// Go up directories when the querying path is not a directory
124-
while !cache.is_dir(&cache_value, ctx) {
125-
if let Some(cv) = cache_value.parent() {
126-
cache_value = cv;
127-
} else {
128-
break;
129-
}
130-
}
131-
let mut cache_value = Some(cache_value);
132-
while let Some(cv) = cache_value {
133-
if let Some(package_json) = cache.get_package_json(&cv, options, ctx)? {
134-
return Ok(Some(package_json));
135-
}
136-
cache_value = cv.parent();
137-
}
138-
Ok(None)
139-
}
140-
141109
pub(crate) fn add_extension<Fs: FileSystem>(&self, ext: &str, cache: &Cache<Fs>) -> Self {
142110
SCRATCH_PATH.with_borrow_mut(|path| {
143111
path.clear();

src/lib.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
304304
}
305305
Ok(last)
306306
} else {
307-
cached_path.find_package_json(&self.options, self.cache.as_ref(), ctx)
307+
self.cache.find_package_json(cached_path, &self.options, ctx)
308308
}
309309
}
310310

@@ -594,8 +594,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
594594
) -> ResolveResult {
595595
// 1. Find the closest package scope SCOPE to DIR.
596596
// 2. If no scope was found, return.
597-
let Some(package_json) =
598-
cached_path.find_package_json(&self.options, self.cache.as_ref(), ctx)?
597+
let Some(package_json) = self.cache.find_package_json(cached_path, &self.options, ctx)?
599598
else {
600599
return Ok(None);
601600
};
@@ -778,7 +777,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
778777
) -> ResolveResult {
779778
if !self.options.alias_fields.is_empty()
780779
&& let Some(package_json) =
781-
cached_path.find_package_json(&self.options, self.cache.as_ref(), ctx)?
780+
self.cache.find_package_json(cached_path, &self.options, ctx)?
782781
&& let Some(path) = self.load_browser_field(cached_path, None, &package_json, ctx)?
783782
{
784783
return Ok(Some(path));
@@ -1030,8 +1029,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
10301029
) -> ResolveResult {
10311030
// 1. Find the closest package scope SCOPE to DIR.
10321031
// 2. If no scope was found, return.
1033-
let Some(package_json) =
1034-
cached_path.find_package_json(&self.options, self.cache.as_ref(), ctx)?
1032+
let Some(package_json) = self.cache.find_package_json(cached_path, &self.options, ctx)?
10351033
else {
10361034
return Ok(None);
10371035
};
@@ -2104,8 +2102,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
21042102
Some("js" | "ts") => {
21052103
// 7. Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(url).
21062104
// 8. Let pjson be the result of READ_PACKAGE_JSON(packageURL).
2107-
let package_json =
2108-
cached_path.find_package_json(&self.options, self.cache.as_ref(), ctx)?;
2105+
let package_json = self.cache.find_package_json(cached_path, &self.options, ctx)?;
21092106
// 9. Let packageType be null.
21102107
if let Some(package_json) = package_json {
21112108
// 10. If pjson?.type is "module" or "commonjs", then

0 commit comments

Comments
 (0)