diff --git a/Trunk.toml b/Trunk.toml
index ddc9cc8d..300dae55 100644
--- a/Trunk.toml
+++ b/Trunk.toml
@@ -22,8 +22,8 @@ offline = false
frozen = false
# Require Cargo.lock is up to date
locked = false
-# Allow disabling minification
-no_minification = false
+# Control minification
+minify = "never" # can be one of: never, on_release, always
# Allow disabling sub-resource integrity (SRI)
no_sri = false
@@ -45,7 +45,7 @@ no_autoreload = false
# Disable error reporting
no_error_reporting = false
# Additional headers set for responses.
-#headers = { "test-header" = "header value", "test-header2" = "header value 2" }
+# headers = { "test-header" = "header value", "test-header2" = "header value 2" }
# Protocol used for autoreload WebSockets connection.
ws_protocol = "ws"
# The certificate/private key pair to use for TLS, which is enabled if both are set.
diff --git a/site/content/assets.md b/site/content/assets.md
index b463c7f9..20a7b28a 100644
--- a/site/content/assets.md
+++ b/site/content/assets.md
@@ -56,7 +56,7 @@ This will typically look like: `` tag instead of using a `` tag.
- `data-integrity`: (optional) the `integrity` digest type for code & script resources. Defaults to plain `sha384`.
+ - `data-no-minify`: (optional) Opt-out of minification. Also see: [Minification](#minification).
- `data-target-path`: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without `..`.
## icon
@@ -72,6 +73,7 @@ This will typically look like: `,
- /// Allows disabling minification
+ /// Enable minification.
+ ///
+ /// This overrides the value from the configuration file.
+ // cli version
#[serde(default)]
- #[arg(long)]
- pub no_minification: bool,
+ #[arg(id = "minify", short = 'M', long)]
+ pub minify_cli: bool,
+
+ /// Control minification.
+ // toml version
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ #[serde(rename = "minify")]
+ #[arg(skip)]
+ pub minify_toml: Option,
/// Allows disabling sub-resource integrity (SRI)
#[serde(default)]
diff --git a/src/config/models/mod.rs b/src/config/models/mod.rs
index 05c56063..9c90b41d 100644
--- a/src/config/models/mod.rs
+++ b/src/config/models/mod.rs
@@ -353,9 +353,10 @@ impl ConfigOpts {
g.pattern_preload = g.pattern_preload.or(l.pattern_preload);
g.pattern_script = g.pattern_script.or(l.pattern_script);
g.pattern_params = g.pattern_params.or(l.pattern_params);
- // NOTE: this can not be disabled in the cascade.
- if l.no_minification {
- g.no_minification = true;
+
+ g.minify_toml = g.minify_toml.or(l.minify_toml);
+ if l.minify_cli {
+ g.minify_cli = true;
}
// NOTE: this can not be disabled in the cascade.
if l.no_sri {
diff --git a/src/config/models/types/duration.rs b/src/config/models/types/duration.rs
index d3e3de64..296b26c7 100644
--- a/src/config/models/types/duration.rs
+++ b/src/config/models/types/duration.rs
@@ -2,11 +2,12 @@ use serde::{Deserialize, Deserializer};
use std::str::FromStr;
use std::time::Duration;
+/// A newtype to allow using humantime durations as clap and serde values.
#[derive(Clone, Debug)]
pub struct ConfigDuration(pub Duration);
impl<'de> Deserialize<'de> for ConfigDuration {
- fn deserialize(deserializer: D) -> std::result::Result
+ fn deserialize(deserializer: D) -> Result
where
D: Deserializer<'de>,
{
@@ -17,7 +18,7 @@ impl<'de> Deserialize<'de> for ConfigDuration {
impl FromStr for ConfigDuration {
type Err = humantime::DurationError;
- fn from_str(s: &str) -> std::result::Result {
+ fn from_str(s: &str) -> Result {
Ok(Self(humantime::Duration::from_str(s)?.into()))
}
}
diff --git a/src/config/models/types/minify.rs b/src/config/models/types/minify.rs
new file mode 100644
index 00000000..a774c777
--- /dev/null
+++ b/src/config/models/types/minify.rs
@@ -0,0 +1,11 @@
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum Minify {
+ /// Never minify
+ #[default]
+ Never,
+ /// Minify for release builds
+ OnRelease,
+ /// Minify for all builds
+ Always,
+}
diff --git a/src/config/models/types/mod.rs b/src/config/models/types/mod.rs
index cc066dab..4cc10dce 100644
--- a/src/config/models/types/mod.rs
+++ b/src/config/models/types/mod.rs
@@ -1,9 +1,11 @@
mod address_family;
mod base_url;
mod duration;
+mod minify;
mod ws;
pub use address_family::*;
pub use base_url::*;
pub use duration::*;
+pub use minify::*;
pub use ws::*;
diff --git a/src/config/rt/build.rs b/src/config/rt/build.rs
index 2978c327..9ae4f075 100644
--- a/src/config/rt/build.rs
+++ b/src/config/rt/build.rs
@@ -1,6 +1,8 @@
use super::super::{DIST_DIR, STAGE_DIR};
-use crate::config::models::BaseUrl;
-use crate::config::{ConfigOptsBuild, ConfigOptsCore, ConfigOptsHook, ConfigOptsTools, RtcCore};
+use crate::config::{
+ models::{BaseUrl, Minify},
+ ConfigOptsBuild, ConfigOptsCore, ConfigOptsHook, ConfigOptsTools, RtcCore,
+};
use anyhow::{ensure, Context};
use std::collections::HashMap;
use std::io::ErrorKind;
@@ -72,8 +74,8 @@ pub struct RtcBuild {
///
/// **WARNING**: Setting this to true can make you vulnerable to man-in-the-middle attacks. Sometimes this is necessary when working behind corporate proxies.
pub accept_invalid_certs: Option,
- /// Allow disabling minification
- pub no_minification: bool,
+ /// Control minification
+ pub minify: Minify,
/// Allow disabling SRI
pub no_sri: bool,
/// Ignore error's due to self closed script tags, instead will issue a warning.
@@ -152,6 +154,13 @@ impl RtcBuild {
public_url = public_url.fix_trailing_slash();
}
+ let minify = match (opts.minify_cli, opts.minify_toml) {
+ // the CLI will override with "always"
+ (true, _) => Minify::Always,
+ // otherwise, we take the configuration value, or the default
+ (false, minify) => minify.unwrap_or_default(),
+ };
+
Ok(Self {
core,
target,
@@ -174,7 +183,7 @@ impl RtcBuild {
locked: opts.locked,
root_certificate: opts.root_certificate.map(PathBuf::from),
accept_invalid_certs: opts.accept_invalid_certs,
- no_minification: opts.no_minification,
+ minify,
no_sri: opts.no_sri,
ignore_script_error: opts.ignore_script_error,
})
@@ -217,16 +226,23 @@ impl RtcBuild {
locked: false,
root_certificate: None,
accept_invalid_certs: None,
- no_minification: false,
+ minify: Minify::Never,
no_sri: false,
ignore_script_error: false,
})
}
- /// Check if minification is globally enabled
- ///
- /// An asset might have an additional configuration, overriding the global one.
+ /// Evaluate the minify state with an asset's no_minify setting.
+ pub fn minify_asset(&self, no_minify: bool) -> bool {
+ !no_minify && self.should_minify()
+ }
+
+ /// Evaluate a global minify state, assets might override this.
pub fn should_minify(&self) -> bool {
- self.release && !self.no_minification
+ match (self.minify, self.release) {
+ (Minify::Never, _) => false,
+ (Minify::OnRelease, release) => release,
+ (Minify::Always, _) => true,
+ }
}
}
diff --git a/src/pipelines/css.rs b/src/pipelines/css.rs
index 7a92e80b..402a0001 100644
--- a/src/pipelines/css.rs
+++ b/src/pipelines/css.rs
@@ -28,7 +28,7 @@ pub struct Css {
/// The required integrity setting
integrity: IntegrityType,
/// Whether to minify or not
- minify: bool,
+ no_minify: bool,
/// Optional target path inside the dist dir.
target_path: Option,
}
@@ -51,7 +51,7 @@ impl Css {
let asset = AssetFile::new(&html_dir, path).await?;
let integrity = IntegrityType::from_attrs(&attrs, &cfg)?;
- let minify = !attrs.contains_key(ATTR_NO_MINIFY);
+ let no_minify = attrs.contains_key(ATTR_NO_MINIFY);
let target_path = data_target_path(&attrs)?;
Ok(Self {
@@ -60,7 +60,7 @@ impl Css {
asset,
attrs,
integrity,
- minify,
+ no_minify,
target_path,
})
}
@@ -76,7 +76,6 @@ impl Css {
async fn run(self) -> Result {
let rel_path = crate::common::strip_prefix(&self.asset.path);
tracing::debug!(path = ?rel_path, "copying & hashing css");
- let minify = self.cfg.should_minify() && self.minify;
let result_path =
target_path(&self.cfg.staging_dist, self.target_path.as_deref(), None).await?;
@@ -87,7 +86,7 @@ impl Css {
&self.cfg.staging_dist,
&result_path,
self.cfg.filehash,
- minify,
+ self.cfg.minify_asset(self.no_minify),
AssetFileType::Css,
)
.await?;
diff --git a/src/pipelines/icon.rs b/src/pipelines/icon.rs
index 16b67905..9ea00e34 100644
--- a/src/pipelines/icon.rs
+++ b/src/pipelines/icon.rs
@@ -27,7 +27,7 @@ pub struct Icon {
/// The required integrity setting
integrity: IntegrityType,
/// Whether to minify or not
- minify: bool,
+ no_minify: bool,
/// Optional target path inside the dist dir.
target_path: Option,
}
@@ -50,7 +50,7 @@ impl Icon {
let asset = AssetFile::new(&html_dir, path).await?;
let integrity = IntegrityType::from_attrs(&attrs, &cfg)?;
- let minify = !attrs.contains_key(ATTR_NO_MINIFY);
+ let no_minify = attrs.contains_key(ATTR_NO_MINIFY);
let target_path = data_target_path(&attrs)?;
Ok(Self {
@@ -58,7 +58,7 @@ impl Icon {
cfg,
asset,
integrity,
- minify,
+ no_minify,
target_path,
})
}
@@ -89,7 +89,7 @@ impl Icon {
&self.cfg.staging_dist,
&result_dir,
self.cfg.filehash,
- self.cfg.should_minify() && self.minify,
+ self.cfg.minify_asset(self.no_minify),
AssetFileType::Icon(image_type),
)
.await?;
diff --git a/src/pipelines/js.rs b/src/pipelines/js.rs
index 7927dde1..3a4f8450 100644
--- a/src/pipelines/js.rs
+++ b/src/pipelines/js.rs
@@ -30,7 +30,7 @@ pub struct Js {
/// If it's a JavaScript module (vs a classic script)
module: bool,
/// Whether to minify or not
- minify: bool,
+ no_minify: bool,
/// Optional target path inside the dist dir.
target_path: Option,
}
@@ -52,7 +52,7 @@ impl Js {
let integrity = IntegrityType::from_attrs(&attrs, &cfg)?;
let module = attrs.get("type").map(|s| s.as_str()) == Some("module");
- let minify = !attrs.contains_key(ATTR_NO_MINIFY);
+ let no_minify = attrs.contains_key(ATTR_NO_MINIFY);
let target_path = data_target_path(&attrs)?;
Ok(Self {
@@ -62,7 +62,7 @@ impl Js {
module,
attrs,
integrity,
- minify,
+ no_minify,
target_path,
})
}
@@ -78,7 +78,6 @@ impl Js {
async fn run(self) -> Result {
let rel_path = crate::common::strip_prefix(&self.asset.path);
tracing::debug!(path = ?rel_path, "copying & hashing js");
- let minify = self.cfg.should_minify() && self.minify;
let result_dir =
target_path(&self.cfg.staging_dist, self.target_path.as_deref(), None).await?;
@@ -89,7 +88,7 @@ impl Js {
&self.cfg.staging_dist,
&result_dir,
self.cfg.filehash,
- minify,
+ self.cfg.minify_asset(self.no_minify),
if self.module {
AssetFileType::Mjs
} else {
diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs
index 10e45e0c..6255a9d0 100644
--- a/src/pipelines/mod.rs
+++ b/src/pipelines/mod.rs
@@ -48,6 +48,7 @@ const ATTR_TYPE: &str = "type";
const ATTR_REL: &str = "rel";
const ATTR_NO_MINIFY: &str = "data-no-minify";
const ATTR_TARGET_PATH: &str = "data-target-path";
+
const SNIPPETS_DIR: &str = "snippets";
const TRUNK_ID: &str = "data-trunk-id";
const PNG_OPTIMIZATION_LEVEL: u8 = 6;
diff --git a/src/pipelines/rust/mod.rs b/src/pipelines/rust/mod.rs
index 817651cd..0902f9f8 100644
--- a/src/pipelines/rust/mod.rs
+++ b/src/pipelines/rust/mod.rs
@@ -800,7 +800,7 @@ impl RustApp {
.await
.context("error reading JS loader file")?;
- let write_bytes = match self.cfg.release && !self.cfg.no_minification {
+ let write_bytes = match self.cfg.should_minify() {
true => minify_js(bytes, mode),
false => bytes,
};
diff --git a/src/pipelines/sass.rs b/src/pipelines/sass.rs
index 917d4a9a..82307e9f 100644
--- a/src/pipelines/sass.rs
+++ b/src/pipelines/sass.rs
@@ -31,7 +31,7 @@ pub struct Sass {
/// The required integrity setting
integrity: IntegrityType,
/// Whether to minify or not
- minify: bool,
+ no_minify: bool,
/// Optional target path inside the dist dir.
target_path: Option,
}
@@ -54,7 +54,7 @@ impl Sass {
path.extend(href_attr.split('/'));
let asset = AssetFile::new(&html_dir, path).await?;
let use_inline = attrs.contains_key(ATTR_INLINE);
- let minify = !attrs.contains_key(ATTR_NO_MINIFY);
+ let no_minify = attrs.contains_key(ATTR_NO_MINIFY);
let integrity = IntegrityType::from_attrs(&attrs, &cfg)?;
let target_path = data_target_path(&attrs)?;
@@ -66,7 +66,7 @@ impl Sass {
use_inline,
other_attrs: attrs,
integrity,
- minify,
+ no_minify,
target_path,
})
}
@@ -105,17 +105,22 @@ impl Sass {
.display()
.to_string();
- let (source_map, output_style) = match self.cfg.release {
- true => (
- "--no-source-map",
- match self.cfg.no_minification || !self.minify {
- true => "expanded",
- false => "compressed",
- },
- ),
- false => ("--embed-source-map", "expanded"),
+ // source map setting, embedded for non-release builds
+
+ let source_map = match self.cfg.release {
+ true => "--no-source-map",
+ false => "--embed-source-map",
};
+ // put style, depends on minify state
+
+ let output_style = match self.cfg.minify_asset(self.no_minify) {
+ true => "compressed",
+ false => "expanded",
+ };
+
+ // collect arguments
+
let args = &[
source_map,
"--style",
@@ -124,6 +129,8 @@ impl Sass {
&temp_target_file_path,
];
+ // run
+
let rel_path = common::strip_prefix(&self.asset.path);
tracing::debug!(path = ?rel_path, "compiling sass/scss");
common::run_command(Application::Sass.name(), &sass, args).await?;
diff --git a/src/pipelines/tailwind_css.rs b/src/pipelines/tailwind_css.rs
index 3fa17472..bc11243c 100644
--- a/src/pipelines/tailwind_css.rs
+++ b/src/pipelines/tailwind_css.rs
@@ -4,10 +4,12 @@ use super::{
data_target_path, AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF,
ATTR_INLINE, ATTR_NO_MINIFY,
};
-use crate::common::{self, dist_relative, html_rewrite::Document, target_path};
-use crate::config::RtcBuild;
-use crate::processing::integrity::{IntegrityType, OutputDigest};
-use crate::tools::{self, Application};
+use crate::{
+ common::{self, dist_relative, html_rewrite::Document, target_path},
+ config::RtcBuild,
+ processing::integrity::{IntegrityType, OutputDigest},
+ tools::{self, Application},
+};
use anyhow::{Context, Result};
use std::path::PathBuf;
use std::sync::Arc;
@@ -29,7 +31,7 @@ pub struct TailwindCss {
/// The required integrity setting
integrity: IntegrityType,
/// Whether to minify or not
- minify: bool,
+ no_minify: bool,
/// Optional target path inside the dist dir.
target_path: Option,
}
@@ -51,9 +53,9 @@ impl TailwindCss {
path.extend(href_attr.split('/'));
let asset = AssetFile::new(&html_dir, path).await?;
let use_inline = attrs.contains_key(ATTR_INLINE);
- let minify = !attrs.contains_key(ATTR_NO_MINIFY);
let integrity = IntegrityType::from_attrs(&attrs, &cfg)?;
+ let no_minify = attrs.contains_key(ATTR_NO_MINIFY);
let target_path = data_target_path(&attrs)?;
Ok(Self {
@@ -63,7 +65,7 @@ impl TailwindCss {
use_inline,
integrity,
attrs,
- minify,
+ no_minify,
target_path,
})
}
@@ -90,18 +92,21 @@ impl TailwindCss {
.await?;
// Compile the target tailwind css file.
- let minify = self.cfg.should_minify() && self.minify;
- let style = if minify { "--minify" } else { "" };
let path_str = dunce::simplified(&self.asset.path).display().to_string();
let file_name = format!("{}.css", &self.asset.file_stem.to_string_lossy());
let file_path = dunce::simplified(&self.cfg.staging_dist.join(&file_name))
.display()
.to_string();
- let args = &["--input", &path_str, "--output", &file_path, style];
- let rel_path = crate::common::strip_prefix(&self.asset.path);
+ let mut args = vec!["--input", &path_str, "--output", &file_path];
+
+ if self.cfg.minify_asset(self.no_minify) {
+ args.push("--minify");
+ }
+
+ let rel_path = common::strip_prefix(&self.asset.path);
tracing::debug!(path = ?rel_path, "compiling tailwind css");
- common::run_command(Application::TailwindCss.name(), &tailwind, args).await?;
+ common::run_command(Application::TailwindCss.name(), &tailwind, &args).await?;
let css = fs::read_to_string(&file_path).await?;
fs::remove_file(&file_path).await?;