Pārlūkot izejas kodu

Feat: Manganis SCSS Support (#3294)

* feat: scss manganis support

* revision: make ScssLogger private
Miles Murgaw 6 mēneši atpakaļ
vecāks
revīzija
bff5478fdf

+ 42 - 0
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"

+ 4 - 0
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 }

+ 57 - 0
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
+        );
+    }
+}

+ 5 - 0
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)?;
             }

+ 25 - 1
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};