diff --git a/Cargo.lock b/Cargo.lock index ac6e8d73ad..3366878829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "anymap2" diff --git a/packages/cli-opt/src/file.rs b/packages/cli-opt/src/file.rs index 8ef22f3c7d..024ea6b472 100644 --- a/packages/cli-opt/src/file.rs +++ b/packages/cli-opt/src/file.rs @@ -1,5 +1,7 @@ use anyhow::Context; -use manganis_core::{AssetOptions, CssAssetOptions, ImageAssetOptions, JsAssetOptions}; +use manganis_core::{ + AssetOptions, CssAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptions, +}; use std::path::Path; use crate::css::process_scss; @@ -15,17 +17,10 @@ pub fn process_file_to( source: &Path, output_path: &Path, ) -> anyhow::Result<()> { - // If the file already exists, then we must have a file with the same hash - // already. The hash has the file contents and options, so if we find a file - // with the same hash, we probably already created this file in the past - if output_path.exists() { + if asset_exists(output_path)? { return Ok(()); } - if let Some(parent) = output_path.parent() { - if !parent.exists() { - std::fs::create_dir_all(parent)?; - } - } + match options { AssetOptions::Unknown => match source.extension().map(|e| e.to_string_lossy()).as_deref() { Some("css") => { @@ -44,20 +39,10 @@ pub fn process_file_to( process_image(&ImageAssetOptions::new(), source, output_path)?; } Some(_) | None => { - if source.is_dir() { - process_folder(source, output_path)?; - } else { - let source_file = std::fs::File::open(source)?; - let mut reader = std::io::BufReader::new(source_file); - let output_file = std::fs::File::create(output_path)?; - let mut writer = std::io::BufWriter::new(output_file); - std::io::copy(&mut reader, &mut writer).with_context(|| { - format!( - "Failed to write file to output location: {}", - output_path.display() - ) - })?; - } + match source.is_dir() { + true => process_folder(&FolderAssetOptions::new(), source, output_path)?, + false => copy_file_to(source, output_path)?, + }; } }, AssetOptions::Css(options) => { @@ -69,8 +54,11 @@ pub fn process_file_to( AssetOptions::Image(options) => { process_image(options, source, output_path)?; } - AssetOptions::Folder(_) => { - process_folder(source, output_path)?; + AssetOptions::Folder(options) => { + process_folder(options, source, output_path)?; + } + AssetOptions::PreservedFolder(options) => { + process_folder(options, source, output_path)?; } _ => { tracing::warn!("Unknown asset options: {:?}", options); @@ -79,3 +67,44 @@ pub fn process_file_to( Ok(()) } + +/// Copies an asset to it's destination without any processing. +pub fn copy_file_to(source: &Path, output_path: &Path) -> anyhow::Result<()> { + if asset_exists(output_path)? { + return Ok(()); + } + + let source_file = std::fs::File::open(source)?; + let mut reader = std::io::BufReader::new(source_file); + let output_file = std::fs::File::create(output_path)?; + let mut writer = std::io::BufWriter::new(output_file); + std::io::copy(&mut reader, &mut writer).with_context(|| { + format!( + "Failed to write file to output location: {}", + output_path.display() + ) + })?; + + Ok(()) +} + +/// Check the asset output path to ensure that: +/// 1. The asset doesn't already exist. +/// 2. That the parent path exists or is created. +/// +/// Returns true if asset processing should be skipped. +fn asset_exists(output_path: &Path) -> anyhow::Result { + // If the file already exists, then we must have a file with the same hash + // already. The hash has the file contents and options, so if we find a file + // with the same hash, we probably already created this file in the past + if output_path.exists() { + return Ok(true); + } + if let Some(parent) = output_path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; + } + } + + Ok(false) +} diff --git a/packages/cli-opt/src/folder.rs b/packages/cli-opt/src/folder.rs index 699f6d64d8..29e5113a63 100644 --- a/packages/cli-opt/src/folder.rs +++ b/packages/cli-opt/src/folder.rs @@ -1,11 +1,15 @@ -use std::path::Path; - -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; - use super::file::process_file_to; +use crate::file::copy_file_to; +use manganis_core::FolderOptions; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use std::path::Path; /// Process a folder, optimizing and copying all assets into the output folder -pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> { +pub fn process_folder( + options: &impl FolderOptions, + source: &Path, + output_folder: &Path, +) -> anyhow::Result<()> { // Create the folder std::fs::create_dir_all(output_folder)?; @@ -21,9 +25,12 @@ pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> let metadata = file.metadata()?; let output_path = output_folder.join(file.strip_prefix(source)?); if metadata.is_dir() { - process_folder(&file, &output_path) + process_folder(options, &file, &output_path) } else { - process_file_minimal(&file, &output_path) + match options.optimize_files() { + true => process_file_minimal(&file, &output_path), + false => copy_file_to(&file, &output_path), + } } })?; diff --git a/packages/manganis/manganis-core/src/folder.rs b/packages/manganis/manganis-core/src/folder.rs index 06127cf093..d1cc52960a 100644 --- a/packages/manganis/manganis-core/src/folder.rs +++ b/packages/manganis/manganis-core/src/folder.rs @@ -1,6 +1,10 @@ +use crate::AssetOptions; use const_serialize::SerializeConst; -use crate::AssetOptions; +// TODO, 0.7: +// - Mark `FolderAssetOptions` as non-exhaustive. +// - Remove `FolderOptions` trait. +// - Migrate `PreservedFolderAssetOptions` to `optimize_files` field on `FolderAssetOptions` /// The builder for [`FolderAsset`] #[derive( @@ -28,8 +32,66 @@ impl FolderAssetOptions { Self {} } + /// Convert this folder asset into a preserved folder asset. + /// + /// This is planned to be removed in 0.7. + #[doc(hidden)] + pub const fn into_preserved(self) -> PreservedFolderAssetOptions { + PreservedFolderAssetOptions::new() + } + /// Convert the options into options for a generic asset pub const fn into_asset_options(self) -> AssetOptions { AssetOptions::Folder(self) } } + +/// A helper trait so crates can accept either folder type. +/// +/// This is planned to be removed in 0.7. +#[doc(hidden)] +pub trait FolderOptions: Sync { + fn optimize_files(&self) -> bool; +} + +impl FolderOptions for FolderAssetOptions { + fn optimize_files(&self) -> bool { + true + } +} + +impl FolderOptions for PreservedFolderAssetOptions { + fn optimize_files(&self) -> bool { + false + } +} + +/// A preserved folder which has no optimizations. +/// +/// This is planned to be removed in 0.7. +#[doc(hidden)] +#[non_exhaustive] +#[derive( + Debug, + Default, + PartialEq, + PartialOrd, + Clone, + Copy, + Hash, + SerializeConst, + serde::Serialize, + serde::Deserialize, +)] +pub struct PreservedFolderAssetOptions {} + +impl PreservedFolderAssetOptions { + pub const fn new() -> Self { + Self {} + } + + /// Convert the options into options for a generic asset + pub const fn into_asset_options(self) -> AssetOptions { + AssetOptions::PreservedFolder(self) + } +} diff --git a/packages/manganis/manganis-core/src/options.rs b/packages/manganis/manganis-core/src/options.rs index 714c1f9007..c21c5a2591 100644 --- a/packages/manganis/manganis-core/src/options.rs +++ b/packages/manganis/manganis-core/src/options.rs @@ -1,6 +1,9 @@ use const_serialize::SerializeConst; -use crate::{CssAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptions}; +use crate::{ + CssAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptions, + PreservedFolderAssetOptions, +}; /// Settings for a generic asset #[derive( @@ -21,6 +24,9 @@ pub enum AssetOptions { Image(ImageAssetOptions), /// A folder asset Folder(FolderAssetOptions), + /// A preserved folder asset. + #[doc(hidden)] + PreservedFolder(PreservedFolderAssetOptions), /// A css asset Css(CssAssetOptions), /// A javascript asset @@ -37,6 +43,7 @@ impl AssetOptions { AssetOptions::Css(_) => Some("css"), AssetOptions::Js(_) => Some("js"), AssetOptions::Folder(_) => None, + AssetOptions::PreservedFolder(_) => None, AssetOptions::Unknown => None, } } diff --git a/packages/manganis/manganis/src/macro_helpers.rs b/packages/manganis/manganis/src/macro_helpers.rs index 26a27b2450..f10cbb4245 100644 --- a/packages/manganis/manganis/src/macro_helpers.rs +++ b/packages/manganis/manganis/src/macro_helpers.rs @@ -137,7 +137,7 @@ fn test_unique_path() { let asset_config = AssetOptions::Unknown; let output_path = generate_unique_path(&input_path.to_string_lossy(), content_hash, &asset_config); - assert_eq!(output_path.as_str(), "test-c8c4cfad21cac262"); + assert_eq!(output_path.as_str(), "test-8d6e32dc0b45f853"); // Just changing the content hash should change the total hash let mut input_path = PathBuf::from("test"); @@ -147,7 +147,7 @@ fn test_unique_path() { let asset_config = AssetOptions::Unknown; let output_path = generate_unique_path(&input_path.to_string_lossy(), content_hash, &asset_config); - assert_eq!(output_path.as_str(), "test-7bced03789ff865c"); + assert_eq!(output_path.as_str(), "test-40783366737abc4d"); } /// Serialize an asset to a const buffer