breaking: gate 'styly!(const scope...' behind the const-scoping feature.\nFor some reason it did only become a problem recently, but it appears that having lightningcss/grass unconditionally will break WASM builds by having getrandom dependency

This commit is contained in:
yiffyrusdev 2025-05-08 19:42:06 +05:00 • committed by spushk
commit 355465e8cf
Signed by: spushk
GPG key ID: 7797B761E105E8AA
14 changed files with 150 additions and 97 deletions

View file

@ -4,10 +4,33 @@ members = ["sabry_procmacro", "sabry_procmacro_impl", "sabry_intrnl"]
exclude = ["examples"] exclude = ["examples"]
[workspace.dependencies] [workspace.dependencies]
## Workspace
sabry_intrnl = { path = "sabry_intrnl", version = "0.0.5" } sabry_intrnl = { path = "sabry_intrnl", version = "0.0.5" }
sabry_procmacro = { path = "sabry_procmacro", version = "0.0.5" } sabry_procmacro = { path = "sabry_procmacro", version = "0.0.5" }
sabry_procmacro_impl = { path = "sabry_procmacro_impl", version = "0.0.5" } sabry_procmacro_impl = { path = "sabry_procmacro_impl", version = "0.0.5" }
sabry_build = { path = "sabry_build", version = "0.0.5" } sabry_build = { path = "sabry_build", version = "0.0.5" }
## Package
## Package
lightningcss.version = "1.0.0-alpha.65"
lightningcss.default-features = false
grass.version = "^0.13"
grass.default-features = false
raffia.version = "^0.9"
#/
serde.version = "^1"
toml.version = "^0.8"
blake3.version = "^1"
base64.version = "^0.22"
regex.version = "^1"
#/
itertools.version = "^0.14"
walkdir.version = "^2"
#/
cfg-if.version = "^1"
thiserror.version = "^2"
proc-macro2.version = "^1"
quote.version = "^1"
syn.version = "^2"
[workspace.package] [workspace.package]
version = "0.0.5" version = "0.0.5"
@ -31,6 +54,7 @@ license.workspace = true
publish.workspace = true publish.workspace = true
[dependencies] [dependencies]
## Workspace
sabry_procmacro = { workspace = true, optional = true } sabry_procmacro = { workspace = true, optional = true }
sabry_build = { workspace = true, optional = true } sabry_build = { workspace = true, optional = true }
sabry_intrnl = { workspace = true } sabry_intrnl = { workspace = true }
@ -42,6 +66,7 @@ insta = { version = "^1.41", features = ["glob"] }
[features] [features]
default = ["procmacro"] default = ["procmacro"]
lepty-scoping = ["sabry_intrnl/lepty-scoping"] lepty-scoping = ["sabry_intrnl/lepty-scoping"]
const-scoping = ["sabry_procmacro?/const-compile"]
procmacro = ["dep:sabry_procmacro"] procmacro = ["dep:sabry_procmacro"]
build = ["dep:sabry_build"] build = ["dep:sabry_build"]
net-files = ["sabry_build/net-files"] net-files = ["sabry_build/net-files"]

View file

