Skip to content

Commit 47fcbae

Browse files
authored
perf: reduce hash while resolving package.json (#319)
1 parent e903e4e commit 47fcbae

File tree

2 files changed

+46
-43
lines changed

2 files changed

+46
-43
lines changed

src/cache.rs

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,30 @@ impl<Fs: FileSystem> Cache<Fs> {
9797
#[derive(Clone)]
9898
pub struct CachedPath(Arc<CachedPathImpl>);
9999

100+
pub struct CachedPathImpl {
101+
hash: u64,
102+
path: Box<Path>,
103+
parent: Option<CachedPath>,
104+
meta: OnceLock<Option<FileMetadata>>,
105+
canonicalized: OnceLock<Option<CachedPath>>,
106+
node_modules: OnceLock<Option<CachedPath>>,
107+
package_json: OnceLock<Option<(CachedPath, Arc<PackageJson>)>>,
108+
}
109+
110+
impl CachedPathImpl {
111+
const fn new(hash: u64, path: Box<Path>, parent: Option<CachedPath>) -> Self {
112+
Self {
113+
hash,
114+
path,
115+
parent,
116+
meta: OnceLock::new(),
117+
canonicalized: OnceLock::new(),
118+
node_modules: OnceLock::new(),
119+
package_json: OnceLock::new(),
120+
}
121+
}
122+
}
123+
100124
impl Hash for CachedPath {
101125
fn hash<H: Hasher>(&self, state: &mut H) {
102126
self.0.hash.hash(state);
@@ -220,7 +244,7 @@ impl CachedPath {
220244
options: &ResolveOptions,
221245
cache: &Cache<Fs>,
222246
ctx: &mut Ctx,
223-
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
247+
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError> {
224248
// Change to `std::sync::OnceLock::get_or_try_init` when it is stable.
225249
let result = self
226250
.package_json
@@ -235,14 +259,13 @@ impl CachedPath {
235259
package_json_path.clone()
236260
};
237261
PackageJson::parse(package_json_path.clone(), real_path, &package_json_string)
238-
.map(Arc::new)
239-
.map(Some)
262+
.map(|package_json| Some((self.clone(), (Arc::new(package_json)))))
240263
.map_err(|error| ResolveError::from_serde_json_error(package_json_path, &error))
241264
})
242265
.cloned();
243266
// https://github.com/webpack/enhanced-resolve/blob/58464fc7cb56673c9aa849e68e6300239601e615/lib/DescriptionFileUtils.js#L68-L82
244267
match &result {
245-
Ok(Some(package_json)) => {
268+
Ok(Some((_, package_json))) => {
246269
ctx.add_file_dependency(&package_json.path);
247270
}
248271
Ok(None) => {
@@ -270,7 +293,7 @@ impl CachedPath {
270293
options: &ResolveOptions,
271294
cache: &Cache<Fs>,
272295
ctx: &mut Ctx,
273-
) -> Result<Option<Arc<PackageJson>>, ResolveError> {
296+
) -> Result<Option<(Self, Arc<PackageJson>)>, ResolveError> {
274297
let mut cache_value = self;
275298
// Go up directories when the querying path is not a directory
276299
while !cache_value.is_dir(&cache.fs, ctx) {
@@ -283,7 +306,7 @@ impl CachedPath {
283306
let mut cache_value = Some(cache_value);
284307
while let Some(cv) = cache_value {
285308
if let Some(package_json) = cv.package_json(options, cache, ctx)? {
286-
return Ok(Some(Arc::clone(&package_json)));
309+
return Ok(Some(package_json));
287310
}
288311
cache_value = cv.parent.as_ref();
289312
}
@@ -357,30 +380,6 @@ impl CachedPath {
357380
}
358381
}
359382

360-
pub struct CachedPathImpl {
361-
hash: u64,
362-
path: Box<Path>,
363-
parent: Option<CachedPath>,
364-
meta: OnceLock<Option<FileMetadata>>,
365-
canonicalized: OnceLock<Option<CachedPath>>,
366-
node_modules: OnceLock<Option<CachedPath>>,
367-
package_json: OnceLock<Option<Arc<PackageJson>>>,
368-
}
369-
370-
impl CachedPathImpl {
371-
const fn new(hash: u64, path: Box<Path>, parent: Option<CachedPath>) -> Self {
372-
Self {
373-
hash,
374-
path,
375-
parent,
376-
meta: OnceLock::new(),
377-
canonicalized: OnceLock::new(),
378-
node_modules: OnceLock::new(),
379-
package_json: OnceLock::new(),
380-
}
381-
}
382-
}
383-
384383
/// Memoized cache key, code adapted from <https://stackoverflow.com/a/50478038>.
385384
trait CacheKey {
386385
fn tuple(&self) -> (u64, &Path);

src/lib.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,15 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
250250
// enhanced-resolve: restrictions
251251
self.check_restrictions(&path)?;
252252
let package_json = cached_path.find_package_json(&self.options, &self.cache, ctx)?;
253-
if let Some(package_json) = &package_json {
253+
if let Some((_, package_json)) = &package_json {
254254
// path must be inside the package.
255255
debug_assert!(path.starts_with(package_json.directory()));
256256
}
257257
Ok(Resolution {
258258
path,
259259
query: ctx.query.take(),
260260
fragment: ctx.fragment.take(),
261-
package_json,
261+
package_json: package_json.map(|(_, p)| p),
262262
})
263263
}
264264

@@ -499,7 +499,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
499499
) -> ResolveResult {
500500
// 1. Find the closest package scope SCOPE to DIR.
501501
// 2. If no scope was found, return.
502-
let Some(package_json) = cached_path.find_package_json(&self.options, &self.cache, ctx)?
502+
let Some((_, package_json)) =
503+
cached_path.find_package_json(&self.options, &self.cache, ctx)?
503504
else {
504505
return Ok(None);
505506
};
@@ -538,7 +539,9 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
538539
// 1. If X/package.json is a file,
539540
if !self.options.description_files.is_empty() {
540541
// a. Parse X/package.json, and look for "main" field.
541-
if let Some(package_json) = cached_path.package_json(&self.options, &self.cache, ctx)? {
542+
if let Some((_, package_json)) =
543+
cached_path.package_json(&self.options, &self.cache, ctx)?
544+
{
542545
// b. If "main" is a falsy value, GOTO 2.
543546
for main_field in package_json.main_fields(&self.options.main_fields) {
544547
// c. let M = X + (json main field)
@@ -657,11 +660,11 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
657660

658661
fn load_alias_or_file(&self, cached_path: &CachedPath, ctx: &mut Ctx) -> ResolveResult {
659662
if !self.options.alias_fields.is_empty() {
660-
if let Some(package_json) =
663+
if let Some((package_url, package_json)) =
661664
cached_path.find_package_json(&self.options, &self.cache, ctx)?
662665
{
663666
if let Some(path) =
664-
self.load_browser_field(cached_path, None, &package_json, ctx)?
667+
self.load_browser_field(cached_path, None, &package_url, &package_json, ctx)?
665668
{
666669
return Ok(Some(path));
667670
}
@@ -815,7 +818,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
815818
) -> ResolveResult {
816819
// 2. If X does not match this pattern or DIR/NAME/package.json is not a file,
817820
// return.
818-
let Some(package_json) = cached_path.package_json(&self.options, &self.cache, ctx)? else {
821+
let Some((_, package_json)) = cached_path.package_json(&self.options, &self.cache, ctx)?
822+
else {
819823
return Ok(None);
820824
};
821825
// 3. Parse DIR/NAME/package.json, and look for "exports" field.
@@ -842,7 +846,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
842846
) -> ResolveResult {
843847
// 1. Find the closest package scope SCOPE to DIR.
844848
// 2. If no scope was found, return.
845-
let Some(package_json) = cached_path.find_package_json(&self.options, &self.cache, ctx)?
849+
let Some((package_url, package_json)) =
850+
cached_path.find_package_json(&self.options, &self.cache, ctx)?
846851
else {
847852
return Ok(None);
848853
};
@@ -856,7 +861,6 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
856861
// 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
857862
// "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
858863
// defined in the ESM resolver.
859-
let package_url = self.cache.value(package_json.directory());
860864
// Note: The subpath is not prepended with a dot on purpose
861865
// because `package_exports_resolve` matches subpath without the leading dot.
862866
for exports in package_json.exports_fields(&self.options.exports_fields) {
@@ -871,7 +875,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
871875
}
872876
}
873877
}
874-
self.load_browser_field(cached_path, Some(specifier), &package_json, ctx)
878+
self.load_browser_field(cached_path, Some(specifier), &package_url, &package_json, ctx)
875879
}
876880

877881
/// RESOLVE_ESM_MATCH(MATCH)
@@ -897,6 +901,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
897901
&self,
898902
cached_path: &CachedPath,
899903
module_specifier: Option<&str>,
904+
package_url: &CachedPath,
900905
package_json: &PackageJson,
901906
ctx: &mut Ctx,
902907
) -> ResolveResult {
@@ -926,8 +931,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
926931
}
927932
ctx.with_resolving_alias(new_specifier.to_string());
928933
ctx.with_fully_specified(false);
929-
let cached_path = self.cache.value(package_json.directory());
930-
self.require(&cached_path, new_specifier, ctx).map(Some)
934+
self.require(package_url, new_specifier, ctx).map(Some)
931935
}
932936

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

0 commit comments

Comments
 (0)