From 230d7899db1210464cf4fa190b7177ee5c8f5952 Mon Sep 17 00:00:00 2001 From: Yiffy Dev Date: Thu, 31 Oct 2024 08:55:00 +0500 Subject: [PATCH] introduce lepty-scoping, which is more effective and more nice - especially with leptos --- .github/workflows/cbt.yml | 3 +- Cargo.lock | 1 + Cargo.toml | 3 + README.md | 69 +++++++++++++++++++++ examples/leptos-axum/Cargo.toml | 9 +-- examples/leptos-axum/src/routes/home.rs | 26 +++----- examples/leptos-axum/src/routes/home.scss | 11 ++++ sabry_intrnl/Cargo.toml | 2 + sabry_intrnl/src/scoper/mod.rs | 73 +++++++++++++++++++---- 9 files changed, 162 insertions(+), 35 deletions(-) diff --git a/.github/workflows/cbt.yml b/.github/workflows/cbt.yml index aed0fdd..b8773f0 100644 --- a/.github/workflows/cbt.yml +++ b/.github/workflows/cbt.yml @@ -6,7 +6,7 @@ on: pull_request: branches: ["window"] -env: +env: CARGO_TERM_COLOR: always jobs: @@ -22,4 +22,5 @@ jobs: - uses: actions/checkout@v4 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - run: cargo build --verbose + - run: cargo test --verbose --all - run: cargo test --verbose --all --all-features diff --git a/Cargo.lock b/Cargo.lock index 91a3997..bd98667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1012,6 +1012,7 @@ version = "0.0.1-alpha.1" dependencies = [ "base64", "blake3", + "cfg-if", "const_format", "grass", "lightningcss", diff --git a/Cargo.toml b/Cargo.toml index d244cf6..1cc9c1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,9 @@ sabry_intrnl = {workspace = true} [features] default = ["procmacro"] +lepty-scoping = [ + "sabry_intrnl/lepty-scoping" +] procmacro = [ "dep:sabry_procmacro" ] diff --git a/README.md b/README.md index ea37bb7..34f0d86 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,73 @@ fn render() -> HtmlElement { ``` So the `mixins!` macro we just passed to the `usey!` macro inside of `buildy` function call is now accessible with simple and natural `@use "mixins"` SASS rule! +### Leptos specials + +With [leptos](https://github.com/leptos-rs/leptos) framework you can do the trick to apply some class to most of HtmlElements for the entire component/island: + +```rust,ignore +#[component] +fn component() -> impl IntoView { + view! {class="cls1", /* <- here */ +

"Head"

+

"text"

+ } +} +``` +... and have "cls1" on both `h1` and `p` auto-assigned. Isn't this cool? + +So, if you're on leptos, and don't mind to take this approach, I'd highly recommend turning the *lepty-scoping* feature on: + +```toml +# Cargo.toml + +[dependencies] +sabry = {version = "0.0.1", features = ["lepty-scoping"]} + +[build-dependencies] +sabry = {version = "0.0.1", features = ["build", "lepty-scoping"]} +``` + +```rust,ignore +styly!(scope { + @use "tokens"; + h1 { + @include tokens.sectionhead(); + img { + @include tokens.sectionimg(); + } + } + .breadcumbs { + @include tokens.badgelist(); + &__item { + @include tokens.badge(secondary); + } + } +}); + +#[component] +fn component() -> impl IntoView { + view! {class=SCOPE, +

+ "Head" + +