@ -8,9 +8,6 @@
> >
> "master" branch is what's currently on crates.io > "master" branch is what's currently on crates.io
> >
> each version ever been on crates.io (except the latest release) is available in "vX.X.X" branch
>
> the latest-trashy state is in the "window" branch
[![Crates.io](https://img.shields.io/crates/v/sabry.svg)](https://crates.io/crates/sabry) [![Crates.io](https://img.shields.io/crates/v/sabry.svg)](https://crates.io/crates/sabry)
[![Docs.rs](https://img.shields.io/docsrs/sabry/latest.svg)](https://docs.rs/sabry) [![Docs.rs](https://img.shields.io/docsrs/sabry/latest.svg)](https://docs.rs/sabry)
@ -586,6 +583,7 @@ As you've seen above, scope doe not contain any style code by itself. That's the
However you could still compile styles into the artifact by simply adding the `const` to the macro call: However you could still compile styles into the artifact by simply adding the `const` to the macro call:
```rust ```rust
// Requires `const-scoping` feature
sabry::styly!(const scope "tests/assets/mixin-module.scss"); sabry::styly!(const scope "tests/assets/mixin-module.scss");
``` ```
Which results in following: Which results in following:

View file

@ -13,19 +13,19 @@ leptos = { version = "^0.7" }
leptos_meta = { version = "^0.7" } leptos_meta = { version = "^0.7" }
leptos_router = { version = "^0.7" } leptos_router = { version = "^0.7" }
sabry = {path = "../../", features = ["lepty-scoping"]} sabry = { path = "../../", features = ["lepty-scoping", "const-scoping"] }
[build-dependencies] [build-dependencies]
sabry = {path = "../../", features = ["build", "lepty-scoping"]} sabry = { path = "../../", features = [
define-styles = {path = "../define-styles"} "build",
"lepty-scoping",
"const-scoping",
] }
define-styles = { path = "../define-styles" }
[features] [features]
hydrate = ["leptos/hydrate"] hydrate = ["leptos/hydrate"]
ssr = [ ssr = ["leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr"]
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.sabry] [package.metadata.sabry]
css.minify = true css.minify = true

View file

@ -9,13 +9,17 @@ authors.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
sabry_intrnl = { workspace = true } sabry_intrnl.workspace = true
sabry_procmacro_impl = { workspace = true } sabry_intrnl.features = ["compile", "scope"]
sabry_procmacro_impl.workspace = true
sabry_procmacro_impl.features = ["const-compile"]
syn = { version = "^2", features = ["visit", "derive", "parsing", "full"] } syn.workspace = true
proc-macro2 = { version = "^1", features = ["span-locations"] } syn.features = ["visit", "derive", "parsing", "full"]
thiserror = { version = "^2" } proc-macro2.workspace = true
walkdir = { version = "^2" } proc-macro2.features = ["span-locations"]
thiserror.workspace = true
walkdir.workspace = true
[features] [features]
net-files = [] net-files = []

View file

@ -9,21 +9,30 @@ authors.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
syn = { version = "^2" } syn.workspace = true
raffia = { version = "^0.9" } raffia.workspace = true
regex = { version = "^1" } regex.workspace = true
grass = { version = "^0.13" } regex.optional = true
lightningcss = { version = "1.0.0-alpha.65" } grass.workspace = true
blake3 = { version = "^1.5" } grass.optional = true
base64 = { version = "^0.22" } lightningcss.workspace = true
lightningcss.features = ["grid"]
lightningcss.optional = true
blake3.workspace = true
blake3.optional = true
base64.workspace = true
base64.optional = true
thiserror = { version = "^2" } thiserror.workspace = true
serde = { version = "^1", features = ["derive"] } serde.workspace = true
toml = { version = "^0.8" } serde.features = ["derive"]
cfg-if = { version = "^1" } toml.workspace = true
cfg-if.workspace = true
[features] [features]
lepty-scoping = [] lepty-scoping = []
scope = ["dep:blake3", "dep:base64", "dep:regex"]
compile = ["dep:lightningcss", "dep:grass"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -1,16 +1,8 @@
use std::fmt::Debug; use crate::config::SabryConfig;
use lightningcss::{
error::{MinifyErrorKind, PrinterErrorKind},
printer::PrinterOptions,
stylesheet::MinifyOptions,
targets::Targets,
};
use crate::{config::SabryConfig, syntax::ostrta::OneSyntaxToRuleThemAll};
/// Convenience wrapper on Grass and Lightningcss /// Convenience wrapper on Grass and Lightningcss
pub struct CompilerAdapter { pub struct CompilerAdapter {
#[allow(dead_code)] // without 'compile' feature, still want to set this
config: SabryConfig, config: SabryConfig,
} }
@ -21,9 +13,10 @@ impl CompilerAdapter {
/// Compile given SASS/SCSS into CSS /// Compile given SASS/SCSS into CSS
/// with respect to self.config /// with respect to self.config
#[cfg(feature = "compile")]
pub fn compile_module( pub fn compile_module(
&self, &self,
syntax: OneSyntaxToRuleThemAll, syntax: crate::syntax::ostrta::OneSyntaxToRuleThemAll,
code: &str, code: &str,
) -> Result<String, SabryCompilerError> { ) -> Result<String, SabryCompilerError> {
let options = grass::Options::from(&self.config).input_syntax(syntax.into()); let options = grass::Options::from(&self.config).input_syntax(syntax.into());
@ -35,7 +28,10 @@ impl CompilerAdapter {
/// Perform lightningcss transformations on given css /// Perform lightningcss transformations on given css
/// with respect to self.config.css.minify on minification /// with respect to self.config.css.minify on minification
#[cfg(feature = "compile")]
pub fn lightningcss(&self, css: &str) -> Result<String, SabryCompilerError> { pub fn lightningcss(&self, css: &str) -> Result<String, SabryCompilerError> {
use lightningcss::{printer::PrinterOptions, stylesheet::MinifyOptions, targets::Targets};
let mut lightsheet = match lightningcss::stylesheet::StyleSheet::parse( let mut lightsheet = match lightningcss::stylesheet::StyleSheet::parse(
css, css,
lightningcss::stylesheet::ParserOptions::default(), lightningcss::stylesheet::ParserOptions::default(),
@ -81,6 +77,7 @@ impl CompilerAdapter {
} }
} }
#[cfg(feature = "compile")]
impl From<&SabryConfig> for grass::Options<'_> { impl From<&SabryConfig> for grass::Options<'_> {
fn from(config: &SabryConfig) -> Self { fn from(config: &SabryConfig) -> Self {
let mut options = grass::Options::default(); let mut options = grass::Options::default();
@ -95,14 +92,15 @@ impl From<&SabryConfig> for grass::Options<'_> {
} }
} }
#[cfg(feature = "compile")]
#[derive(thiserror::Error)] #[derive(thiserror::Error)]
pub enum SabryCompilerError { pub enum SabryCompilerError {
#[error("Could not compile into CSS with grass")] #[error("Could not compile into CSS with grass")]
GrassCompile(#[from] Box<grass::Error>), GrassCompile(#[from] Box<grass::Error>),
#[error("Could not print compiled CSS with lightningcss")] #[error("Could not print compiled CSS with lightningcss")]
LightPrint(#[from] lightningcss::error::Error<PrinterErrorKind>), LightPrint(#[from] lightningcss::error::Error<lightningcss::error::PrinterErrorKind>),
#[error("Could not minify CSS with lightningcss")] #[error("Could not minify CSS with lightningcss")]
LightMinify(#[from] lightningcss::error::Error<MinifyErrorKind>), LightMinify(#[from] lightningcss::error::Error<lightningcss::error::MinifyErrorKind>),
#[error("Could not parse CSS with lightningcss")] #[error("Could not parse CSS with lightningcss")]
LightParse { LightParse {
kind: String, kind: String,
@ -110,7 +108,8 @@ pub enum SabryCompilerError {
}, },
} }
impl Debug for SabryCompilerError { #[cfg(feature = "compile")]
impl std::fmt::Debug for SabryCompilerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let explain = match self { let explain = match self {
Self::LightPrint(e) => format!( Self::LightPrint(e) => format!(

View file

@ -199,6 +199,7 @@ impl SabryLightTargets {
} }
} }
#[cfg(feature = "compile")]
impl From<SabryLightTargets> for lightningcss::targets::Browsers { impl From<SabryLightTargets> for lightningcss::targets::Browsers {
fn from(value: SabryLightTargets) -> Self { fn from(value: SabryLightTargets) -> Self {
Self { Self {

View file

@ -1,15 +1,14 @@
use base64::Engine;
use crate::config::SabryHashConfig;
use super::{apply_basic_rusty_member_gen_rules, ArbitraryScope};
/// Convenience wrapper for String-being-a-hash /// Convenience wrapper for String-being-a-hash
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ScopeHash(String); pub struct ScopeHash(String);
impl ScopeHash { impl ScopeHash {
pub fn new(scope: &ArbitraryScope, config: &SabryHashConfig) -> Self { #[cfg(feature = "scope")]
pub fn new(scope: &super::ArbitraryScope, config: &crate::config::SabryHashConfig) -> Self {
use base64::Engine;
use crate::scoper::apply_basic_rusty_member_gen_rules;
let mut hasher = blake3::Hasher::new(); let mut hasher = blake3::Hasher::new();
if config.use_scope_name { if config.use_scope_name {

View file

@ -3,12 +3,8 @@ use std::fmt::Debug;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use hash::ScopeHash; use hash::ScopeHash;
use raffia::{Span, Spanned}; use raffia::{Span, Spanned};
use regex::Regex;
use crate::{ use crate::syntax::{ostrta::OneSyntaxToRuleThemAll, StylesheetAdapter};
config::SabryHashConfig,
syntax::{ostrta::OneSyntaxToRuleThemAll, StylesheetAdapter},
};
pub mod hash; pub mod hash;
@ -56,7 +52,11 @@ impl<'s> ArbitraryScope<'s> {
/// Consume arbitrary scope and create a [HashedScope] in its basis /// Consume arbitrary scope and create a [HashedScope] in its basis
/// ///
/// This function calls [HashedScope::new] under the hood /// This function calls [HashedScope::new] under the hood
pub fn hashed(self, config: &SabryHashConfig) -> Result<HashedScope<'s>, ScopeError> { #[cfg(feature = "scope")]
pub fn hashed(
self,
config: &crate::config::SabryHashConfig,
) -> Result<HashedScope<'s>, ScopeError> {
let hash = ScopeHash::new(&self, config); let hash = ScopeHash::new(&self, config);
HashedScope::new(hash, self) HashedScope::new(hash, self)
} }
@ -332,6 +332,7 @@ impl ScopedSelector {
} }
/// Generate rusty member ident based on selector type and CSS ident /// Generate rusty member ident based on selector type and CSS ident
#[cfg(feature = "scope")]
pub fn gen_rusty_ident(&self) -> Option<syn::Ident> { pub fn gen_rusty_ident(&self) -> Option<syn::Ident> {
let arb = &self.as_arbitrary().ident; let arb = &self.as_arbitrary().ident;
let basic = apply_basic_rusty_member_gen_rules(arb); let basic = apply_basic_rusty_member_gen_rules(arb);
@ -401,8 +402,9 @@ impl ScopedSelector {
} }
} }
#[cfg(feature = "scope")]
pub fn apply_basic_rusty_member_gen_rules(source: &str) -> String { pub fn apply_basic_rusty_member_gen_rules(source: &str) -> String {
let omit_regex = Regex::new(r"(^\-)|(\-$)|[^a-zA-Z0-9\-\_]") let omit_regex = regex::Regex::new(r"(^\-)|(\-$)|[^a-zA-Z0-9\-\_]")
.expect("BUG: can not build omition regex for rusty member generation"); .expect("BUG: can not build omition regex for rusty member generation");
// omit forbidden chars // omit forbidden chars

View file

@ -34,6 +34,7 @@ impl TryFrom<raffia::Syntax> for OneSyntaxToRuleThemAll {
} }
} }
#[cfg(feature = "compile")]
impl TryFrom<grass::InputSyntax> for OneSyntaxToRuleThemAll { impl TryFrom<grass::InputSyntax> for OneSyntaxToRuleThemAll {
type Error = (); type Error = ();
fn try_from(value: grass::InputSyntax) -> Result<Self, Self::Error> { fn try_from(value: grass::InputSyntax) -> Result<Self, Self::Error> {
@ -54,6 +55,7 @@ impl From<OneSyntaxToRuleThemAll> for raffia::Syntax {
} }
} }
#[cfg(feature = "compile")]
impl From<OneSyntaxToRuleThemAll> for grass::InputSyntax { impl From<OneSyntaxToRuleThemAll> for grass::InputSyntax {
fn from(value: OneSyntaxToRuleThemAll) -> Self { fn from(value: OneSyntaxToRuleThemAll) -> Self {
match value { match value {

View file

@ -12,11 +12,12 @@ license.workspace = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
sabry_procmacro_impl = { workspace = true } sabry_procmacro_impl.workspace = true
cfg-if = { version = "^1" } cfg-if.workspace = true
[features] [features]
nightly = [] nightly = []
const-compile = ["sabry_procmacro_impl/const-compile"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -9,15 +9,20 @@ authors.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
sabry_intrnl = { workspace = true } sabry_intrnl.workspace = true
syn = { version = "^2", features = ["parsing"] } sabry_intrnl.features = ["scope"]
quote = { version = "^1" } syn.workspace = true
proc-macro2 = { version = "^1", features = ["span-locations"] } syn.features = ["parsing"]
regex = { version = "^1" } quote.workspace = true
itertools = { version = "^0.14" } proc-macro2.workspace = true
proc-macro2.features = ["span-locations"]
regex.workspace = true
itertools.workspace = true
cfg-if.workspace = true
[features] [features]
nightly = [] nightly = []
const-compile = ["sabry_intrnl/compile"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -36,7 +36,7 @@ pub fn scssy_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
} }
let sourcesass = code.to_string(); let sourcesass = code.to_string();
let macro_doc = format!("Arbitrary {:?} code declared with `scssy!`. Pretty usable in tandem with `usey!` and `buildy` at build time", syntax); let macro_doc = format!("Arbitrary {syntax:?} code declared with `scssy!`. Pretty usable in tandem with `usey!` and `buildy` at build time");
quote! { quote! {
#[doc = #macro_doc] #[doc = #macro_doc]

View file

@ -1,10 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use cfg_if::cfg_if;
use itertools::Itertools; use itertools::Itertools;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use sabry_intrnl::{ use sabry_intrnl::{
compiler::CompilerAdapter,
config::SabryConfig, config::SabryConfig,
scoper::{apply_basic_rusty_member_gen_rules, ArbitraryScope, ScopedSelector}, scoper::{apply_basic_rusty_member_gen_rules, ArbitraryScope, ScopedSelector},
}; };
@ -38,10 +38,7 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
Err(e) => { Err(e) => {
return syn::Error::new( return syn::Error::new(
Span::call_site(), Span::call_site(),
format!( format!("Could not read sabry configuration required by this macro: {e:?}",),
"Could not read sabry configuration required by this macro: {:?}",
e
),
) )
.to_compile_error() .to_compile_error()
} }
@ -63,7 +60,7 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
Err(e) => { Err(e) => {
return syn::Error::new( return syn::Error::new(
Span::call_site(), Span::call_site(),
format!("Could not generate scope: {:?}", e), format!("Could not generate scope: {e:?}"),
) )
.to_compile_error() .to_compile_error()
} }
@ -127,7 +124,7 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
}) })
}) })
.map(|(name, ident, html, css)| { .map(|(name, ident, html, css)| {
let docs = format!("'{:?}' {}. CSS selector '{}'", ident, name, css); let docs = format!("'{ident:?}' {name}. CSS selector '{css}'");
quote! { quote! {
#[doc = #docs] #[doc = #docs]
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
@ -142,33 +139,44 @@ pub fn styly_macro_impl(input: TokenStream, source_path: Option<PathBuf>) -> Tok
let wrp_docs = format!("wrapper class for '{}' scope. If you have any tagname selectors - they should live as children of element with this class applied.", &scope_ident); let wrp_docs = format!("wrapper class for '{}' scope. If you have any tagname selectors - they should live as children of element with this class applied.", &scope_ident);
if constant { if constant {
let compiler = CompilerAdapter::new(config.clone()); cfg_if! {
let css = match compiler.compile_module(ms.syntax.0, &scope.hashed_code) { if #[cfg(feature = "const-compile")] {
Ok(c) => c, use sabry_intrnl::compiler::CompilerAdapter;
Err(e) => { let compiler = CompilerAdapter::new(config.clone());
return syn::Error::new( let css = match compiler.compile_module(ms.syntax.0, &scope.hashed_code) {
ms.code.span, Ok(c) => c,
format!("Could not compile const inline: {e:?}"), Err(e) => {
return syn::Error::new(
ms.code.span,
format!("Could not compile const inline: {e:?}"),
)
.to_compile_error()
}
};
let const_docs = format!("The compiled CSS style for {} scope", &scope_ident);
let const_wrapper_ident = syn::parse_str::<Ident>(
format!("{}_CSS", &scope_ident.to_string().to_uppercase()).as_str(),
) )
.to_compile_error() .expect("BUG: We just converted valid Ident to string and uppercased");
}
};
let const_docs = format!("The compiled CSS style for {} scope", &scope_ident); quote! {
let const_wrapper_ident = syn::parse_str::<Ident>( #[doc = #const_docs]
format!("{}_CSS", &scope_ident.to_string().to_uppercase()).as_str(), #scope_vis const #const_wrapper_ident: &str = #css ;
) #[doc = #wrp_docs]
.expect("BUG: We just converted valid Ident to string and uppercased"); #scope_vis const #scope_wrapper_ident : &str = #scope_hash ;
#[doc = #mod_docs]
quote! { #scope_vis mod #scope_ident {
#[doc = #const_docs] #(#scope_members)*
#scope_vis const #const_wrapper_ident: &str = #css ; #(#special_nesting_members)*
#[doc = #wrp_docs] }
#scope_vis const #scope_wrapper_ident : &str = #scope_hash ; }
#[doc = #mod_docs] } else {
#scope_vis mod #scope_ident { return syn::Error::new(
#(#scope_members)* Span::call_site(),
#(#special_nesting_members)* "`styly!(const scope...` requires `experimental-const-scoping` feature flag",
)
.to_compile_error();
} }
} }
} else { } else {