futursolo / stylist-rs

A CSS-in-Rust styling solution for WebAssembly Applications
https://crates.io/crates/stylist
MIT License
366 stars 22 forks source link

Feature request: css modules, or an equivalent mechanism #106

Closed Iceburgino closed 1 year ago

Iceburgino commented 1 year ago

It's not even about modules themselves (as files) - it's about the fact that each class name gets mapped to a unique class name, and that makes the style local. It would be great to have something like local_css! macro that takes String as an argument and returns a Map, which you can then use. Like this

let some_style =  local_css! {
    "
    .some_class {
         property: value;
         property: value;
    }
    .some_other_class {
         property: value;
         property: value;
    }
    "
};

and then, in Yew:

html! {
    <div class={some_style["some_class"]}>
        {"Styled text"}
    </div>
    <div class={some_style["some_other_class"]}>
         {"Differently styled text"}
     </div>
}

I know there is modules-css, but having this functionality in this crate would make it feature-complete. Besides, I see you have a css! macro which functions similarly to what I described. I don't know the actual complexity of implementing this, but I hope it's easy enough for you to consider.

futursolo commented 1 year ago

Thank you for the feature request.

I think this is already possible with the following code:

let (some_class, some_other_class) = (
     css! {
         property: value;
         property: value;
    },
    css! {
         property: value;
         property: value;
    }
);
Iceburgino commented 1 year ago

@futursolo

Yes, but that still doesn't provide you with the feature of separation of files you would have if you could name your classes yourself, and the macro could replace them with the generated class names for localizing the scope of styling. Like it would in react:

import styles from './some_style.module.css';

function SomeComponent(props) {
   return <div className={styles.some_class}/>
}

In this case, some_style.module.css is a pure css file that contains a style for .some_class. In the DOM, styles.some_class would be replaced with something-like some_class__SGq7F where the __SGq7F part would be added by react to make it local to the file it is being used in.

I've seen a similar thing with css! macro. Class names when using it look like

<button class="stylist-HuhJdxcD src/app.rs:17:15">Hello styled button</button>

I'm sure you can recreate something like this with include! or include_str! macro in Rust and use it in combination with css! (or potentially local_css!), but there would need to be a mechanism to recognize class names and turn them into ids.

futursolo commented 1 year ago

With stylist, You can assign prefixes with the following method:

let (some_class, some_other_class) = (
     Style::create("some-class", css! {
         property: value;
         property: value;
    }).unwrap(),
    Style::create("some-other-class", css! {
         property: value;
         property: value;
    }).unwrap()
);

You can make a macro_rules! wrapper if you think above is too verbose.

Iceburgino commented 1 year ago

As I've mentioned before, this does not handle the case where you want to separate your style, with multiple classes in it, into a file, and then selectively apply styles from this file to your component. You are required to put all your styles into Rust code. include! won't help here since there is no need to separate a style of a single class into a file.

futursolo commented 1 year ago

Stylist is a CSS-in-Rust solution where stylesheet should present alongside the component's definition. This is similar to Styled Components and / or Emotion for React.

The solution you are describing is a bundler integration that doesn't exist yet with Yew or Rust WebAssembly in general.

futursolo commented 1 year ago

Thank you for this proposal.

Whilst I feel it's possible to utilise the existing API to make it with procedural macros relatively easily by reusing the parser exposed by Stylist and emitting the result into a struct that exposes each top level individual class names as Style instances with fields, importing stylesheets from external files is out-of-scope for a CSS-in-Rust solution like Stylist.

I do not feel we should accept this into Stylist as a local_css! macro is less than ideal when processing inline stylesheets.

Assigning local class names shares the same philosophy as the now defunct makeStyles / useStyles API from Material UI v4 (now known as MUI). IMHO, one of the reasons why this API failed to gain popularity is because it forced users to give additional class names where users can simply make more styles with makeStyles. So users started naming their classes root, container, which is not very useful in general and resulted in less than ideal experience with CSS-in-JS when comparing with styled components / emotion.

If you would like to make this proposal as a different crate and is interested in using stylist for stylesheet parsing / processing, feel free to do so and I am happy to provide support with it. As I also do not want see Stylist to be the only maintained css solution for Yew (the other one is global css files).

WorldSEnder commented 1 year ago
import styles from './some_style.module.css';

function SomeComponent(props) {
   return <div className={styles.some_class}/>
}

This kind of processing is not feasible in rust proc macros, to my knowledge. Reading and modifying the contents of a file referred to by path is hard to do correctly in proc macros (i.e. rebuilding in watch mode and incremental caching should work without a separate build script). As such, I can't identify an approach where we could work with external file contents instead of string literals. Hope that changes in the future, but afaik, it cant be currently done.