Add sass macro
This commit is contained in:
16
crates/macro-sass/Cargo.toml
Normal file
16
crates/macro-sass/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "macro-sass"
|
||||
version = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
grass = { workspace = true }
|
||||
157
crates/macro-sass/src/lib.rs
Normal file
157
crates/macro-sass/src/lib.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::path::PathBuf;
|
||||
use syn::{LitStr, parse_macro_input};
|
||||
|
||||
/// A macro for parsing Sass/SCSS files at compile time.
|
||||
///
|
||||
/// This macro takes a file path to a Sass/SCSS file, compiles it to CSS at compile time
|
||||
/// using the `grass` compiler, and returns the resulting CSS as a `&'static str`.
|
||||
///
|
||||
/// # Behavior
|
||||
///
|
||||
/// Similar to `include_str!`, this macro:
|
||||
/// - Reads the specified file at compile time
|
||||
/// - Compiles the Sass/SCSS to CSS using `grass::from_path()` with default options
|
||||
/// - Handles `@import` and `@use` directives, resolving imported files relative to the main file
|
||||
/// - Embeds the resulting CSS string in the binary
|
||||
/// - Returns a `&'static str` containing the compiled CSS
|
||||
///
|
||||
/// # Syntax
|
||||
///
|
||||
/// ```notrust
|
||||
/// sass!("path/to/file.scss")
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - A string literal containing the path to the Sass/SCSS file (relative to the crate root)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```notrust
|
||||
/// // Relative to crate root: looks for src/routes/css/main.scss
|
||||
/// const MY_STYLES: &str = sass!("src/routes/css/main.scss");
|
||||
///
|
||||
/// // Use in HTML generation
|
||||
/// html! {
|
||||
/// style { (PreEscaped(MY_STYLES)) }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Import Support
|
||||
///
|
||||
/// The macro fully supports Sass imports and uses:
|
||||
/// ```notrust
|
||||
/// // main.scss
|
||||
/// @import "variables";
|
||||
/// @use "mixins";
|
||||
///
|
||||
/// .button {
|
||||
/// color: $primary-color;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// All imported files are resolved relative to the location of the main Sass file.
|
||||
///
|
||||
/// # Compile-time vs Runtime
|
||||
///
|
||||
/// Instead of this runtime code:
|
||||
/// ```notrust
|
||||
/// let css = grass::from_path(
|
||||
/// "styles.scss",
|
||||
/// &grass::Options::default()
|
||||
/// ).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// You can use:
|
||||
/// ```notrust
|
||||
/// const CSS: &str = sass!("styles.scss");
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This macro will cause a compile error if:
|
||||
/// - The specified file does not exist
|
||||
/// - The file path is invalid
|
||||
/// - The Sass/SCSS file contains syntax errors
|
||||
/// - Any imported files cannot be found
|
||||
/// - The grass compiler fails for any reason
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The file path is relative to the crate root (where `Cargo.toml` is located), determined
|
||||
/// by the `CARGO_MANIFEST_DIR` environment variable. This is similar to how `include!()` works
|
||||
/// but differs from `include_str!()` which is relative to the current file.
|
||||
#[proc_macro]
|
||||
pub fn sass(input: TokenStream) -> TokenStream {
|
||||
let input_lit = parse_macro_input!(input as LitStr);
|
||||
|
||||
let file_path = match PathBuf::try_from(input_lit.value()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return syn::Error::new(input_lit.span(), format!("Invalid path: {e}"))
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
|
||||
// Not stable yet, we have to use crate-relative paths :(
|
||||
//let span = proc_macro::Span::call_site();
|
||||
//let source_file = span.source_file();
|
||||
//let path: PathBuf = source_file.path();
|
||||
|
||||
// Use a combination of include_str! and grass compilation
|
||||
// include_str! handles the relative path resolution for us
|
||||
// We generate code that uses include_str! at the user's call site
|
||||
// and compiles it at macro expansion time
|
||||
|
||||
// First, try to read and compile the file at macro expansion time
|
||||
// The path is interpreted relative to CARGO_MANIFEST_DIR
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
|
||||
let full_path = std::path::Path::new(&manifest_dir).join(&file_path);
|
||||
|
||||
let css = match grass::from_path(&full_path, &grass::Options::default()) {
|
||||
Ok(css) => css,
|
||||
Err(e) => {
|
||||
return syn::Error::new(
|
||||
input_lit.span(),
|
||||
format!(
|
||||
"Failed to compile Sass file '{}': {}",
|
||||
file_path.display(),
|
||||
e,
|
||||
),
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
|
||||
// Generate code that returns the compiled CSS as a string literal
|
||||
let expanded = quote! {
|
||||
#css
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn sass_str(input: TokenStream) -> TokenStream {
|
||||
let input_lit = parse_macro_input!(input as LitStr);
|
||||
let sass_str = input_lit.value();
|
||||
|
||||
let css = match grass::from_string(&sass_str, &grass::Options::default()) {
|
||||
Ok(css) => css,
|
||||
Err(e) => {
|
||||
return syn::Error::new(
|
||||
input_lit.span(),
|
||||
format!("Failed to compile Sass string: {e}."),
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
|
||||
let expanded = quote! { #css };
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
Reference in New Issue
Block a user