Skip to content

Commit d5b88b3

Browse files
authored
perf: reduce memory allocation while resolving package.json (#317)
perf: reduce path allocation while resolving package.json
1 parent 08a19d7 commit d5b88b3

File tree

3 files changed

+40
-39
lines changed

3 files changed

+40
-39
lines changed

src/lib.rs

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -828,12 +828,9 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
828828
// `package.json` "exports", ["node", "require"]) defined in the ESM resolver.
829829
// Note: The subpath is not prepended with a dot on purpose
830830
for exports in package_json.exports_fields(&self.options.exports_fields) {
831-
if let Some(path) = self.package_exports_resolve(
832-
cached_path.path(),
833-
&format!(".{subpath}"),
834-
exports,
835-
ctx,
836-
)? {
831+
if let Some(path) =
832+
self.package_exports_resolve(cached_path, &format!(".{subpath}"), exports, ctx)?
833+
{
837834
// 6. RESOLVE_ESM_MATCH(MATCH)
838835
return self.resolve_esm_match(specifier, &path, ctx);
839836
};
@@ -864,13 +861,16 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
864861
// 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
865862
// "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
866863
// defined in the ESM resolver.
867-
let package_url = package_json.directory();
864+
let package_url = self.cache.value(package_json.directory());
868865
// Note: The subpath is not prepended with a dot on purpose
869866
// because `package_exports_resolve` matches subpath without the leading dot.
870867
for exports in package_json.exports_fields(&self.options.exports_fields) {
871-
if let Some(cached_path) =
872-
self.package_exports_resolve(package_url, &format!(".{subpath}"), exports, ctx)?
873-
{
868+
if let Some(cached_path) = self.package_exports_resolve(
869+
&package_url,
870+
&format!(".{subpath}"),
871+
exports,
872+
ctx,
873+
)? {
874874
// 6. RESOLVE_ESM_MATCH(MATCH)
875875
return self.resolve_esm_match(specifier, &cached_path, ctx);
876876
}
@@ -1270,7 +1270,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
12701270
// 1. Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
12711271
for exports in package_json.exports_fields(&self.options.exports_fields) {
12721272
if let Some(path) = self.package_exports_resolve(
1273-
cached_path.path(),
1273+
&cached_path,
12741274
&format!(".{subpath}"),
12751275
exports,
12761276
ctx,
@@ -1304,7 +1304,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
13041304
/// PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions)
13051305
fn package_exports_resolve(
13061306
&self,
1307-
package_url: &Path,
1307+
package_url: &CachedPath,
13081308
subpath: &str,
13091309
exports: &JSONValue,
13101310
ctx: &mut Ctx,
@@ -1320,7 +1320,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
13201320
without_dot = without_dot || !starts_with_dot_or_hash;
13211321
if has_dot && without_dot {
13221322
return Err(ResolveError::InvalidPackageConfig(
1323-
package_url.join("package.json"),
1323+
package_url.path().join("package.json"),
13241324
));
13251325
}
13261326
}
@@ -1336,7 +1336,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
13361336
let fragment = ctx.fragment.clone().unwrap_or_default();
13371337
return Err(ResolveError::PackagePathNotExported(
13381338
format!("./{}{query}{fragment}", subpath.trim_start_matches('.')),
1339-
package_url.join("package.json"),
1339+
package_url.path().join("package.json"),
13401340
));
13411341
}
13421342
// 1. Let mainExport be undefined.
@@ -1401,7 +1401,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
14011401
// 4. Throw a Package Path Not Exported error.
14021402
Err(ResolveError::PackagePathNotExported(
14031403
subpath.to_string(),
1404-
package_url.join("package.json"),
1404+
package_url.path().join("package.json"),
14051405
))
14061406
}
14071407

@@ -1438,7 +1438,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
14381438
if let Some(path) = self.package_imports_exports_resolve(
14391439
specifier,
14401440
imports,
1441-
package_json.directory(),
1441+
&self.cache.value(package_json.directory()),
14421442
/* is_imports */ true,
14431443
&self.options.condition_names,
14441444
ctx,
@@ -1464,7 +1464,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
14641464
&self,
14651465
match_key: &str,
14661466
match_obj: &JSONMap,
1467-
package_url: &Path,
1467+
package_url: &CachedPath,
14681468
is_imports: bool,
14691469
conditions: &[String],
14701470
ctx: &mut Ctx,
@@ -1548,7 +1548,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
15481548
#[allow(clippy::too_many_arguments)]
15491549
fn package_target_resolve(
15501550
&self,
1551-
package_url: &Path,
1551+
package_url: &CachedPath,
15521552
target_key: &str,
15531553
target: &JSONValue,
15541554
pattern_match: Option<&str>,
@@ -1560,7 +1560,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
15601560
target_key: &'a str,
15611561
target: &'a str,
15621562
pattern_match: Option<&'a str>,
1563-
package_url: &Path,
1563+
package_url: &CachedPath,
15641564
) -> Result<Cow<'a, str>, ResolveError> {
15651565
let target = if let Some(pattern_match) = pattern_match {
15661566
if !target_key.contains('*') && !target.contains('*') {
@@ -1570,7 +1570,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
15701570
Cow::Owned(format!("{target}{pattern_match}"))
15711571
} else {
15721572
return Err(ResolveError::InvalidPackageConfigDirectory(
1573-
package_url.join("package.json"),
1573+
package_url.path().join("package.json"),
15741574
));
15751575
}
15761576
} else {
@@ -1593,16 +1593,15 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
15931593
return Err(ResolveError::InvalidPackageTarget(
15941594
target.to_string(),
15951595
target_key.to_string(),
1596-
package_url.join("package.json"),
1596+
package_url.path().join("package.json"),
15971597
));
15981598
}
15991599
// 2. If patternMatch is a String, then
16001600
// 1. Return PACKAGE_RESOLVE(target with every instance of "*" replaced by patternMatch, packageURL + "/").
16011601
let target =
16021602
normalize_string_target(target_key, target, pattern_match, package_url)?;
1603-
let package_url = self.cache.value(package_url);
16041603
// // 3. Return PACKAGE_RESOLVE(target, packageURL + "/").
1605-
return self.package_resolve(&package_url, &target, ctx);
1604+
return self.package_resolve(package_url, &target, ctx);
16061605
}
16071606

16081607
// 2. If target split on "/" or "\" contains any "", ".", "..", or "node_modules" segments after the first "." segment, case insensitive and including percent encoded variants, throw an Invalid Package Target error.
@@ -1615,14 +1614,12 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
16151614
return Err(ResolveError::InvalidPackageTarget(
16161615
target.to_string(),
16171616
target_key.to_string(),
1618-
package_url.join("package.json"),
1617+
package_url.path().join("package.json"),
16191618
));
16201619
}
1621-
let resolved_target = package_url.normalize_with(target.as_ref());
16221620
// 6. If patternMatch split on "/" or "\" contains any "", ".", "..", or "node_modules" segments, case insensitive and including percent encoded variants, throw an Invalid Module Specifier error.
16231621
// 7. Return the URL resolution of resolvedTarget with every instance of "*" replaced with patternMatch.
1624-
let value = self.cache.value(&resolved_target);
1625-
return Ok(Some(value));
1622+
return Ok(Some(package_url.normalize_with(target.as_ref(), &self.cache)));
16261623
}
16271624
// 2. Otherwise, if target is a non-null Object, then
16281625
JSONValue::Object(target) => {
@@ -1659,7 +1656,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
16591656
// Note: return PackagePathNotExported has the same effect as return because there are no matches.
16601657
return Err(ResolveError::PackagePathNotExported(
16611658
pattern_match.unwrap_or(".").to_string(),
1662-
package_url.join("package.json"),
1659+
package_url.path().join("package.json"),
16631660
));
16641661
}
16651662
// 2. For each item targetValue in target, do

src/tests/exports_field.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,21 +2513,23 @@ fn test_cases() {
25132513
];
25142514

25152515
for case in test_cases {
2516-
let resolved = Resolver::new(ResolveOptions {
2516+
let resolver = Resolver::new(ResolveOptions {
25172517
condition_names: case
25182518
.condition_names
25192519
.iter()
25202520
.map(ToString::to_string)
25212521
.collect::<Vec<_>>(),
25222522
..ResolveOptions::default()
2523-
})
2524-
.package_exports_resolve(
2525-
Path::new(""),
2526-
case.request,
2527-
&case.exports_field,
2528-
&mut Ctx::default(),
2529-
)
2530-
.map(|p| p.map(|p| p.to_path_buf()));
2523+
});
2524+
let cached_path = resolver.cache.value(Path::new(""));
2525+
let resolved = resolver
2526+
.package_exports_resolve(
2527+
&cached_path,
2528+
case.request,
2529+
&case.exports_field,
2530+
&mut Ctx::default(),
2531+
)
2532+
.map(|p| p.map(|p| p.to_path_buf()));
25312533
if let Some(expect) = case.expect {
25322534
if expect.is_empty() {
25332535
assert!(

src/tests/imports_field.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,11 +1296,13 @@ fn test_cases() {
12961296
];
12971297

12981298
for case in test_cases {
1299-
let resolved = Resolver::default()
1299+
let resolver = Resolver::default();
1300+
let cached_path = resolver.cache.value(Path::new(""));
1301+
let resolved = resolver
13001302
.package_imports_exports_resolve(
13011303
case.request,
13021304
&case.imports_field,
1303-
Path::new(""),
1305+
&cached_path,
13041306
true,
13051307
&case.condition_names.iter().map(ToString::to_string).collect::<Vec<_>>(),
13061308
&mut Ctx::default(),

0 commit comments

Comments
 (0)