Skip to content

Commit 4a1bc3b

Browse files
committed
Change where minification happens
* `write_content` isn't smart enough to do what we need. * this is a cleaner architecture. * Now pages with txt templates are not minified. * Add test for text template whitespace preservation with minification * Test verifies that non-HTML templates (e.g., .txt) preserve whitespace * Created text_content.txt template with multiple spaces, tabs, and newlines * Created test page using the text template * Test passes with the minification fix (ed36c1e6) and fails without it * Updated page count assertions due to new test page
1 parent 187ef74 commit 4a1bc3b

File tree

5 files changed

+138
-45
lines changed

5 files changed

+138
-45
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Preserve exact line endings in test templates (no normalization)
2+
test_site/templates/*.txt -text

components/site/src/lib.rs

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,11 @@ impl Site {
653653
clean_site_output_folder(&self.output_path, self.config.preserve_dotfiles_in_output)
654654
}
655655

656+
/// Minify HTML content if minification is enabled.
657+
fn maybe_minify(&self, content: String) -> Result<String> {
658+
if self.config.minify_html { minify::html(content) } else { Ok(content) }
659+
}
660+
656661
/// Handles whether to write to disk or to memory
657662
pub fn write_content(
658663
&self,
@@ -668,19 +673,11 @@ impl Site {
668673
site_path.push(component);
669674
}
670675

671-
let final_content = if !filename.ends_with("html") || !self.config.minify_html {
672-
content
673-
} else {
674-
match minify::html(content) {
675-
Ok(minified_content) => minified_content,
676-
Err(error) => bail!(error),
677-
}
678-
};
679-
676+
// Content is assumed to already be finalized (minified if needed)
680677
match self.build_mode {
681678
BuildMode::Disk | BuildMode::Both => {
682679
let end_path = current_path.join(filename);
683-
create_file(&end_path, &final_content)?;
680+
create_file(&end_path, &content)?;
684681
}
685682
_ => (),
686683
}
@@ -689,7 +686,7 @@ impl Site {
689686
let site_path =
690687
if filename != "index.html" { site_path.join(filename) } else { site_path };
691688

692-
SITE_CONTENT.write().unwrap().insert(site_path, final_content);
689+
SITE_CONTENT.write().unwrap().insert(site_path, content);
693690
}
694691
_ => (),
695692
}
@@ -720,8 +717,19 @@ impl Site {
720717

721718
let output = page.render_html(&self.tera, &self.config, &self.library.read().unwrap())?;
722719
let content = self.inject_livereload(output);
720+
721+
// Determine if we should minify based on template extension
722+
let template = page.meta.template.as_deref().unwrap_or("page.html");
723+
let final_content = if template.to_lowercase().ends_with(".html")
724+
|| template.to_lowercase().ends_with(".htm")
725+
{
726+
self.maybe_minify(content)?
727+
} else {
728+
content
729+
};
730+
723731
let components: Vec<&str> = page.path.split('/').collect();
724-
let current_path = self.write_content(&components, "index.html", content)?;
732+
let current_path = self.write_content(&components, "index.html", final_content)?;
725733

726734
// Copy any asset we found previously into the same directory as the index.html
727735
self.copy_assets(page.file.path.parent().unwrap(), &page.assets, &current_path)?;
@@ -889,7 +897,11 @@ impl Site {
889897
None => "index.html",
890898
};
891899
let content = render_redirect_template(permalink, &self.tera)?;
892-
self.write_content(&split, page_name, content)?;
900+
901+
// Aliases are always HTML redirects - minify if enabled
902+
let final_content = self.maybe_minify(content)?;
903+
904+
self.write_content(&split, page_name, final_content)?;
893905
Ok(())
894906
}
895907

@@ -917,7 +929,11 @@ impl Site {
917929
context.insert("lang", &self.config.default_language);
918930
let output = render_template("404.html", &self.tera, context, &self.config.theme)?;
919931
let content = self.inject_livereload(output);
920-
self.write_content(&[], "404.html", content)?;
932+
933+
// 404.html is always HTML - minify if enabled
934+
let final_content = self.maybe_minify(content)?;
935+
936+
self.write_content(&[], "404.html", final_content)?;
921937
Ok(())
922938
}
923939

@@ -926,6 +942,8 @@ impl Site {
926942
let mut context = Context::new();
927943
context.insert("config", &self.config.serialize(&self.config.default_language));
928944
let content = render_template("robots.txt", &self.tera, context, &self.config.theme)?;
945+
946+
// robots.txt is plain text - never minify
929947
self.write_content(&[], "robots.txt", content)?;
930948
Ok(())
931949
}
@@ -961,7 +979,11 @@ impl Site {
961979
let list_output =
962980
taxonomy.render_all_terms(&self.tera, &self.config, &self.library.read().unwrap())?;
963981
let content = self.inject_livereload(list_output);
964-
self.write_content(&components, "index.html", content)?;
982+
983+
// Taxonomy list pages are always HTML - minify if enabled
984+
let final_content = self.maybe_minify(content)?;
985+
986+
self.write_content(&components, "index.html", final_content)?;
965987

966988
let library = self.library.read().unwrap();
967989
taxonomy
@@ -986,7 +1008,11 @@ impl Site {
9861008
let single_output =
9871009
taxonomy.render_term(item, &self.tera, &self.config, &library)?;
9881010
let content = self.inject_livereload(single_output);
989-
self.write_content(&comp, "index.html", content)?;
1011+
1012+
// Taxonomy term pages are always HTML - minify if enabled
1013+
let final_content = self.maybe_minify(content)?;
1014+
1015+
self.write_content(&comp, "index.html", final_content)?;
9901016
}
9911017

9921018
if taxonomy.kind.feed {
@@ -1044,6 +1070,8 @@ impl Site {
10441070
let mut context = Context::new();
10451071
context.insert("entries", &all_sitemap_entries);
10461072
let sitemap = render_template("sitemap.xml", &self.tera, context, &self.config.theme)?;
1073+
1074+
// Sitemaps are XML - never minify
10471075
self.write_content(&[], "sitemap.xml", sitemap)?;
10481076
return Ok(());
10491077
}
@@ -1056,6 +1084,8 @@ impl Site {
10561084
let mut context = Context::new();
10571085
context.insert("entries", &chunk);
10581086
let sitemap = render_template("sitemap.xml", &self.tera, context, &self.config.theme)?;
1087+
1088+
// Sitemaps are XML - never minify
10591089
let file_name = format!("sitemap{}.xml", i + 1);
10601090
self.write_content(&[], &file_name, sitemap)?;
10611091
let mut sitemap_url = self.config.make_permalink(&file_name);
@@ -1072,6 +1102,8 @@ impl Site {
10721102
main_context,
10731103
&self.config.theme,
10741104
)?;
1105+
1106+
// Sitemap index is XML - never minify
10751107
self.write_content(&[], "sitemap.xml", sitemap)?;
10761108

10771109
Ok(())
@@ -1096,6 +1128,7 @@ impl Site {
10961128
for (feed, feed_filename) in
10971129
feeds.into_iter().zip(self.config.languages[lang].feed_filenames.iter())
10981130
{
1131+
// Feeds are XML - never minify
10991132
if let Some(base) = base_path {
11001133
let mut components = Vec::new();
11011134
for component in base.components() {
@@ -1164,11 +1197,12 @@ impl Site {
11641197
} else {
11651198
Cow::Owned(self.config.make_permalink(redirect_to))
11661199
};
1167-
self.write_content(
1168-
&components,
1169-
"index.html",
1170-
render_redirect_template(&permalink, &self.tera)?,
1171-
)?;
1200+
let content = render_redirect_template(&permalink, &self.tera)?;
1201+
1202+
// Section redirects are always HTML - minify if enabled
1203+
let final_content = self.maybe_minify(content)?;
1204+
1205+
self.write_content(&components, "index.html", final_content)?;
11721206

11731207
return Ok(());
11741208
}
@@ -1182,7 +1216,11 @@ impl Site {
11821216
let output =
11831217
section.render_html(&self.tera, &self.config, &self.library.read().unwrap())?;
11841218
let content = self.inject_livereload(output);
1185-
self.write_content(&components, "index.html", content)?;
1219+
1220+
// Section pages are always HTML - minify if enabled
1221+
let final_content = self.maybe_minify(content)?;
1222+
1223+
self.write_content(&components, "index.html", final_content)?;
11861224
}
11871225

11881226
Ok(())
@@ -1233,15 +1271,20 @@ impl Site {
12331271
)?;
12341272
let content = self.inject_livereload(output);
12351273

1274+
// Pagination pages are always HTML - minify if enabled
1275+
let final_content = self.maybe_minify(content)?;
1276+
12361277
if pager.index > 1 {
1237-
self.write_content(&pager_components, "index.html", content)?;
1278+
self.write_content(&pager_components, "index.html", final_content)?;
12381279
} else {
1239-
self.write_content(&index_components, "index.html", content)?;
1240-
self.write_content(
1241-
&pager_components,
1242-
"index.html",
1243-
render_redirect_template(&paginator.permalink, &self.tera)?,
1244-
)?;
1280+
self.write_content(&index_components, "index.html", final_content)?;
1281+
1282+
// The redirect for page 1 is also HTML
1283+
let redirect_content =
1284+
render_redirect_template(&paginator.permalink, &self.tera)?;
1285+
let final_redirect = self.maybe_minify(redirect_content)?;
1286+
1287+
self.write_content(&pager_components, "index.html", final_redirect)?;
12451288
}
12461289

12471290
Ok(())

components/site/tests/site.rs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn can_parse_site() {
2222
let library = site.library.read().unwrap();
2323

2424
// Correct number of pages (sections do not count as pages, draft are ignored)
25-
assert_eq!(library.pages.len(), 36);
25+
assert_eq!(library.pages.len(), 37);
2626
let posts_path = path.join("content").join("posts");
2727

2828
// Make sure the page with a url doesn't have any sections
@@ -45,7 +45,7 @@ fn can_parse_site() {
4545

4646
let posts_section = library.sections.get(&posts_path.join("_index.md")).unwrap();
4747
assert_eq!(posts_section.subsections.len(), 2);
48-
assert_eq!(posts_section.pages.len(), 10); // 11 with 1 draft == 10
48+
assert_eq!(posts_section.pages.len(), 11); // 12 with 1 draft == 11
4949
assert_eq!(posts_section.ancestors, vec![index_section.file.relative.clone()]);
5050

5151
// Make sure we remove all the pwd + content from the sections
@@ -445,7 +445,7 @@ fn can_build_site_with_pagination_for_section() {
445445
"posts/page/1/index.html",
446446
"http-equiv=\"refresh\" content=\"0; url=https://replace-this-with-your-url.com/posts/\""
447447
));
448-
assert!(file_contains!(public, "posts/index.html", "Num pagers: 5"));
448+
assert!(file_contains!(public, "posts/index.html", "Num pagers: 6"));
449449
assert!(file_contains!(public, "posts/index.html", "Page size: 2"));
450450
assert!(file_contains!(public, "posts/index.html", "Current index: 1"));
451451
assert!(!file_contains!(public, "posts/index.html", "has_prev"));
@@ -458,12 +458,12 @@ fn can_build_site_with_pagination_for_section() {
458458
assert!(file_contains!(
459459
public,
460460
"posts/index.html",
461-
"Last: https://replace-this-with-your-url.com/posts/page/5/"
461+
"Last: https://replace-this-with-your-url.com/posts/page/6/"
462462
));
463463
assert!(!file_contains!(public, "posts/index.html", "has_prev"));
464464

465465
assert!(file_exists!(public, "posts/page/2/index.html"));
466-
assert!(file_contains!(public, "posts/page/2/index.html", "Num pagers: 5"));
466+
assert!(file_contains!(public, "posts/page/2/index.html", "Num pagers: 6"));
467467
assert!(file_contains!(public, "posts/page/2/index.html", "Page size: 2"));
468468
assert!(file_contains!(public, "posts/page/2/index.html", "Current index: 2"));
469469
assert!(file_contains!(public, "posts/page/2/index.html", "has_prev"));
@@ -476,11 +476,11 @@ fn can_build_site_with_pagination_for_section() {
476476
assert!(file_contains!(
477477
public,
478478
"posts/page/2/index.html",
479-
"Last: https://replace-this-with-your-url.com/posts/page/5/"
479+
"Last: https://replace-this-with-your-url.com/posts/page/6/"
480480
));
481481

482482
assert!(file_exists!(public, "posts/page/3/index.html"));
483-
assert!(file_contains!(public, "posts/page/3/index.html", "Num pagers: 5"));
483+
assert!(file_contains!(public, "posts/page/3/index.html", "Num pagers: 6"));
484484
assert!(file_contains!(public, "posts/page/3/index.html", "Page size: 2"));
485485
assert!(file_contains!(public, "posts/page/3/index.html", "Current index: 3"));
486486
assert!(file_contains!(public, "posts/page/3/index.html", "has_prev"));
@@ -493,11 +493,11 @@ fn can_build_site_with_pagination_for_section() {
493493
assert!(file_contains!(
494494
public,
495495
"posts/page/3/index.html",
496-
"Last: https://replace-this-with-your-url.com/posts/page/5/"
496+
"Last: https://replace-this-with-your-url.com/posts/page/6/"
497497
));
498498

499499
assert!(file_exists!(public, "posts/page/4/index.html"));
500-
assert!(file_contains!(public, "posts/page/4/index.html", "Num pagers: 5"));
500+
assert!(file_contains!(public, "posts/page/4/index.html", "Num pagers: 6"));
501501
assert!(file_contains!(public, "posts/page/4/index.html", "Page size: 2"));
502502
assert!(file_contains!(public, "posts/page/4/index.html", "Current index: 4"));
503503
assert!(file_contains!(public, "posts/page/4/index.html", "has_prev"));
@@ -510,7 +510,7 @@ fn can_build_site_with_pagination_for_section() {
510510
assert!(file_contains!(
511511
public,
512512
"posts/page/4/index.html",
513-
"Last: https://replace-this-with-your-url.com/posts/page/5/"
513+
"Last: https://replace-this-with-your-url.com/posts/page/6/"
514514
));
515515

516516
// sitemap contains the pager pages
@@ -932,16 +932,44 @@ fn can_find_site_and_page_authors() {
932932
let posts_path = path.join("content").join("posts");
933933
let posts_section = library.sections.get(&posts_path.join("_index.md")).unwrap();
934934

935-
let p1 = &library.pages[&posts_section.pages[0]];
936-
let p2 = &library.pages[&posts_section.pages[1]];
935+
// Find the page with an author (transparent-page.md)
936+
let transparent_page =
937+
library.pages.get(&posts_path.join("2018").join("transparent-page.md")).unwrap();
938+
assert_eq!(1, transparent_page.meta.authors.len());
939+
assert_eq!("[email protected] (Page Author)", transparent_page.meta.authors.first().unwrap());
937940

938-
// Only the first page has had an author added.
939-
assert_eq!(1, p1.meta.authors.len());
940-
assert_eq!("[email protected] (Page Author)", p1.meta.authors.first().unwrap());
941-
assert_eq!(0, p2.meta.authors.len());
941+
// Find a page without an author (simple.md)
942+
let simple_page = library.pages.get(&posts_path.join("simple.md")).unwrap();
943+
assert_eq!(0, simple_page.meta.authors.len());
942944
}
943945

944946
// Follows test_site/themes/sample/templates/current_path.html
945947
fn current_path(path: &str) -> String {
946948
format!("[current_path]({})", path)
947949
}
950+
951+
#[test]
952+
fn can_preserve_whitespace_in_text_templates_with_minification() {
953+
let (_, _tmp_dir, public) = build_site_with_setup("test_site", |mut site| {
954+
site.config.minify_html = true;
955+
(site, true)
956+
});
957+
958+
assert!(&public.exists());
959+
960+
// The page with text template should exist
961+
assert!(file_exists!(public, "posts/text-template-test/index.html"));
962+
963+
// Verify that multiple spaces are preserved (not collapsed by minification)
964+
assert!(file_contains!(public, "posts/text-template-test/index.html", "multiple spaces"));
965+
966+
// Verify that tabs are preserved
967+
assert!(file_contains!(public, "posts/text-template-test/index.html", "tabs\tbetween"));
968+
969+
// Verify that multiple newlines are preserved
970+
assert!(file_contains!(
971+
public,
972+
"posts/text-template-test/index.html",
973+
"Line 1\nLine 2\nLine 3"
974+
));
975+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
+++
2+
title = "Text Template Test"
3+
description = "Testing text template with minification"
4+
date = 2024-01-15
5+
template = "text_content.txt"
6+
+++
7+
8+
This page uses a text template and should preserve whitespace even when minify_html is enabled.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Title: {{ page.title }}
2+
3+
Date: {{ page.date }}
4+
5+
Content with multiple spaces
6+
And tabs between words
7+
8+
Line 1
9+
Line 2
10+
Line 3
11+
12+
End of text file

0 commit comments

Comments
 (0)