diff --git a/Cargo.lock b/Cargo.lock index 55044502..e3f53151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1320,6 +1320,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "minify-html" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b917ef1eb405ca3b1a489608f36392f0718ff4740bbc6f7d89edd4ec4c869fb7" +dependencies = [ + "aho-corasick", + "lazy_static", + "memchr", +] + [[package]] name = "miniz_oxide" version = "0.3.7" @@ -2177,6 +2188,7 @@ dependencies = [ "lazy_static", "library", "link_checker", + "minify-html", "rayon", "sass-rs", "search", diff --git a/components/config/src/config/mod.rs b/components/config/src/config/mod.rs index 0e715354..15f5f753 100644 --- a/components/config/src/config/mod.rs +++ b/components/config/src/config/mod.rs @@ -74,6 +74,8 @@ pub struct Config { /// Whether to compile the `sass` directory and output the css files into the static folder pub compile_sass: bool, + /// Whether to minify the html output + pub minify_html: bool, /// Whether to build the search index for the content pub build_search_index: bool, /// A list of file glob patterns to ignore when processing the content folder. Defaults to none. @@ -320,6 +322,7 @@ impl Default for Config { hard_link_static: false, taxonomies: Vec::new(), compile_sass: false, + minify_html: false, mode: Mode::Build, build_search_index: false, ignored_content: Vec::new(), diff --git a/components/site/Cargo.toml b/components/site/Cargo.toml index 12373c84..60008852 100644 --- a/components/site/Cargo.toml +++ b/components/site/Cargo.toml @@ -8,6 +8,7 @@ include = ["src/**/*"] [dependencies] tera = "1" glob = "0.3" +minify-html = "0.3.6" rayon = "1" serde = "1" serde_derive = "1" diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs index cd8b6bff..61961b79 100644 --- a/components/site/src/lib.rs +++ b/components/site/src/lib.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex, RwLock}; use glob::glob; use lazy_static::lazy_static; +use minify_html::{truncate, Cfg}; use rayon::prelude::*; use tera::{Context, Tera}; @@ -445,6 +446,21 @@ impl Site { html } + /// Minifies html content + fn minify(&self, html: String) -> Result { + let cfg = &Cfg { minify_js: false }; + let mut input_bytes = html.as_bytes().to_vec(); + match truncate(&mut input_bytes, cfg) { + Ok(_len) => match std::str::from_utf8(&mut input_bytes) { + Ok(result) => Ok(result.to_string()), + Err(err) => bail!("Failed to convert bytes to string : {}", err), + }, + Err(minify_error) => { + bail!("Failed to truncate html at character {}:", minify_error.position); + } + } + } + /// Copy the main `static` folder and the theme `static` folder if a theme is used pub fn copy_static_directories(&self) -> Result<()> { // The user files will overwrite the theme files @@ -511,10 +527,19 @@ impl Site { create_directory(¤t_path)?; } + let final_content = if !filename.ends_with("html") || !self.config.minify_html { + content + } else { + match self.minify(content) { + Ok(minified_content) => minified_content, + Err(error) => bail!(error), + } + }; + match self.build_mode { BuildMode::Disk => { let end_path = current_path.join(filename); - create_file(&end_path, &content)?; + create_file(&end_path, &final_content)?; } BuildMode::Memory => { let path = if filename != "index.html" { @@ -527,7 +552,7 @@ impl Site { } .trim_end_matches('/') .to_owned(); - &SITE_CONTENT.write().unwrap().insert(path, content); + &SITE_CONTENT.write().unwrap().insert(path, final_content); } } @@ -543,8 +568,12 @@ impl Site { let output = page.render_html(&self.tera, &self.config, &self.library.read().unwrap())?; let content = self.inject_livereload(output); let components: Vec<&str> = page.path.split('/').collect(); - let current_path = - self.write_content(&components, "index.html", content, !page.assets.is_empty())?; + let current_path = self.write_content( + &components, + "index.html", + content, + !page.assets.is_empty(), + )?; // Copy any asset we found previously into the same directory as the index.html for asset in &page.assets { diff --git a/components/site/tests/site.rs b/components/site/tests/site.rs index bf9f3358..c7419d8e 100644 --- a/components/site/tests/site.rs +++ b/components/site/tests/site.rs @@ -712,6 +712,18 @@ fn can_build_site_custom_builtins_from_theme() { assert!(file_contains!(public, "404.html", "Oops")); } +#[test] +fn can_build_site_with_html_minified() { + let (_, _tmp_dir, public) = build_site_with_setup("test_site", |mut site| { + site.config.minify_html = true; + (site, true) + }); + + assert!(&public.exists()); + assert!(file_exists!(public, "index.html")); + assert!(file_contains!(public, "index.html", "")); +} + #[test] fn can_ignore_markdown_content() { let (_, _tmp_dir, public) = build_site("test_site"); diff --git a/docs/content/documentation/getting-started/configuration.md b/docs/content/documentation/getting-started/configuration.md index e029e1cb..e1096074 100644 --- a/docs/content/documentation/getting-started/configuration.md +++ b/docs/content/documentation/getting-started/configuration.md @@ -85,6 +85,9 @@ languages = [] # When set to "true", the Sass files in the `sass` directory are compiled. compile_sass = false +# When set to "true", the HTML output files will be minified +minify_html = false + # A list of glob patterns specifying asset files to ignore when the content # directory is processed. Defaults to none, which means that all asset files are # copied over to the `public` directory.