+ + } +} +``` + +That will perform *much* better: + +- All scope members - like `scope::breadcumbs` - wont contain repeating scope hash, just the original class/id selector +- You will not encounter [the catch](#scoping) with nested tagname selectors + +Also, this isn't really exclusive leptos-supporting feature. It just changes scoping behavour. + ## Configuration Sabry configuration lives in `[package.metadata.sabry]` table of the manifest file. @@ -535,6 +602,8 @@ Different selector types are scoped differently: > > Also if there are nested tagname selectors - sabry won't take this into account and you'll > have to wrap them with `SCOPE` class as well +> +> **However** If you are able to apply some class to all html elements you have - like `view!{class=CLASS...}` with Leptos - you could use *lepty-scoping* feature flag for sabry and get rid of this catch! See more [here](#leptos-specials). As for **SASS parent selectors**: they are currently handled in different way. Instead of walking up the syntax tree sabry just creates function member for the scope and leave the rest to grass: diff --git a/examples/leptos-axum/Cargo.toml b/examples/leptos-axum/Cargo.toml index 1b30dbb..39e1e43 100644 --- a/examples/leptos-axum/Cargo.toml +++ b/examples/leptos-axum/Cargo.toml @@ -23,11 +23,11 @@ tracing = { version = "0.1", optional = true } http = "1" # sabry with sass -sabry = {path = "../../"} +sabry = {path = "../../", features = ["lepty-scoping"]} [build-dependencies] # sabry for build with sass -sabry = {path = "../../", features = ["build"]} +sabry = {path = "../../", features = ["build", "lepty-scoping"]} # lets depend on our styles define-styles = {path = "../define-styles"} @@ -51,11 +51,12 @@ css.minify = true css.bundle = "target/.sabry/target/bundle.css" [package.metadata.leptos] +# Sabry-specific quality of life config +style-file = "target/.sabry/target/bundle.css" + output-name = "leptos-axum" site-root = "target/site" site-pkg-dir = "pkg" -# Sabry-generated bundle for leptos -style-file = "target/.sabry/target/bundle.css" assets-dir = "public" site-addr = "127.0.0.1:3000" reload-port = 3001 diff --git a/examples/leptos-axum/src/routes/home.rs b/examples/leptos-axum/src/routes/home.rs index 0e06caf..1c006db 100644 --- a/examples/leptos-axum/src/routes/home.rs +++ b/examples/leptos-axum/src/routes/home.rs @@ -1,18 +1,7 @@ use leptos::prelude::*; use sabry::styly; -styly!(s "src/routes/home.scss"); - -styly!(s2 { - @use "theme"; - .card { - @include theme.surface(secondary); - } - .pocwarn { - @include theme.surface(secondary); - text-transform: uppercase; - } -}); +styly!(style "src/routes/home.scss"); /// Renders the home page of your application. #[component] @@ -21,13 +10,14 @@ pub fn Route() -> impl IntoView { let count = RwSignal::new(0); let on_click = move |_| *count.write() += 1; - view! { -
+ view! {class = STYLE, +

"Welcome to Leptos!"

- - "DISCLAIMER:" -

"This isn't a design example! Its just proof of sabry's capabilities."

-

"Plz dont make websites/apps which look like this page :D"

+ + + "DISCLAIMER:" +

"This isn't a design example! Its just proof of sabry's capabilities."

+

"Plz dont make websites/apps which look like this page :D"

} } diff --git a/examples/leptos-axum/src/routes/home.scss b/examples/leptos-axum/src/routes/home.scss index 67e7ec1..d9ef063 100644 --- a/examples/leptos-axum/src/routes/home.scss +++ b/examples/leptos-axum/src/routes/home.scss @@ -4,9 +4,20 @@ .btn { @include tokens.clickable(accent); + &-dark { + @include tokens.clickable(secondary, 0.8, 1.1); + } } .table { @include utils.flex(column, center, center); @include theme.surface(primary); } + +.card { + @include theme.surface(secondary); +} +.pocwarn { + @include theme.surface(secondary); + text-transform: uppercase; +} diff --git a/sabry_intrnl/Cargo.toml b/sabry_intrnl/Cargo.toml index 19d7f51..41c5cbc 100644 --- a/sabry_intrnl/Cargo.toml +++ b/sabry_intrnl/Cargo.toml @@ -23,5 +23,7 @@ const_format = {version = "^0.2"} thiserror = {version = "^1.0"} serde = {version = "^1.0", features = ["derive"]} toml = {version = "^0.8"} +cfg-if = {version = "^1.0"} [features] +lepty-scoping = [] diff --git a/sabry_intrnl/src/scoper/mod.rs b/sabry_intrnl/src/scoper/mod.rs index 658e8f0..0d82a7d 100644 --- a/sabry_intrnl/src/scoper/mod.rs +++ b/sabry_intrnl/src/scoper/mod.rs @@ -1,3 +1,4 @@ +use cfg_if::cfg_if; use hash::ScopeHash; use raffia::Spanned; use regex::Regex; @@ -187,10 +188,22 @@ impl HashedSelector { // As we dont want to use two HTML props for a single ID, // ID scoping is done with id modification ScopedSelector::Id(a) => { - format!("{}-{}", hash.as_str(), a.ident) + cfg_if! { + if #[cfg(feature = "lepty-scoping")] { + format!("{}.{}", a.ident, hash.as_str()) + } else { + format!("{}-{}", hash.as_str(), a.ident) + } + } } ScopedSelector::Tag(a) => { - format!(".{} {}", hash.as_str(), a.ident) + cfg_if! { + if #[cfg(feature = "lepty-scoping")] { + format!("{}.{}", a.ident, hash.as_str()) + } else { + format!(".{} {}", hash.as_str(), a.ident) + } + } } } } @@ -205,10 +218,26 @@ impl HashedSelector { pub fn make_hashed_html(value: &ScopedSelector, hash: &ScopeHash) -> Option { match value { // Class scoping is done with class composition - ScopedSelector::Class(a) => Some(format!("{} {}", hash.as_str(), a.ident)), + ScopedSelector::Class(a) => { + cfg_if! { + if #[cfg(feature = "lepty-scoping")] { + Some(a.ident.to_string()) + } else { + Some(format!("{} {}", hash.as_str(), a.ident)) + } + } + }, // As we dont want to use two HTML props for a single ID, // ID scoping is done with id modification - ScopedSelector::Id(a) => Some(format!("{}-{}", hash.as_str(), a.ident)), + ScopedSelector::Id(a) => { + cfg_if! { + if #[cfg(feature = "lepty-scoping")] { + Some(a.ident.to_string()) + } else { + Some(format!("{}-{}", hash.as_str(), a.ident)) + } + } + }, ScopedSelector::Tag(_) => None, } } @@ -330,6 +359,7 @@ pub fn apply_basic_rusty_member_gen_rules(source: &str) -> String { mod test { use std::collections::HashSet; + use cfg_if::cfg_if; use syn::Ident; use crate::{ @@ -345,14 +375,33 @@ mod test { ".cls1{color:red; &-dark{color: black;} #id1 {color:green;} div {color:blue;}} .cls3#id2{color: black;}"; let hash = ScopeHash::test_init("F2kf8nMs".into()); - let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}"; - let expect_selector_htmls = HashSet::from([ - "F2kf8nMs cls1".to_string(), - "F2kf8nMs-id1".to_string(), - "F2kf8nMs cls3".to_string(), - "F2kf8nMs-id2".to_string(), - "".to_string(), // comes from `div` which has no html hashed code, still want to check presense - ]); + cfg_if! { + if #[cfg(feature = "lepty-scoping")]{ + let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #id1.F2kf8nMs {color:green;} div.F2kf8nMs {color:blue;}} .F2kf8nMs.cls3#id2.F2kf8nMs{color: black;}"; + } else { + let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}"; + } + } + + cfg_if! { + if #[cfg(feature = "lepty-scoping")] { + let expect_selector_htmls = HashSet::from([ + "cls1".to_string(), + "id1".to_string(), + "cls3".to_string(), + "id2".to_string(), + "".to_string(), // comes from `div` which has no html hashed code, still want to check presense + ]); + } else { + let expect_selector_htmls = HashSet::from([ + "F2kf8nMs cls1".to_string(), + "F2kf8nMs-id1".to_string(), + "F2kf8nMs cls3".to_string(), + "F2kf8nMs-id2".to_string(), + "".to_string(), // comes from `div` which has no html hashed code, still want to check presense + ]); + } + } let scope = ArbitraryScope::from_source( raffia::Syntax::Scss,