Skip to content

Commit 5fc7aa5

Browse files
committed
refactor: use RwLock<Vec<Arc<PackageJson>> for package.json storage
1 parent be1e1b4 commit 5fc7aa5

File tree

8 files changed

+97
-40
lines changed

8 files changed

+97
-40
lines changed

Cargo.lock

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ indexmap = { version = "2", features = ["serde"] }
8282
json-strip-comments = "3"
8383
once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
8484
papaya = "0.2"
85+
parking_lot = "0.12"
8586
rustc-hash = { version = "2" }
8687
serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json
8788
serde_json = { version = "1", features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`

src/cache/cache_impl.rs

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use cfg_if::cfg_if;
1111
#[cfg(feature = "yarn_pnp")]
1212
use once_cell::sync::OnceCell;
1313
use papaya::{HashMap, HashSet};
14+
use parking_lot::RwLock;
1415
use rustc_hash::FxHasher;
1516

1617
use super::borrowed_path::BorrowedCachedPath;
@@ -21,11 +22,14 @@ use crate::{
2122
context::ResolveContext as Ctx, path::PathUtil,
2223
};
2324

25+
pub type PackageJsonIndex = usize;
26+
2427
/// Cache implementation used for caching filesystem access.
2528
#[derive(Default)]
2629
pub struct Cache<Fs> {
2730
pub(crate) fs: Fs,
2831
pub(crate) paths: HashSet<CachedPath, BuildHasherDefault<IdentityHasher>>,
32+
pub(crate) package_jsons: RwLock<Vec<Arc<PackageJson>>>,
2933
pub(crate) tsconfigs: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
3034
#[cfg(feature = "yarn_pnp")]
3135
pub(crate) yarn_pnp_manifest: OnceCell<pnp::Manifest>,
@@ -35,6 +39,7 @@ impl<Fs: FileSystem> Cache<Fs> {
3539
pub fn clear(&self) {
3640
self.paths.pin().clear();
3741
self.tsconfigs.pin().clear();
42+
self.package_jsons.write().clear();
3843
}
3944

4045
#[allow(clippy::cast_possible_truncation)]
@@ -110,38 +115,45 @@ impl<Fs: FileSystem> Cache<Fs> {
110115
.get_or_try_init(|| {
111116
let package_json_path = path.path.join("package.json");
112117
let Ok(package_json_bytes) = self.fs.read(&package_json_path) else {
118+
if let Some(deps) = &mut ctx.missing_dependencies {
119+
deps.push(package_json_path.clone());
120+
}
113121
return Ok(None);
114122
};
115-
116123
let real_path = if options.symlinks {
117124
self.canonicalize(path)?.join("package.json")
118125
} else {
119126
package_json_path.clone()
120127
};
121-
PackageJson::parse(&self.fs, package_json_path, real_path, package_json_bytes)
122-
.map(|package_json| Some(Arc::new(package_json)))
123-
.map_err(ResolveError::Json)
128+
PackageJson::parse(
129+
&self.fs,
130+
package_json_path.clone(),
131+
real_path,
132+
package_json_bytes,
133+
)
134+
.map(|package_json| {
135+
let arc = Arc::new(package_json);
136+
let mut arena = self.package_jsons.write();
137+
let index = arena.len();
138+
arena.push(arc);
139+
Some(index)
140+
})
141+
.map_err(ResolveError::Json)
142+
// https://github.com/webpack/enhanced-resolve/blob/58464fc7cb56673c9aa849e68e6300239601e615/lib/DescriptionFileUtils.js#L68-L82
143+
.inspect(|_| {
144+
ctx.add_file_dependency(&package_json_path);
145+
})
146+
.inspect_err(|_| {
147+
if let Some(deps) = &mut ctx.file_dependencies {
148+
deps.push(package_json_path.clone());
149+
}
150+
})
124151
})
125152
.cloned();
126153

127-
// https://github.com/webpack/enhanced-resolve/blob/58464fc7cb56673c9aa849e68e6300239601e615/lib/DescriptionFileUtils.js#L68-L82
128-
match &result {
129-
Ok(Some(package_json)) => {
130-
ctx.add_file_dependency(&package_json.path);
131-
}
132-
Ok(None) => {
133-
// Avoid an allocation by making this lazy
134-
if let Some(deps) = &mut ctx.missing_dependencies {
135-
deps.push(path.path.join("package.json"));
136-
}
137-
}
138-
Err(_) => {
139-
if let Some(deps) = &mut ctx.file_dependencies {
140-
deps.push(path.path.join("package.json"));
141-
}
142-
}
143-
}
144-
result
154+
result.map(|option_index| {
155+
option_index.and_then(|index| self.package_jsons.read().get(index).cloned())
156+
})
145157
}
146158

147159
pub(crate) fn get_tsconfig<F: FnOnce(&mut TsConfig) -> Result<(), ResolveError>>(
@@ -213,6 +225,7 @@ impl<Fs: FileSystem> Cache<Fs> {
213225
.hasher(BuildHasherDefault::default())
214226
.resize_mode(papaya::ResizeMode::Blocking)
215227
.build(),
228+
package_jsons: RwLock::new(Vec::with_capacity(512)),
216229
tsconfigs: HashMap::builder()
217230
.hasher(BuildHasherDefault::default())
218231
.resize_mode(papaya::ResizeMode::Blocking)

src/cache/cached_path.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use cfg_if::cfg_if;
1111
use once_cell::sync::OnceCell as OnceLock;
1212

1313
use super::cache_impl::Cache;
14+
use super::cache_impl::PackageJsonIndex;
1415
use super::thread_local::SCRATCH_PATH;
1516
use crate::{
1617
FileSystem, PackageJson, ResolveError, ResolveOptions, TsConfig, context::ResolveContext as Ctx,
@@ -28,7 +29,7 @@ pub struct CachedPathImpl {
2829
pub meta: OnceLock<Option<(/* is_file */ bool, /* is_dir */ bool)>>, // None means not found.
2930
pub canonicalized: OnceLock<Weak<CachedPathImpl>>,
3031
pub node_modules: OnceLock<Option<Weak<CachedPathImpl>>>,
31-
pub package_json: OnceLock<Option<Arc<PackageJson>>>,
32+
pub package_json: OnceLock<Option<PackageJsonIndex>>,
3233
pub tsconfig: OnceLock<Option<Arc<TsConfig>>>,
3334
}
3435

src/package_json/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ pub use serde::*;
1414
#[cfg(target_endian = "little")]
1515
pub use simd::*;
1616

17-
use std::{fmt, path::PathBuf};
17+
use std::{fmt, path::Path};
1818

1919
use crate::JSONError;
2020

2121
/// Check if JSON content is empty or contains only whitespace
22-
fn check_if_empty(json_bytes: &[u8], path: PathBuf) -> Result<(), JSONError> {
22+
fn check_if_empty(json_bytes: &[u8], path: &Path) -> Result<(), JSONError> {
2323
// Check if content is empty or whitespace-only
2424
if json_bytes.iter().all(|&b| b.is_ascii_whitespace()) {
25-
return Err(JSONError { path, message: "File is empty".to_string(), line: 0, column: 0 });
25+
return Err(JSONError {
26+
path: path.to_path_buf(),
27+
message: "File is empty".to_string(),
28+
line: 0,
29+
column: 0,
30+
});
2631
}
2732
Ok(())
2833
}

src/package_json/serde.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl PackageJson {
230230
let json_bytes = if json.starts_with(b"\xEF\xBB\xBF") { &json[3..] } else { &json[..] };
231231

232232
// Check if empty after BOM stripping
233-
super::check_if_empty(json_bytes, path.clone())?;
233+
super::check_if_empty(json_bytes, &path)?;
234234

235235
// Parse JSON directly from bytes
236236
let value = serde_json::from_slice::<Value>(json_bytes).map_err(|error| JSONError {

src/package_json/simd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl PackageJson {
269269
}
270270

271271
// Check if empty after BOM stripping
272-
super::check_if_empty(&json_bytes, path.clone())?;
272+
super::check_if_empty(&json_bytes, &path)?;
273273

274274
// Create the self-cell with the JSON bytes and parsed BorrowedValue
275275
let cell = PackageJsonCell::try_new(MutBorrow::new(json_bytes), |bytes| {

src/tests/dependencies.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! https://github.com/webpack/enhanced-resolve/blob/main/test/dependencies.test.js
22
33
#[cfg(not(target_os = "windows"))] // MemoryFS's path separator is always `/` so the test will not pass in windows.
4-
mod windows {
4+
mod test {
55
use std::path::PathBuf;
66

77
use super::super::memory_fs::MemoryFS;
@@ -18,17 +18,6 @@ mod windows {
1818

1919
#[test]
2020
fn test() {
21-
let file_system = file_system();
22-
23-
let resolver = ResolverGeneric::new_with_file_system(
24-
file_system,
25-
ResolveOptions {
26-
extensions: vec![".json".into(), ".js".into()],
27-
modules: vec!["/modules".into(), "node_modules".into()],
28-
..ResolveOptions::default()
29-
},
30-
);
31-
3221
let data = [
3322
(
3423
"middle module request",
@@ -92,6 +81,15 @@ mod windows {
9281
];
9382

9483
for (name, context, request, result, file_dependencies, missing_dependencies) in data {
84+
let file_system = file_system();
85+
let resolver = ResolverGeneric::new_with_file_system(
86+
file_system,
87+
ResolveOptions {
88+
extensions: vec![".json".into(), ".js".into()],
89+
modules: vec!["/modules".into(), "node_modules".into()],
90+
..ResolveOptions::default()
91+
},
92+
);
9593
let mut ctx = ResolveContext::default();
9694
let path = PathBuf::from(context);
9795
let resolved_path =

0 commit comments

Comments
 (0)