#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
pub mod config;
pub mod minify;
use anyhow::Result;
pub use config::{MinifyOptions, Target};
pub struct Minifier {
options: MinifyOptions,
}
pub struct MinifyResult {
pub code: String,
pub map: Option<String>,
pub stats: MinifyStats,
}
#[derive(Debug, Default, Clone)]
pub struct MinifyStats {
pub original_size: usize,
pub minified_size: usize,
pub compression_ratio: f64,
pub time_ms: u128,
}
impl Minifier {
#[must_use]
pub const fn new(options: MinifyOptions) -> Self {
Self { options }
}
pub fn minify_js(&self, source: &str) -> Result<MinifyResult> {
minify::js::minify(source, None, &self.options)
}
pub fn minify_js_with_name(&self, source: &str, filename: &str) -> Result<MinifyResult> {
minify::js::minify(source, Some(filename), &self.options)
}
pub fn minify_css(&self, source: &str) -> Result<MinifyResult> {
minify::css::minify(source, None, &self.options)
}
pub fn minify_css_with_name(&self, source: &str, filename: &str) -> Result<MinifyResult> {
minify::css::minify(source, Some(filename), &self.options)
}
pub fn minify_json(&self, source: &str) -> Result<MinifyResult> {
minify::json::minify(source, &self.options)
}
pub fn minify_html(&self, source: &str) -> Result<MinifyResult> {
minify::html::minify(source, &self.options)
}
pub fn minify_svg(&self, source: &str) -> Result<MinifyResult> {
minify::svg::minify(source, &self.options)
}
pub fn minify_auto(&self, source: &str, filename: Option<&str>) -> Result<MinifyResult> {
let format = detect_format(source, filename);
match format {
Format::JavaScript => minify::js::minify(source, filename, &self.options),
Format::CSS => minify::css::minify(source, filename, &self.options),
Format::JSON => self.minify_json(source),
Format::Html => self.minify_html(source),
Format::Svg => self.minify_svg(source),
}
}
}
#[allow(clippy::upper_case_acronyms)]
enum Format {
JavaScript,
CSS,
JSON,
Html,
Svg,
}
fn detect_format(source: &str, filename: Option<&str>) -> Format {
if let Some(name) = filename
&& let Some(ext) = std::path::Path::new(name).extension()
{
if ext.eq_ignore_ascii_case("css") {
return Format::CSS;
}
if ext.eq_ignore_ascii_case("json") {
return Format::JSON;
}
if ext.eq_ignore_ascii_case("html") || ext.eq_ignore_ascii_case("htm") {
return Format::Html;
}
if ext.eq_ignore_ascii_case("svg") {
return Format::Svg;
}
if ext.eq_ignore_ascii_case("js")
|| ext.eq_ignore_ascii_case("mjs")
|| ext.eq_ignore_ascii_case("cjs")
{
return Format::JavaScript;
}
}
let trimmed = source.trim_start();
if trimmed.starts_with('{') || trimmed.starts_with('[') {
if serde_json::from_str::<serde_json::Value>(source).is_ok() {
return Format::JSON;
}
}
let head = trimmed.get(..256).unwrap_or(trimmed);
let head_lower = head.to_ascii_lowercase();
if head_lower.starts_with("<!doctype html") || head_lower.starts_with("<html") {
return Format::Html;
}
if head_lower.starts_with("<svg") || head_lower.contains("<svg ") {
return Format::Svg;
}
Format::JavaScript
}
impl MinifyStats {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn with_sizes(original_size: usize, minified_size: usize) -> Self {
let compression_ratio = if original_size > 0 {
1.0 - (minified_size as f64 / original_size as f64)
} else {
0.0
};
Self {
original_size,
minified_size,
compression_ratio,
time_ms: 0,
}
}
#[must_use]
pub const fn with_time(mut self, time_ms: u128) -> Self {
self.time_ms = time_ms;
self
}
}