✨ RFC: Lazy-loadable, type-safe, scalable style support #174
Replies: 41 comments
-
Will this be able to support:
|
Beta Was this translation helpful? Give feedback.
-
Good call @samijaber updated the spec: const hover = CSS$`color.hover: blue`
const hoverOL = CSS$({
'color.hover': blue
});
const smallScreen$ = MEDIA$`screen and (min-width: 480px)`;
const media = smallScreen$`background-color.hover: lightgreen`;
const mediaOL = smallScreen$({
'backgroundColor.hover': 'lightgreen';
}); |
Beta Was this translation helpful? Give feedback.
-
What about, along the lines of const hover$ = PSEUDO$`hover`;
const hover = hover$`color: blue`
const hoverOL = hover$({
color: 'blue'
});
const smallScreen$ = MEDIA$`screen and (min-width: 480px)`;
const media = smallScreen$(hover$`background-color: lightgreen`);
// not sure how you would combine 2 tagged templates, that part is a bit confusing to me
const mediaOL = smallScreen$(hover$({
'backgroundColor': 'lightgreen'
})); |
Beta Was this translation helpful? Give feedback.
-
ohh! I like that! What do you think of this: const hover = CSS$.hover`color: blue`
const hoverOL = CSS$.hover({
'color': blue
});
const smallScreen$ = MEDIA$`screen and (min-width: 480px)`;
const media = smallScreen$.hover`background-color: lightgreen`;
const mediaOL = smallScreen$.hover({
'backgroundColor': 'lightgreen';
}); |
Beta Was this translation helpful? Give feedback.
-
We need to account for pseudo-selectors that receive arguments... https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child My suggestion allows arbitrary strings for pseudo selectors, but yours makes things messier. How would we do |
Beta Was this translation helpful? Give feedback.
-
CSS$.hover.nthChild('2n+3')`color: blue` |
Beta Was this translation helpful? Give feedback.
-
I personally think adding typings to handle all of these could get...cumbersome to maintain. But up to you! Also, as I think about this even more...would be cool to support attribute selectors, class selectors, and all sorts of other selectors. It makes me wonder if we could have a /* hover pseudo selector */
const hover$ = SELECTOR$`:hover`;
/* <a> elements with a title attribute */
const href$ = SELECTOR$`a[title]`;
/* All <li> elements with class="spacious" */
const spaciousLi$ = SELECTOR$`li.spacious` But of course, the benefit of your approach is that it comes with types and autocomplete. Having both would be neat maybe? |
Beta Was this translation helpful? Give feedback.
-
This is cool! It feels like this could work with tailwind quite well. I wonder if there’s a way to integrate the 2, allowing us to import compiled CSS$ tailwind objects and use them to compose our styles as well. We can then stick with our UI frameworks and current theme and switching over would be a breeze. I imagine vite could compile class names such as import { px8, textWhite } from ‘virtual’
<div class={[px8, textWhite, etc...]}/> I would see this as beneficial as the qwik-react integration which would make adoption easier and give new projects a leap forward. is this something we could consider @mhevery ? |
Beta Was this translation helpful? Give feedback.
-
I have 2 main things:
and a small thing is: can we have a concrete example with CSS variables?:) |
Beta Was this translation helpful? Give feedback.
-
Will In vanilla-extract we can do // ****.css.ts
function generateStyle(config: LayoutConfig) {
// style object is generated here.
}
export const styleA = generateStyle({...});
export const styleB = generateStyle({...});
// Even array
export const styles = [generateStyle({...}), generateStyle({...})]; |
Beta Was this translation helpful? Give feedback.
-
[ Copying here from Discord ] Good write up @mhevery. Thoughts:
Below is a LONG thread (of 69 messages) worth scanning through containing conversation on the topic between several community members. There are many others messages out-of-band, repo Issues in VE, Emotion, and Stylis on the topic. Atomic units of style | CSS as a first-class citizen for CSS-in-JS | Locally-scoped at runtime |
Beta Was this translation helpful? Give feedback.
-
i belive that could be implemented in the user land so it should just work.
yes static code generation is the goal here
Probably not.
Right now no. What would be the purpose of that? Also I will add some CSS vars examples |
Beta Was this translation helpful? Give feedback.
-
As someone who was an active participant in that discussion that @n8sabes linked, here are some of my thoughts: My initial impression was that this shouldn't be a priority and if it is, it seems like a lot of effort for not a lot of gain. This impression is a result of numerous Qwik talks demonstrating (and perhaps somewhat implying?) that optimising CSS does not lead to a huge performance improvement. So I don't know for sure how this would work but if I also have some concerns about perceived DX improvements. As @n8sabes rightfully points out, being able to support all CSS properties via the string literal format is very much an expectation that CSS writers have. I believe this is quite important for adoption from those coming from existing CSS tooling like SASS/SCSS, Emotion, styled-components, CSS modules; and perhaps even Vue/Svelte(Kit) users who get to write co-located scoped vanilla CSS right at the component level. And speaking of co-located scoped CSS, those two frameworks have them in easily collapsible Considering how divided people are on Tailwind's verbosity, I doubt people will be using const styleName = CSS$`
property: value
` clogging up vertical real estate in their code editors, and worse, no easy way to collapse all of them together. Ironically, also causing everyone to think of names again. So when that happens, I'm sure all the variables will end up living in their own separate file to be used as design tokens to be imported and composed. At this point, I'd argue that it ends up being a worse version of vanilla-extract since it will require the user to learn additional syntax for CSS queries while not supporting complex selectors. The current spec also has maintenance problems. CSS has been on a trend of introducing new features such as There's already excellent CSS tooling or frameworks that help tame the CSS beast. Tailwind proudly boasts that most projects end up using less than ~10kb of CSS in their docs and while I do see JS bundle sizes trending upwards, I can't say the same for CSS when looking at Tailwind's growing popularity. Also, to add to the examples already provided, I recently chanced upon another one that could be interesting to watch: https://css.master.co/ I just want to say that I sincerely believe Qwik is pushing the web forward with some truly novel innovation, in fact, pushed it so hard that a once lurking hobbyist like me got so excited about the future of web dev that he's now actively participating in contributing to open source over at Qwik UI. If anything, my opinion is largely observation as I don't have professional experience in the industry but I hope I provided some useful feedback! |
Beta Was this translation helpful? Give feedback.
-
Thank you @KenAKAFrosty for providing the ChatGPT analysis. Below are excerpts of the summary for those whom do not wish to read the extensive dialog in just one of the threads on this topic —
The analysis covered many high-level points discussed in the thread, but much is out of band in VE, Emotion, or Stylis Issues. A few points:
See Discord for @KenAKAFrosty's ChatGPT analysis. |
Beta Was this translation helpful? Give feedback.
-
Here is an I was looking back at my old code from 2020 that scopes everything, but it's highly customized for Example 2 in the code below is just an idea for a NOTE: I believe I used emotion js-object structures and its import { $, component$, useId } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';
import { compile, serialize, stringify } from 'stylis'
export default component$(() => {
const componentId = useId();
const experiment1 = $(() => {
// debugger;
const cssString = `@keyframes fadeIn { 0% {opacity:0;} 100% {opacity:1;} } .oBox{ opacity: 1; box-sizing: border-box; animation: fadeIn 5s;}`;
const compiled = compile(cssString)
console.log(compiled);
const serialized = serialize(compiled, stringify)
console.log(serialized);
});
const experiment2 = $(() => {
// debugger;
// 🤔 Maybe floating styles not contained within a class could be used as a default for style
const cssString = `@keyframes fadeIn { 0% {opacity:0;} 100% {opacity:1;} } .oBox{ color: red;} opacity: 1; box-sizing: border-box; animation: fadeIn 5s;`;
const compiled = compile(cssString)
console.log(compiled);
const serialized = serialize(compiled, stringify)
console.log(serialized);
});
return <>
<div>Stylis Experiments -</div>
<div>
<button onClick$={() => { experiment1() }}>Experiment 1</button>
</div>
<div>
<button onClick$={() => { experiment2() }}>Experiment 2</button>
</div>
</>
});
export const head: DocumentHead = {
title: 'Syle Experiments',
meta: [
{ name: 'description', content: "Syle Experiments" },
],
}; |
Beta Was this translation helpful? Give feedback.
-
Interesting tidbits around atomic CSS:
CSS$:
styling libraries:
|
Beta Was this translation helpful? Give feedback.
-
@wmertens 100% agree, devs need an escape hatch. You're also right about single character class names causing clashes with multiple independent projects on the page. Gotta use hashing to keep the class representations predictable across independent sources in order to support scenarios akin to module federation or pre-bundled artifacts. Hadn't thought about using unicode in the classname, interesting but sounds painful to debug. |
Beta Was this translation helpful? Give feedback.
-
Hi, My question is: Is there is any ETA when this feature would be done? I mean, are we talking in months, year, 2 years? And possibly what can we do to push this feature forward in roadmap priorities? Thanks ! |
Beta Was this translation helpful? Give feedback.
-
Hey @Roman-Simik Recently we've been looking a lot into Panda CSS, @manucorporat even created a vite plugin to make sure we can integrate with it. From all of the solutions, it looks like the one who provides the combination of all the benefits we discussed. I imagine we'll have something more ready in terms of usage and examples in the near future |
Beta Was this translation helpful? Give feedback.
-
Hi,
|
Beta Was this translation helpful? Give feedback.
-
@Roman-Simik the CSS landscape for Qwik is still settling down. Overview:
At a higher level, there's no full component libraries for Qwik yet (that I know of) so you would either make your own or use a JS-less UI like daisyui. https://qwikui.com/ is still under heavy development. |
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
@mhevery The link to StyleX in your original post should probably go to the official Facebook/Meta StyleX library (yet unreleased, but they want feedback on their docs site), since I presume you meant Facebook's Atomic CSS called StyleX, which you also linked to. |
Beta Was this translation helpful? Give feedback.
-
If you're using Vanilla Extract, I ported some helpers I used with React. |
Beta Was this translation helpful? Give feedback.
-
There is a small use case that is only supported by
Browsers that do not understand the value (the |
Beta Was this translation helpful? Give feedback.
-
@fabb I believe most css convertors support passing an array for multiple values of the same prop |
Beta Was this translation helpful? Give feedback.
-
In panda css not, because there array syntax is already used for responsive values: chakra-ui/panda#1109 |
Beta Was this translation helpful? Give feedback.
-
Does this mean css cannot be dependent on props? Or do we just need to use conditionals to pick between different css depending on a prop? e.g.
|
Beta Was this translation helpful? Give feedback.
-
We moved this issue to |
Beta Was this translation helpful? Give feedback.
-
RFC: Lazy-loadable, type-safe, scalable style support
[ HackMD | #2767 ]
Goal
Have an ergonomic way of styling components where the styles are:
Prior Art
A quick overview of existing styling solutions and their pros-cons.
Legend:
I think this is part of the reason why CSS-in-JS, and tailwind are so popular, because they don't derail the developer flow. The developer styles the element and moves on without having to make any decisions.
component$
which shares parent component styling scope qwik#2726; Scope styling anchor elements rendered from <Link> qwik#2071; Allow "scoped" styles to be inherited by Qwik Components qwik#1710; and https://github.com/BuilderIO/qwik/discussions/1063.Influence
There is an interesting article on Atomic CSS called StyleX This points out that as applications get very large the need for additional CSS reaches zero growth. This is because the styling is broken up into primitives which are then reused. We think this is a good approach for Qwik as well.
Proposal
The basic idea is to create a
CSS$
tagged string literal which can be used as:Both of the above examples are equally supported and are identical. Advantages between the two approaches are:
redBorderFromString
: devs can cut&paste from the dev-tools. Downside is that to get code completion they have to install an editor plugin.redBorderFromObjLiteral
: TypeScript can verify types, but cut&paste would not work from dev-tools/existing CSS.Pseudo Selectors and Media Queries
Return value
The return value of
CSS$
is an opaque object which can contain 1 or more classes along with the associated QRLs. The returned values can be composed together in markup.Transformation
As you can see the
CSS$
ends with$
which means it is subject to optimizer and lazy-loading.Will be transformed to:
file:
HASH_OF_JS
NOTE: exact implementation to be determined and may be different.
Runtime & SSR
The Qwik runtime will be able to recognize the strings which are QRLs and will know to load a specific JS files and create
<style>
tags from those JS files.The SSR will insert the
<style>
tags into the corresponding SSR output.Because the styles are just JS loaded through QRLs, existing prefetching and bundling system will be able to optimize the loading of the styles.
The runtime can easily see which styles have already been loaded and which still need to be loaded.
No need for
useStyle$()
With the
CSS$
approach there is no need foruseStyle$()
to load the styles. The renderer is now intelligent enough to recognize when a QRL is being passed into theclass
and if it needs to be loaded. Because the rendering can delay flushing of the UI to DOM, the renderer can load the CSS without causing a flash of unstyled content.Constraints
The
CSS$
will be able to refer to static content only. So things like this will not be supported and will be a compiled error.:If the CSS needs to have variable, than CSS variables should be used.
Thoughts on CSS
CSS selectors can have complex rules such as
body>ul>li
. We think such rules are very hard to reason about and make the CSS append only as devs are worried that changing them will break something. Such rules are also hard to tree-shake for.We think for styling components such rules are an anti-pattern and will not be supported by the
CSS$
which has one-to-one connection.Instead if you want to use such complex rules,
global.css
is a good place to put them, but you lose the ability to lazy load such rules.Advantages
CSS$
can be composed together or grouped into arrays and referred to by other JSX.Beta Was this translation helpful? Give feedback.
All reactions