Skip to content

Commit 8e36b80

Browse files
committed
fix: lol
1 parent 3481b03 commit 8e36b80

23 files changed

Lines changed: 1174 additions & 39 deletions

File tree

Cargo.lock

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

benchmarks/realistic-blog/src/routes/article.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ impl Route<ArticlesParams, PaginatedContentPage<ArticleContent>> for Articles {
1616
&self,
1717
ctx: &mut DynamicRouteContext,
1818
) -> Pages<ArticlesParams, PaginatedContentPage<ArticleContent>> {
19-
let articles = &ctx.content.get_source::<ArticleContent>("articles").entries;
19+
let articles = ctx
20+
.content
21+
.get_source::<ArticleContent>("articles")
22+
.entries();
2023

2124
let mut articles = articles.to_vec();
2225
articles.sort_by(|a, b| b.data(ctx).date.cmp(&a.data(ctx).date));

benchmarks/realistic-blog/src/routes/index.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use crate::{
55
content::ArticleContent,
66
layout::layout,
77
routes::{
8-
Article, Articles,
98
article::{ArticleParams, ArticlesParams},
9+
Article, Articles,
1010
},
1111
};
1212

@@ -18,9 +18,8 @@ impl Route for Index {
1818
let mut articles = ctx
1919
.content
2020
.get_source::<ArticleContent>("articles")
21-
.entries
22-
.iter()
23-
.collect::<Vec<_>>(); // Collect into a Vec to allow sorting
21+
.entries()
22+
.to_vec(); // Clone into a Vec to allow sorting
2423

2524
// Sort by date, newest first
2625
articles.sort_by(|a, b| b.data(ctx).date.cmp(&a.data(ctx).date));

crates/maudit/src/build.rs

Lines changed: 155 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::{
1919
options::PrefetchStrategy,
2020
state::{BuildState, RouteIdentifier},
2121
},
22-
content::ContentSources,
22+
content::{ContentSources, finish_tracking_content_files, start_tracking_content_files},
2323
is_dev,
2424
logging::print_title,
2525
route::{CachedRoute, DynamicRouteContext, FullRoute, InternalRoute, PageContext, PageParams},
@@ -142,6 +142,29 @@ fn track_route_source_file(
142142
build_state.track_source_file(source_path, route_id.clone());
143143
}
144144

145+
/// Helper to track content files accessed during page rendering.
146+
/// Only performs work when incremental builds are enabled and route_id is provided.
147+
/// This should be called after `finish_tracking_content_files()` to get the accessed files.
148+
fn track_route_content_files(
149+
build_state: &mut BuildState,
150+
route_id: Option<&RouteIdentifier>,
151+
accessed_files: Option<FxHashSet<PathBuf>>,
152+
) {
153+
// Skip tracking entirely when route_id is not provided (incremental disabled)
154+
let Some(route_id) = route_id else {
155+
return;
156+
};
157+
158+
// Skip if no files were tracked
159+
let Some(files) = accessed_files else {
160+
return;
161+
};
162+
163+
for file_path in files {
164+
build_state.track_content_file(file_path, route_id.clone());
165+
}
166+
}
167+
145168
pub fn execute_build(
146169
routes: &[&dyn FullRoute],
147170
content_sources: &mut ContentSources,
@@ -177,7 +200,7 @@ pub async fn build(
177200
BuildState::new()
178201
};
179202

180-
debug!(target: "build", "Loaded build state with {} asset mappings, {} source mappings", build_state.asset_to_routes.len(), build_state.source_to_routes.len());
203+
debug!(target: "build", "Loaded build state with {} asset mappings, {} source mappings, {} content file mappings", build_state.asset_to_routes.len(), build_state.source_to_routes.len(), build_state.content_file_to_routes.len());
181204
debug!(target: "build", "options.incremental: {}, changed_files.is_some(): {}", options.incremental, changed_files.is_some());
182205

183206
// Determine if this is an incremental build
@@ -191,7 +214,7 @@ pub async fn build(
191214
info!(target: "build", "Incremental build: {} files changed", changed.len());
192215
info!(target: "build", "Changed files: {:?}", changed);
193216

194-
info!(target: "build", "Build state has {} asset mappings, {} source mappings", build_state.asset_to_routes.len(), build_state.source_to_routes.len());
217+
info!(target: "build", "Build state has {} asset mappings, {} source mappings, {} content file mappings", build_state.asset_to_routes.len(), build_state.source_to_routes.len(), build_state.content_file_to_routes.len());
195218

196219
match build_state.get_affected_routes(changed) {
197220
Some(affected) => {
@@ -287,18 +310,86 @@ pub async fn build(
287310

288311
let content_sources_start = Instant::now();
289312
print_title("initializing content sources");
290-
content_sources.sources_mut().iter_mut().for_each(|source| {
291-
let source_start = Instant::now();
292-
source.init();
293313

294-
info!(target: "content", "{} initialized in {}", source.get_name(), format_elapsed_time(source_start.elapsed(), &FormatElapsedTimeOptions::default()));
295-
});
314+
// Determine which content sources need to be initialized
315+
// For incremental builds, only re-init sources whose files have changed
316+
let sources_to_init: Option<FxHashSet<String>> = if is_incremental {
317+
if let Some(changed) = changed_files {
318+
build_state.get_affected_content_sources(changed)
319+
} else {
320+
None // Full init
321+
}
322+
} else {
323+
None // Full init
324+
};
325+
326+
// Initialize content sources (all or selective)
327+
let initialized_sources: Vec<String> = match &sources_to_init {
328+
Some(source_names) if !source_names.is_empty() => {
329+
info!(target: "content", "Selectively initializing {} content source(s): {:?}", source_names.len(), source_names);
330+
331+
// Clear mappings for sources being re-initialized before init
332+
build_state.clear_content_mappings_for_sources(source_names);
333+
334+
// Initialize only the affected sources
335+
let mut initialized = Vec::new();
336+
for source in content_sources.sources_mut() {
337+
if source_names.contains(source.get_name()) {
338+
let source_start = Instant::now();
339+
source.init();
340+
info!(target: "content", "{} initialized in {}", source.get_name(), format_elapsed_time(source_start.elapsed(), &FormatElapsedTimeOptions::default()));
341+
initialized.push(source.get_name().to_string());
342+
} else {
343+
info!(target: "content", "{} (unchanged, skipped)", source.get_name());
344+
}
345+
}
346+
initialized
347+
}
348+
Some(_) => {
349+
// Empty set means no content files changed, skip all initialization
350+
info!(target: "content", "No content files changed, skipping content source initialization");
351+
Vec::new()
352+
}
353+
None => {
354+
// Full initialization (first build, unknown files, or non-incremental)
355+
info!(target: "content", "Initializing all content sources");
356+
357+
// Clear all content mappings for full init
358+
build_state.clear_content_file_mappings();
359+
build_state.content_file_to_source.clear();
360+
361+
let mut initialized = Vec::new();
362+
for source in content_sources.sources_mut() {
363+
let source_start = Instant::now();
364+
source.init();
365+
info!(target: "content", "{} initialized in {}", source.get_name(), format_elapsed_time(source_start.elapsed(), &FormatElapsedTimeOptions::default()));
366+
initialized.push(source.get_name().to_string());
367+
}
368+
initialized
369+
}
370+
};
371+
372+
// Track file->source mappings for all initialized sources
373+
for source in content_sources.sources() {
374+
if initialized_sources.contains(&source.get_name().to_string()) {
375+
let source_name = source.get_name().to_string();
376+
for file_path in source.get_entry_file_paths() {
377+
build_state.track_content_file_source(file_path, source_name.clone());
378+
}
379+
}
380+
}
296381

297382
info!(target: "content", "{}", format!("Content sources initialized in {}", format_elapsed_time(
298383
content_sources_start.elapsed(),
299384
&FormatElapsedTimeOptions::default(),
300385
)).bold());
301386

387+
// Clear content file->routes mappings for routes being rebuilt
388+
// (so they get fresh tracking during this build)
389+
if let Some(ref routes) = routes_to_rebuild {
390+
build_state.clear_content_file_mappings_for_routes(routes);
391+
}
392+
302393
print_title("generating pages");
303394
let pages_start = Instant::now();
304395

@@ -405,6 +496,11 @@ pub async fn build(
405496
let params = PageParams::default();
406497
let url = cached_route.url(&params);
407498

499+
// Start tracking content file access for incremental builds
500+
if options.incremental {
501+
start_tracking_content_files();
502+
}
503+
408504
let result = route.build(&mut PageContext::from_static_route(
409505
content_sources,
410506
&mut route_assets,
@@ -413,15 +509,23 @@ pub async fn build(
413509
None,
414510
))?;
415511

512+
// Finish tracking and record accessed content files
513+
let accessed_files = if options.incremental {
514+
finish_tracking_content_files()
515+
} else {
516+
None
517+
};
518+
416519
let file_path = cached_route.file_path(&params, &options.output_dir);
417520

418521
write_route_file(&result, &file_path)?;
419522

420523
info!(target: "pages", "{} -> {} {}", url, file_path.to_string_lossy().dimmed(), format_elapsed_time(route_start.elapsed(), &route_format_options));
421524

422-
// Track assets and source file for this route
525+
// Track assets, source file, and content files for this route
423526
track_route_assets(&mut build_state, route_id.as_ref(), &route_assets);
424527
track_route_source_file(&mut build_state, route_id.as_ref(), route.source_file());
528+
track_route_content_files(&mut build_state, route_id.as_ref(), accessed_files);
425529

426530
build_pages_images.extend(route_assets.images);
427531
build_pages_scripts.extend(route_assets.scripts);
@@ -482,6 +586,11 @@ pub async fn build(
482586
let url = cached_route.url(&page.0);
483587
let file_path = cached_route.file_path(&page.0, &options.output_dir);
484588

589+
// Start tracking content file access for incremental builds
590+
if options.incremental {
591+
start_tracking_content_files();
592+
}
593+
485594
let content = route.build(&mut PageContext::from_dynamic_route(
486595
&page,
487596
content_sources,
@@ -491,13 +600,21 @@ pub async fn build(
491600
None,
492601
))?;
493602

603+
// Finish tracking and record accessed content files
604+
let accessed_files = if options.incremental {
605+
finish_tracking_content_files()
606+
} else {
607+
None
608+
};
609+
494610
write_route_file(&content, &file_path)?;
495611

496612
info!(target: "pages", "├─ {} {}", file_path.to_string_lossy().dimmed(), format_elapsed_time(page_start.elapsed(), &route_format_options));
497613

498-
// Track assets and source file for this page
614+
// Track assets, source file, and content files for this page
499615
track_route_assets(&mut build_state, route_id.as_ref(), &route_assets);
500616
track_route_source_file(&mut build_state, route_id.as_ref(), route.source_file());
617+
track_route_content_files(&mut build_state, route_id.as_ref(), accessed_files);
501618

502619
build_metadata.add_page(
503620
base_path.clone(),
@@ -558,6 +675,11 @@ pub async fn build(
558675
&variant_id,
559676
)?;
560677

678+
// Start tracking content file access for incremental builds
679+
if options.incremental {
680+
start_tracking_content_files();
681+
}
682+
561683
let result = route.build(&mut PageContext::from_static_route(
562684
content_sources,
563685
&mut route_assets,
@@ -566,13 +688,21 @@ pub async fn build(
566688
Some(variant_id.clone()),
567689
))?;
568690

691+
// Finish tracking and record accessed content files
692+
let accessed_files = if options.incremental {
693+
finish_tracking_content_files()
694+
} else {
695+
None
696+
};
697+
569698
write_route_file(&result, &file_path)?;
570699

571700
info!(target: "pages", "├─ {} {}", file_path.to_string_lossy().dimmed(), format_elapsed_time(variant_start.elapsed(), &route_format_options));
572701

573-
// Track assets and source file for this variant
702+
// Track assets, source file, and content files for this variant
574703
track_route_assets(&mut build_state, route_id.as_ref(), &route_assets);
575704
track_route_source_file(&mut build_state, route_id.as_ref(), route.source_file());
705+
track_route_content_files(&mut build_state, route_id.as_ref(), accessed_files);
576706

577707
build_pages_images.extend(route_assets.images);
578708
build_pages_scripts.extend(route_assets.scripts);
@@ -640,6 +770,11 @@ pub async fn build(
640770
&variant_id,
641771
)?;
642772

773+
// Start tracking content file access for incremental builds
774+
if options.incremental {
775+
start_tracking_content_files();
776+
}
777+
643778
let content = route.build(&mut PageContext::from_dynamic_route(
644779
&page,
645780
content_sources,
@@ -649,13 +784,21 @@ pub async fn build(
649784
Some(variant_id.clone()),
650785
))?;
651786

787+
// Finish tracking and record accessed content files
788+
let accessed_files = if options.incremental {
789+
finish_tracking_content_files()
790+
} else {
791+
None
792+
};
793+
652794
write_route_file(&content, &file_path)?;
653795

654796
info!(target: "pages", "│ ├─ {} {}", file_path.to_string_lossy().dimmed(), format_elapsed_time(variant_page_start.elapsed(), &route_format_options));
655797

656-
// Track assets and source file for this variant page
798+
// Track assets, source file, and content files for this variant page
657799
track_route_assets(&mut build_state, route_id.as_ref(), &route_assets);
658800
track_route_source_file(&mut build_state, route_id.as_ref(), route.source_file());
801+
track_route_content_files(&mut build_state, route_id.as_ref(), accessed_files);
659802

660803
build_metadata.add_page(
661804
variant_path.clone(),

0 commit comments

Comments
 (0)