From bff5478fdf59894e04c277d83387a66254899baa Mon Sep 17 00:00:00 2001 From: Miles Murgaw Date: Thu, 5 Dec 2024 13:41:44 -0500 Subject: [PATCH] Feat: Manganis SCSS Support (#3294) * feat: scss manganis support * revision: make ScssLogger private --- Cargo.lock | 42 ++++++++++++++ packages/cli-opt/Cargo.toml | 4 ++ packages/cli-opt/src/css.rs | 57 +++++++++++++++++++ packages/cli-opt/src/file.rs | 5 ++ .../manganis/manganis/src/macro_helpers.rs | 26 ++++++++- 5 files changed, 133 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3827fc4d4..bc103f85f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2385,6 +2385,12 @@ dependencies = [ "objc", ] +[[package]] +name = "codemap" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -3540,7 +3546,9 @@ version = "0.6.0-rc.0" dependencies = [ "anyhow", "browserslist-rs", + "codemap", "const-serialize", + "grass", "image", "imagequant", "lightningcss", @@ -5948,6 +5956,31 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "grass" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94" +dependencies = [ + "clap", + "getrandom 0.2.15", + "grass_compiler", +] + +[[package]] +name = "grass_compiler" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" +dependencies = [ + "codemap", + "indexmap 2.7.0", + "lasso", + "once_cell", + "phf 0.11.2", + "rand 0.8.5", +] + [[package]] name = "group" version = "0.12.1" @@ -7346,6 +7379,15 @@ dependencies = [ "log", ] +[[package]] +name = "lasso" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "lazy-js-bundle" version = "0.6.0-rc.0" diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index 84f3d25c8..8d5e0cc67 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -34,6 +34,10 @@ image = { version = "0.25", features = ["avif"] } # CSS Minification lightningcss = "1.0.0-alpha.60" +# SCSS Processing +grass = "0.13.4" +codemap = "0.1.3" + # Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates swc = "=0.283.0" swc_allocator = { version = "=0.1.8", default-features = false } diff --git a/packages/cli-opt/src/css.rs b/packages/cli-opt/src/css.rs index f0e3dd155..6dcc37f15 100644 --- a/packages/cli-opt/src/css.rs +++ b/packages/cli-opt/src/css.rs @@ -1,11 +1,14 @@ use std::path::Path; use anyhow::Context; +use codemap::SpanLoc; +use grass::OutputStyle; use lightningcss::{ printer::PrinterOptions, stylesheet::{MinifyOptions, ParserOptions, StyleSheet}, }; use manganis_core::CssAssetOptions; +use tracing::{debug, warn}; pub(crate) fn process_css( css_options: &CssAssetOptions, @@ -40,3 +43,57 @@ pub(crate) fn minify_css(css: &str) -> String { let res = stylesheet.to_css(printer).unwrap(); res.code } + +/// Process an scss/sass file into css. +pub(crate) fn process_scss( + scss_options: &CssAssetOptions, + source: &Path, + output_path: &Path, +) -> anyhow::Result<()> { + let style = match scss_options.minified() { + true => OutputStyle::Compressed, + false => OutputStyle::Expanded, + }; + + let options = grass::Options::default() + .style(style) + .quiet(false) + .logger(&ScssLogger {}); + + let css = grass::from_path(source, &options)?; + let minified = minify_css(&css); + + std::fs::write(output_path, minified).with_context(|| { + format!( + "Failed to write css to output location: {}", + output_path.display() + ) + })?; + + Ok(()) +} + +/// Logger for Grass that re-uses their StdLogger formatting but with tracing. +#[derive(Debug)] +struct ScssLogger {} + +impl grass::Logger for ScssLogger { + fn debug(&self, location: SpanLoc, message: &str) { + debug!( + "{}:{} DEBUG: {}", + location.file.name(), + location.begin.line + 1, + message + ); + } + + fn warn(&self, location: SpanLoc, message: &str) { + warn!( + "Warning: {}\n ./{}:{}:{}", + message, + location.file.name(), + location.begin.line + 1, + location.begin.column + 1 + ); + } +} diff --git a/packages/cli-opt/src/file.rs b/packages/cli-opt/src/file.rs index aec7b5f93..8ef22f3c7 100644 --- a/packages/cli-opt/src/file.rs +++ b/packages/cli-opt/src/file.rs @@ -2,6 +2,8 @@ use anyhow::Context; use manganis_core::{AssetOptions, CssAssetOptions, ImageAssetOptions, JsAssetOptions}; use std::path::Path; +use crate::css::process_scss; + use super::{ css::process_css, folder::process_folder, image::process_image, js::process_js, json::process_json, @@ -29,6 +31,9 @@ pub fn process_file_to( Some("css") => { process_css(&CssAssetOptions::new(), source, output_path)?; } + Some("scss" | "sass") => { + process_scss(&CssAssetOptions::new(), source, output_path)?; + } Some("js") => { process_js(&JsAssetOptions::new(), source, output_path)?; } diff --git a/packages/manganis/manganis/src/macro_helpers.rs b/packages/manganis/manganis/src/macro_helpers.rs index e84ff29f9..26a27b245 100644 --- a/packages/manganis/manganis/src/macro_helpers.rs +++ b/packages/manganis/manganis/src/macro_helpers.rs @@ -74,7 +74,15 @@ pub const fn generate_unique_path( None => { if let Some(extension) = extension { macro_output_path = macro_output_path.push('.'); - macro_output_path = macro_output_path.push_str(extension.as_str()) + + let ext_bytes = extension.as_str().as_bytes(); + + // Rewrite scss as css + if bytes_equal(ext_bytes, b"scss") || bytes_equal(ext_bytes, b"sass") { + macro_output_path = macro_output_path.push_str("css") + } else { + macro_output_path = macro_output_path.push_str(extension.as_str()) + } } } } @@ -82,6 +90,22 @@ pub const fn generate_unique_path( macro_output_path } +const fn bytes_equal(left: &[u8], right: &[u8]) -> bool { + if left.len() != right.len() { + return false; + } + + let mut i = 0; + while i < left.len() { + if left[i] != right[i] { + return false; + } + i += 1; + } + + true +} + #[test] fn test_unique_path() { use manganis_core::{ImageAssetOptions, ImageFormat};