Skip to content

Commit f29c8da

Browse files
authored
feat: persistent cache for swc js minimizer plugin (#13706)
1 parent 9aa45b5 commit f29c8da

17 files changed

Lines changed: 493 additions & 24 deletions

File tree

crates/rspack_core/src/cache/mixed.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ impl Cache for MixedCache {
213213
self.persistent.after_chunk_asset(compilation).await;
214214
}
215215

216+
// PROCESS_ASSETS hooks
217+
async fn before_process_assets(&mut self, compilation: &mut Compilation) {
218+
self.persistent.before_process_assets(compilation).await;
219+
}
220+
221+
async fn after_process_assets(&mut self, compilation: &Compilation) {
222+
self.persistent.after_process_assets(compilation).await;
223+
}
224+
216225
// EMIT_ASSETS hooks
217226
async fn before_emit_assets(&mut self, compilation: &mut Compilation) {
218227
self.memory.before_emit_assets(compilation).await;

crates/rspack_core/src/cache/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ pub trait Cache: Debug + Send + Sync {
8484
async fn before_chunk_asset(&mut self, _compilation: &mut Compilation) {}
8585
async fn after_chunk_asset(&self, _compilation: &Compilation) {}
8686

87+
// PROCESS_ASSETS hooks
88+
async fn before_process_assets(&mut self, _compilation: &mut Compilation) {}
89+
async fn after_process_assets(&mut self, _compilation: &Compilation) {}
90+
8791
// EMIT_ASSETS hooks
8892
async fn before_emit_assets(&mut self, _compilation: &mut Compilation) {}
8993
async fn after_emit_assets(&self, _compilation: &Compilation) {}

crates/rspack_core/src/cache/persistent/mod.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use self::{
2222
build_dependencies::{BuildDeps, BuildDepsOptions},
2323
codec::CacheCodec,
2424
context::CacheContext,
25-
occasion::{MakeOccasion, MetaOccasion},
25+
occasion::{MakeOccasion, MetaOccasion, MinimizeOccasion},
2626
snapshot::{Snapshot, SnapshotOptions},
2727
storage::{StorageOptions, create_storage},
2828
};
@@ -53,6 +53,7 @@ pub struct PersistentCache {
5353
snapshot: Arc<Snapshot>,
5454
make_occasion: MakeOccasion,
5555
meta_occasion: MetaOccasion,
56+
minimize_occasion: MinimizeOccasion,
5657
}
5758

5859
impl PersistentCache {
@@ -100,7 +101,8 @@ impl PersistentCache {
100101
),
101102
snapshot,
102103
make_occasion: MakeOccasion::new(codec.clone()),
103-
meta_occasion: MetaOccasion::new(codec),
104+
meta_occasion: MetaOccasion::new(codec.clone()),
105+
minimize_occasion: MinimizeOccasion::new(codec),
104106
}
105107
}
106108

@@ -209,6 +211,25 @@ impl Cache for PersistentCache {
209211
);
210212
}
211213

214+
async fn before_process_assets(&mut self, compilation: &mut Compilation) {
215+
if compilation.is_rebuild {
216+
return;
217+
}
218+
219+
let artifact = self
220+
.ctx
221+
.load_occasion(&self.minimize_occasion)
222+
.await
223+
.unwrap_or_default();
224+
compilation.minimize_persistent_cache_artifact = Some(artifact);
225+
}
226+
227+
async fn after_process_assets(&mut self, compilation: &Compilation) {
228+
if let Some(artifact) = &compilation.minimize_persistent_cache_artifact {
229+
self.ctx.save_occasion(&self.minimize_occasion, artifact);
230+
}
231+
}
232+
212233
async fn close(&self) {
213234
self.ctx.flush_storage().await;
214235
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use std::sync::Arc;
2+
3+
use rayon::prelude::*;
4+
use rspack_cacheable::{cacheable, with::AsPreset};
5+
use rspack_error::Result;
6+
use rspack_sources::BoxSource;
7+
use rustc_hash::FxHashMap;
8+
9+
use super::{
10+
super::{codec::CacheCodec, storage::Storage},
11+
Occasion,
12+
};
13+
use crate::RayonConsumer;
14+
15+
pub const SCOPE: &str = "occasion_minimize";
16+
17+
#[cacheable]
18+
struct Entry {
19+
#[cacheable(with=AsPreset)]
20+
pub source: BoxSource,
21+
pub extracted_comments: Option<ExtractedCommentsEntry>,
22+
}
23+
24+
#[cacheable]
25+
struct ExtractedCommentsEntry {
26+
#[cacheable(with=AsPreset)]
27+
pub source: BoxSource,
28+
pub comments_file_name: String,
29+
}
30+
31+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32+
pub struct MinimizeCacheKey(u64);
33+
34+
impl MinimizeCacheKey {
35+
pub fn new(hash: u64) -> Self {
36+
Self(hash)
37+
}
38+
39+
fn to_bytes(self) -> Vec<u8> {
40+
self.0.to_ne_bytes().to_vec()
41+
}
42+
43+
fn from_bytes(bytes: &[u8]) -> Option<Self> {
44+
<[u8; 8]>::try_from(bytes)
45+
.ok()
46+
.map(|b| Self(u64::from_ne_bytes(b)))
47+
}
48+
}
49+
50+
#[derive(Debug, Default)]
51+
pub struct MinimizePersistentCacheArtifact {
52+
entries: FxHashMap<MinimizeCacheKey, CachedMinimizeEntry>,
53+
/// Keys of entries that were added during this build and need to be persisted.
54+
dirty_keys: Vec<MinimizeCacheKey>,
55+
}
56+
57+
#[derive(Debug, Clone)]
58+
pub struct CachedMinimizeEntry {
59+
pub source: BoxSource,
60+
pub extracted_comments: Option<CachedExtractedComments>,
61+
}
62+
63+
#[derive(Debug, Clone)]
64+
pub struct CachedExtractedComments {
65+
pub source: BoxSource,
66+
pub comments_file_name: String,
67+
}
68+
69+
impl MinimizePersistentCacheArtifact {
70+
pub fn get(&self, key: MinimizeCacheKey) -> Option<&CachedMinimizeEntry> {
71+
self.entries.get(&key)
72+
}
73+
74+
pub fn insert(&mut self, key: MinimizeCacheKey, entry: CachedMinimizeEntry) {
75+
self.dirty_keys.push(key);
76+
self.entries.insert(key, entry);
77+
}
78+
}
79+
80+
#[derive(Debug)]
81+
pub struct MinimizeOccasion {
82+
codec: Arc<CacheCodec>,
83+
}
84+
85+
impl MinimizeOccasion {
86+
pub fn new(codec: Arc<CacheCodec>) -> Self {
87+
Self { codec }
88+
}
89+
}
90+
91+
#[async_trait::async_trait]
92+
impl Occasion for MinimizeOccasion {
93+
type Artifact = MinimizePersistentCacheArtifact;
94+
95+
#[tracing::instrument(name = "Cache::Occasion::Minimize::reset", skip_all)]
96+
fn reset(&self, storage: &mut dyn Storage) {
97+
storage.reset(SCOPE);
98+
}
99+
100+
#[tracing::instrument(name = "Cache::Occasion::Minimize::save", skip_all)]
101+
fn save(&self, storage: &mut dyn Storage, artifact: &MinimizePersistentCacheArtifact) {
102+
// Only persist entries that were added during this build.
103+
artifact
104+
.dirty_keys
105+
.par_iter()
106+
.filter_map(|key| {
107+
let entry = artifact.entries.get(key)?;
108+
let storage_entry = Entry {
109+
source: entry.source.clone(),
110+
extracted_comments: entry
111+
.extracted_comments
112+
.as_ref()
113+
.map(|ec| ExtractedCommentsEntry {
114+
source: ec.source.clone(),
115+
comments_file_name: ec.comments_file_name.clone(),
116+
}),
117+
};
118+
match self.codec.encode(&storage_entry) {
119+
Ok(bytes) => Some((key.to_bytes(), bytes)),
120+
Err(err) => {
121+
tracing::warn!("minimize persistent cache encode failed: {:?}", err);
122+
None
123+
}
124+
}
125+
})
126+
.consume(|(key, bytes)| {
127+
storage.set(SCOPE, key, bytes);
128+
});
129+
130+
tracing::debug!(
131+
"saved {} minimize persistent cache entries",
132+
artifact.dirty_keys.len()
133+
);
134+
}
135+
136+
#[tracing::instrument(name = "Cache::Occasion::Minimize::recovery", skip_all)]
137+
async fn recovery(&self, storage: &dyn Storage) -> Result<MinimizePersistentCacheArtifact> {
138+
let items = storage.load(SCOPE).await?;
139+
let mut entries = FxHashMap::default();
140+
entries.reserve(items.len());
141+
142+
for (key, value) in items {
143+
let Some(key) = MinimizeCacheKey::from_bytes(&key) else {
144+
tracing::warn!("minimize persistent cache key has invalid length");
145+
continue;
146+
};
147+
match self.codec.decode::<Entry>(&value) {
148+
Ok(entry) => {
149+
entries.insert(
150+
key,
151+
CachedMinimizeEntry {
152+
source: entry.source,
153+
extracted_comments: entry.extracted_comments.map(|ec| CachedExtractedComments {
154+
source: ec.source,
155+
comments_file_name: ec.comments_file_name,
156+
}),
157+
},
158+
);
159+
}
160+
Err(err) => {
161+
tracing::warn!("minimize persistent cache decode failed: {:?}", err);
162+
}
163+
}
164+
}
165+
166+
tracing::debug!(
167+
"recovered {} minimize persistent cache entries",
168+
entries.len()
169+
);
170+
Ok(MinimizePersistentCacheArtifact {
171+
entries,
172+
dirty_keys: Vec::new(),
173+
})
174+
}
175+
}

crates/rspack_core/src/cache/persistent/occasion/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
pub mod make;
22
pub mod meta;
3+
pub mod minimize;
34

45
pub use make::MakeOccasion;
56
pub use meta::MetaOccasion;
7+
pub use minimize::{
8+
CachedExtractedComments, CachedMinimizeEntry, MinimizeOccasion, MinimizePersistentCacheArtifact,
9+
};
610
use rspack_error::Result;
711

812
use super::storage::Storage;

crates/rspack_core/src/compilation/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ use crate::{
8585
RuntimeMode, RuntimeModule, RuntimeSpec, RuntimeSpecMap, RuntimeTemplate, SharedPluginDriver,
8686
SideEffectsOptimizeArtifact, SideEffectsStateArtifact, SourceType, Stats, StatsContext,
8787
StealCell, ValueCacheVersions,
88+
cache::persistent::occasion::minimize::MinimizePersistentCacheArtifact,
8889
compilation::build_module_graph::{
8990
BuildModuleGraphArtifact, ModuleExecutor, UpdateParam, update_module_graph,
9091
},
@@ -269,6 +270,8 @@ pub struct Compilation {
269270
StealCell<ProcessRuntimeRequirementsCacheArtifact>,
270271
pub imported_by_defer_modules_artifact: StealCell<ImportedByDeferModulesArtifact>,
271272

273+
pub minimize_persistent_cache_artifact: Option<MinimizePersistentCacheArtifact>,
274+
272275
pub code_generated_modules: IdentifierSet,
273276
pub build_time_executed_modules: IdentifierSet,
274277
pub build_chunk_graph_artifact: BuildChunkGraphArtifact,
@@ -398,6 +401,7 @@ impl Compilation {
398401
process_runtime_requirements_cache_artifact: StealCell::new(
399402
ProcessRuntimeRequirementsCacheArtifact::new(&options),
400403
),
404+
minimize_persistent_cache_artifact: None,
401405
build_time_executed_modules: Default::default(),
402406
incremental,
403407
build_chunk_graph_artifact: Default::default(),

crates/rspack_core/src/compilation/process_assets/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use async_trait::async_trait;
22

33
use super::*;
4-
use crate::compilation::pass::PassExt;
4+
use crate::{cache::Cache, compilation::pass::PassExt};
55

66
pub struct ProcessAssetsPass;
77

@@ -11,10 +11,18 @@ impl PassExt for ProcessAssetsPass {
1111
"process assets"
1212
}
1313

14+
async fn before_pass(&self, compilation: &mut Compilation, cache: &mut dyn Cache) {
15+
cache.before_process_assets(compilation).await;
16+
}
17+
1418
async fn run_pass(&self, compilation: &mut Compilation) -> Result<()> {
1519
let plugin_driver = compilation.plugin_driver.clone();
1620
process_assets(compilation, plugin_driver).await
1721
}
22+
23+
async fn after_pass(&self, compilation: &mut Compilation, cache: &mut dyn Cache) {
24+
cache.after_process_assets(compilation).await;
25+
}
1826
}
1927

2028
#[instrument("Compilation:process_assets",target=TRACING_BENCH_TARGET, skip_all)]

0 commit comments

Comments
 (0)