diff --git a/integrations/utils/src/lib.rs b/integrations/utils/src/lib.rs index 8039600220..159078746b 100644 --- a/integrations/utils/src/lib.rs +++ b/integrations/utils/src/lib.rs @@ -2,7 +2,7 @@ use futures::{Stream, StreamExt}; use leptos::{nonce::use_nonce, use_context, RuntimeId}; use leptos_config::LeptosOptions; use leptos_meta::MetaContext; -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashMap, env, fs}; extern crate tracing; @@ -103,6 +103,14 @@ pub fn html_parts_separated( } else { "() => mod.hydrate()" }; + + let (js_hash, wasm_hash, css_hash) = get_hashes(&options); + + let head = head.replace( + &format!("{output_name}.css"), + &format!("{output_name}{css_hash}.css"), + ); + let head = format!( r#" @@ -110,8 +118,8 @@ pub fn html_parts_separated( {head} - - + + @@ -134,6 +142,46 @@ pub fn html_parts_separated( (head, tail) } +#[tracing::instrument(level = "trace", fields(error), skip_all)] +fn get_hashes(options: &LeptosOptions) -> (String, String, String) { + let mut ext_to_hash = HashMap::from([ + ("js".to_string(), "".to_string()), + ("wasm".to_string(), "".to_string()), + ("css".to_string(), "".to_string()), + ]); + + if options.frontend_files_content_hashes { + let hash_path = env::current_exe() + .map(|path| { + path.parent().map(|p| p.to_path_buf()).unwrap_or_default() + }) + .unwrap_or_default() + .join(&options.hash_file); + + if hash_path.exists() { + let hashes = fs::read_to_string(&hash_path) + .expect("failed to read hash file"); + for line in hashes.lines() { + let line = line.trim(); + if !line.is_empty() { + if let Some((k, v)) = line.split_once(':') { + ext_to_hash.insert( + k.trim().to_string(), + format!(".{}", v.trim()), + ); + } + } + } + } + } + + ( + ext_to_hash["js"].clone(), + ext_to_hash["wasm"].clone(), + ext_to_hash["css"].clone(), + ) +} + #[tracing::instrument(level = "trace", fields(error), skip_all)] pub async fn build_async_response( stream: impl Stream + 'static, diff --git a/leptos_config/src/errors.rs b/leptos_config/src/errors.rs index 4c13d2d8c4..17517f8cb3 100644 --- a/leptos_config/src/errors.rs +++ b/leptos_config/src/errors.rs @@ -1,4 +1,4 @@ -use std::{net::AddrParseError, num::ParseIntError}; +use std::{net::AddrParseError, num::ParseIntError, str::ParseBoolError}; use thiserror::Error; #[derive(Debug, Error, Clone)] @@ -31,3 +31,9 @@ impl From for LeptosConfigError { Self::ConfigError(e.to_string()) } } + +impl From for LeptosConfigError { + fn from(e: ParseBoolError) -> Self { + Self::ConfigError(e.to_string()) + } +} diff --git a/leptos_config/src/lib.rs b/leptos_config/src/lib.rs index ffde76e1a1..96f89af3e5 100644 --- a/leptos_config/src/lib.rs +++ b/leptos_config/src/lib.rs @@ -70,6 +70,15 @@ pub struct LeptosOptions { #[builder(default = default_not_found_path())] #[serde(default = "default_not_found_path")] pub not_found_path: String, + /// The file name of the hash text file generated by cargo-leptos. Defaults to `hash.txt`. + #[builder(default = default_hash_file_name())] + #[serde(default = "default_hash_file_name")] + pub hash_file: String, + /// If true, hashes will be generated for all files in the site_root and added to their file names. + /// Defaults to `true`. + #[builder(default = default_frontend_files_content_hashes())] + #[serde(default = "default_frontend_files_content_hashes")] + pub frontend_files_content_hashes: bool, } impl LeptosOptions { @@ -108,6 +117,15 @@ impl LeptosOptions { env_w_default("LEPTOS_RELOAD_WS_PROTOCOL", "ws")?.as_str(), )?, not_found_path: env_w_default("LEPTOS_NOT_FOUND_PATH", "/404")?, + hash_file: env_w_default("LEPTOS_HASH_FILE_NAME", "hash.txt")?, + frontend_files_content_hashes: env_w_default( + "LEPTOS_FRONTEND_FILES_CONTENT_HASHES", + "ON", + )? + .to_uppercase() + .replace("ON", "true") + .replace("OFF", "false") + .parse()?, }) } } @@ -146,6 +164,14 @@ fn default_not_found_path() -> String { "/404".to_string() } +fn default_hash_file_name() -> String { + "hash.txt".to_string() +} + +fn default_frontend_files_content_hashes() -> bool { + true +} + fn env_wo_default(key: &str) -> Result, LeptosConfigError> { match std::env::var(key) { Ok(val) => Ok(Some(val)),