-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement $css rune #127
base: svelte5
Are you sure you want to change the base?
implement $css rune #127
Conversation
I created this for my own projects, but wanted to contribute it back so you can include it if you like it. Feel free to give me Feedback, I am willing to modify this. |
This also solves issue #80 but in another way.
Or
|
@JanNitschke, This feature is having for sure some advantages such as:
However, the Even though To avoid that, I'm wondering if we could have it imported from the package but with a different convention (not starting by // svelte-preprocess-cssmodules package
export const css = (str) => {}; // Svelte component
import { css } from 'svelte-preprocess-cssmodules';
const color = css('red'); or with a more "special" syntax // svelte-preprocess-cssmodules package
export const style$ = (str) => {}; // Svelte component
import { style$ } from 'svelte-preprocess-cssmodules';
const color = style$('red'); On the other hand, it might be annoying to import the function on every component. So we could find a way to make the type global (when using typescript) |
@micantoine Thank you for your kind words! I created a standalone version of this where I ran into the issues you mentioned. To make it compatible with typescript I added a global declaration to the import: You then have to add it to the typescript environment: I haven't tried it without typescript yet, so I have no solutions for LSP warnings without TS. I also had a discussion with some of the svelte core team about the rune syntax on discord. One suggestion was parsing the import statements of the file, figuring out the imported name and then replacing that. But I'm not so sure that this is the right solution, as this makes it look like a normal function to the user. Another idea I had was using it with template string syntax, so it won't collide with a $css rune should svelte ever introduce one. I would love to hear your opinion about this! I will keep you updated should I find a better solution! |
Yes, those LSP warnings are really something I'm trying to avoid. It brings a negative experience when using the library. So in our case we would have to definitely rename
It is one of the way indeed. So to replicate what you did: Through global file (opt1)
Through json config (opt2)
{
"compilerOptions": {
"types": [ "svelte-preprocess-cssmodules" ],
}
} Ideally, I would like to stay await from configuration. The package is small and its use should be as smooth as possible. "No headaches", I plug in the preprocessor, and it's good to go! An alternative, would be the local import which is the most common pattern. "I include what I want to use". Local import import { style } from 'svelte-preprocess-cssmodules';
let color = style('red');
This is basically what I did in version 1 of the preprocessor. I was parsing and replacing data with the following pattern <p class="$style.red">My red text</p>
<style>
.red { color: red; }
~~~~~
</style> The idea for version 2, was to let the developer code regular html and css and the replacement would be done by simply adding <p class="red bold">My red text</p>
<style module>
.red { color: red; }
.bold { font-weight: 700 }
~~~~~
</style> The other dilemma was between the native css modules approach (scoping classes) and the svelte system (scoping all selectors). I personally like everything to be scoped, so I created the mixed mode (which was the only approach in v1) to set a unique classname and to avoid inheritance issues. <p class="red">My red text</p>
<style module="mixed">
.red { color: red; }
p { font-weight: 700 }
</style>
<!-- generating -->
<p class="red-3eertt svelte-21yer6">My red text</p>
<style>
.red-3eertt { color: red; }
p.svelte-21yer6 { font-weight: 700 }
</style> The native approach was set by default to follow the CSS Modules principles that developers are familiar with.
What import statements? not sure to understand, is it what I am doing with the When using an external import, all the css classes of the stylesheet are attached to the import's name and can be used for any situation (dynamic variable, component props, html attributes...) <script>
import style from './app.module.css';
import Children from 'Children.svelte';
let active = $state(true);
let color = $state(style.red);
let bgcolor = $derived(active ? style.back : style.white);
</script>
<div class={color} data-size={style.large}>
<Children class={style.small} background={bgcolor} />
</div> During the parsing I'm replacing the import line by an object <script>
const style = { red: 'red-12era', black: 'back-12era', white: 'white-12era', large: 'large-12era', small: 'small-12era' };
import Children from 'Children.svelte';
...
</script> That whole logic can actually be used with a placeholder variable. However, for an unused class, the "unused selector" warning will not be thrown since a dynamic value is being passed to the attribute. <script>
import { style$ } from 'svelte-preprocess-cssmodules';
let color = $state(style$.red);
</script>
<p class={color}>hello</p>
<style module>
.red { color: red; }
.blue { color: blue; } /** no warning */
small { font-size: 12px } /** warning */
~~~~~
</style> generating <script>
const style$ = { red: 'red-waf5', blue: 'blue-waf5' };
let color = $state(style$.red);
</script>
<p class={color}>hello</p>
<style>
.red-waf5 { color: red; }
.blue-waf5 { color: blue; }
</style> Depending on the parsing the blue data could be removed. The object is being creating reading the classes listed in Sum upOpt 1: "Magic" Function (Your runes alike)
<p class="{css$('red')}">hello</p>
<style module>
.red { color: red; }
</style> generating <p class="{'red-asd12'}">hello</p>
<style module>
.red-asd12 { color: red; }
</style> Opt 2: Import "magic" Function
<script>
import { css$ } from 'svelte-preprocess-cssmodules';
</script>
<p class="{css$('red')}">hello</p>
<style module>
.red { color: red; }
</style> generating <p class="{'red-asd12'}">hello</p>
<style module>
.red-asd12 { color: red; }
</style> Opt 3: "Magic" Variable
<p class="{style$.red}">hello</p>
<style module>
.red { color: red; }
</style> generating <p class="{'red-asd12'}">hello</p>
<style module>
.red-asd12 { color: red; }
</style> Opt 4: Import "Magic" Variable
<script>
import { style$ } from 'svelte-preprocess-cssmodules';
</script>
<p class="{style$.red}">hello</p>
<style module>
.red { color: red; }
</style> generating <p class="{'red-asd12'}">hello</p>
<style module>
.red-asd12 { color: red; }
</style> Opt 5: Import Placeholder Variable
<script>
import { style$ } from 'svelte-preprocess-cssmodules';
</script>
<p class="{style$.red}">hello</p>
<style module>
.red { color: red; }
</style> generating <script>
const style$ = { red: 'red-asd12' };
</script>
<p class="{style$.red}">hello</p>
<style module>
.red-asd12 { color: red; }
</style> Opt 6: Namespaced string (like v1)
<p class="$style.red">hello</p>
<style module>
.red { color: red; }
~~~~~
</style> generating <p class="red-asd12">hello</p>
<style module>
.red-asd12 { color: red; }
</style> ConclusionOne focus for the library was to follow svelte's philosophy of using simple html and css ( In most cases, it is enough, since classes can even be toggled on and off from the class attribute and also be passed to a child component. Now, there is some limitation where handling dynamic variable assignment is not possible unless the style is being imported from an external stylesheet Adding the option could definitely be useful and the developers could adopt the approach they prefers or even mix it up. My preference might lean toward the opt5 "placeholder variable". I find it easier to work with a variable and I could use all its advantages (eg: destructuring would not be possible with the opt3/4 "Magic variable"). A "Magic placeholder variable" could also be supported for the developers who wish to do the global typescript settings. What you're doing with your preprocessor is similar to the approach of verson 1 of the It could be revived in addition to one of the above options? <script>
let bgcolor = $state(style$.red);
</script>
<p class="{style$.white} {color} bold">hello</p>
<style>
.red { color: red; }
.white { color: white; }
.bold { font-weight: bold; }
p { font-size: 2rem };
</style>
generating <script>
const style$ = { red: 'red-qwe12', white: 'white-qwe12' };
let bgcolor = $state(style$.red);
</script>
<p class="{style$.white} {color} bold">hello</p>
<style>
:global(.red-qwe12) { color: red; }
:global(.white-qwe12) { color: white; }
.bold.svelte-asg42 { font-weight: bold; }
p.svelte-asg42 { font-size: 2rem };
</style> |
Adds a $css rune that allows to pass class names freely
What?
A rune that is replaced with the generated classname during compile.
Why
Decouple css modules from the class attribute. This rune allows to use css module classes with every attribute without global configuration.
I created this because I needed to pass multiple classes to a component and didn't want to add them all in the includeAttributes option. This rune feels like a very svelte 5 solution to me.
Example