Skip to content

Commit c2da024

Browse files
committed
perf: use simd-json for package.json parsing (#761)
## Summary Replace `serde_json` with `simd-json` using `BorrowedValue` for zero-copy JSON parsing. Uses `self_cell` to manage the self-referential structure where the parsed JSON borrows from the original string. Breaking change: * raw API with `package_json_raw_json_api` feature is removed. * Returned error no longer has line and column information. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 19902fb commit c2da024

File tree

13 files changed

+575
-452
lines changed

13 files changed

+575
-452
lines changed

Cargo.lock

Lines changed: 87 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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,11 @@ json-strip-comments = "3"
8383
once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
8484
papaya = "0.2"
8585
rustc-hash = { version = "2" }
86+
self_cell = "1"
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"]`
89+
# Omit serde_impl and swar-number-parsing (package.json seldom has floats).
90+
simd-json = { version = "0.17.0", default-features = false, features = ["runtime-detection"] }
8891
simdutf8 = { version = "0.1" }
8992
thiserror = "2"
9093
tracing = "0.1"
@@ -115,9 +118,6 @@ windows-sys = { version = "0.61.0", features = ["Win32_Storage", "Win32_Storage_
115118

116119
[features]
117120
default = []
118-
## Enables the [PackageJson::raw_json] API,
119-
## which returns the `package.json` with `serde_json::Value`.
120-
package_json_raw_json_api = []
121121
## [Yarn Plug'n'Play](https://yarnpkg.com/features/pnp)
122122
yarn_pnp = ["pnp"]
123123
# For codspeed benchmark

benches/resolver.rs

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -283,33 +283,24 @@ fn bench_package_json_deserialization(c: &mut Criterion) {
283283
let test_path = PathBuf::from("/test/package.json");
284284
let test_realpath = test_path.clone();
285285

286-
group.bench_function("small", |b| {
287-
b.iter(|| {
288-
PackageJson::parse(test_path.clone(), test_realpath.clone(), small_json)
289-
.expect("Failed to parse small JSON");
290-
});
291-
});
292-
293-
group.bench_function("medium", |b| {
294-
b.iter(|| {
295-
PackageJson::parse(test_path.clone(), test_realpath.clone(), medium_json)
296-
.expect("Failed to parse medium JSON");
297-
});
298-
});
286+
let data = [
287+
("small", small_json.to_string()),
288+
("medium", medium_json.to_string()),
289+
("large", large_json.to_string()),
290+
("complex_real", complex_json),
291+
];
299292

300-
group.bench_function("large", |b| {
301-
b.iter(|| {
302-
PackageJson::parse(test_path.clone(), test_realpath.clone(), large_json)
303-
.expect("Failed to parse large JSON");
304-
});
305-
});
306-
307-
group.bench_function("complex_real", |b| {
308-
b.iter(|| {
309-
PackageJson::parse(test_path.clone(), test_realpath.clone(), &complex_json)
310-
.expect("Failed to parse complex JSON");
293+
for (name, json) in data {
294+
group.bench_function(name, |b| {
295+
b.iter_with_setup_wrapper(|runner| {
296+
let json = json.clone();
297+
runner.run(|| {
298+
PackageJson::parse(test_path.clone(), test_realpath.clone(), json)
299+
.expect("Failed to parse JSON");
300+
});
301+
});
311302
});
312-
});
303+
}
313304

314305
group.finish();
315306
}

deny.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ allow = [
9696
"Unicode-3.0",
9797
"BSD-2-Clause",
9898
"MPL-2.0",
99+
"Zlib",
99100
# "Apache-2.0 WITH LLVM-exception",
100101
]
101102
# The confidence threshold for detecting a license from license text.

src/cache/cache_impl.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ impl<Fs: FileSystem> Cache<Fs> {
120120
} else {
121121
package_json_path.clone()
122122
};
123-
PackageJson::parse(package_json_path.clone(), real_path, &package_json_string)
123+
PackageJson::parse(package_json_path.clone(), real_path, package_json_string)
124124
.map(|package_json| Some(Arc::new(package_json)))
125-
.map_err(|error| ResolveError::from_serde_json_error(package_json_path, &error))
125+
.map_err(|error| ResolveError::from_simd_json_error(package_json_path, &error))
126126
})
127127
.cloned();
128128
// https://github.com/webpack/enhanced-resolve/blob/58464fc7cb56673c9aa849e68e6300239601e615/lib/DescriptionFileUtils.js#L68-L82

src/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,17 @@ impl ResolveError {
135135
column: error.column(),
136136
})
137137
}
138+
139+
#[cold]
140+
#[must_use]
141+
pub fn from_simd_json_error(path: PathBuf, error: &simd_json::Error) -> Self {
142+
Self::Json(JSONError {
143+
path,
144+
message: error.to_string(),
145+
line: 0, // simd_json doesn't provide line/column info
146+
column: 0,
147+
})
148+
}
138149
}
139150

140151
/// Error for [ResolveError::Specifier]

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub use crate::{
7575
},
7676
package_json::{
7777
ImportsExportsArray, ImportsExportsEntry, ImportsExportsKind, ImportsExportsMap,
78-
PackageJson, PackageType,
78+
PackageJson, PackageType, SideEffects,
7979
},
8080
path::PathUtil,
8181
resolution::{ModuleType, Resolution},

0 commit comments

Comments
 (0)