Closed chinedufn closed 6 years ago
#[macro_use]
extern crate virtual_dom_rs;
extern crate inline-stylesheet;
use inline-stylesheet:css;
css! { SOME_COMPONENT_CSS, "
:host {
background-color: red;
}
"}
fn main () {
let html = html! { <div class=*SOME_COMPONENT_CSS, id="some-id"> </div> };
println!("{}", html);
// <div class='98429893' id='some-id'></div>
}
The css!
procedural macro would
css! { ... }
with static SOME_COMPONENT_CSS: &'static str = "_98429893";
where 98429893
is the hash of the CSS string.INLINE_STYLESHEET: ._98429893 { ... css here ... }
to stdoutThe procedural macro would probably get made by taking this example as a starting point and fiddling around.. - https://github.com/dtolnay/syn/blob/master/examples/lazy-static/lazy-static/src/lib.rs
So then you'd
INLINE_STYLESHEET=1 cargo build | inline-stylesheet > output.css
Where inline-stylesheet
was a CLI that parsed out your styles from the cargo build
output (since our procedural macro was calling println!(...)
to write your styles to stdout)
and you'd have the stylesheet that looked like
/* output.css */
._98429893 {
/* ... */
}
I wonder if we could assign these values inline; I reckon it might be slightly more intuitive. Also Rust should already support defining static
variables at the call site, which is neat for locality :D (e.g. variables that are used together, are assigned in similar parts of the code).
static SOME_COMPONENT_CSS = css! {"
:host {
background-color: red;
}
"}
In a recent project I played around with sheetify, a JavaScript browserify transform that lets you write CSS write next to your view components... and I absolutely loved it.
Currently Sheetify's class names are created by using the hash of the content. I think that's a mistake we ended up making. Whenever you change a bit of content in a CSS block, it would cause a chain-reaction of recompilation throughout the pipeline. Instead I think using a global, incrementing counter (or similar) for the class names would end up being much more reliable.
Perhaps not an initial concern, but something to think about: I wonder if there's a side channel we could hook into to access the generated CSS, perhaps even through some global mechanism.
For Sheetify we ended up writing css-extract which was really useful because it allowed us to hash it (for content-integrity
headers), and inline it (using inline-critical-css).
These are probably things that would be needed for any non-trivial application, so it's probably worth thinking about at some point :D
Instead I think using a global, incrementing counter (or similar) for the class names would end up being much more reliable.
Awesome - I'll start there!
Perhaps not an initial concern, but something to think about: I wonder if there's a side channel we could hook into to access the generated CSS, perhaps even through some global mechanism.
I think we'd allow you to
INLINE_STYLESHEET=1 cargo build | inline-stylesheet > output.css
So by setting an environment variable you instruct the procedural macro to write all CSS out to stdout and we have a simple program inline-stylesheet
to grab the CSS output (and ignore the cargo build output) and write it all to a file.
Or am I missing what you're saying?
@yoshuawuyts your feedback here is already INCREDIBLY helpful! Knowing the why behind some of sheetify's decisions will certainly save time here!
Or am I missing what you're saying?
If you want to get perfect HTTP/2 PUSH support for stylesheets, you need to know which CSS was inlined for what route. It'd be really useful to have that information too somehow; but perhaps it's not possible.
The alternative is to create CSS diffs on the fly; but that requires runtime analysis which means there's a bit of overhead involved.
Another thing that we do with Sheetify which I haven't seen brought up here before is being able to reuse styles through a package manager. In Sheetify we have 2 approaches for this:
sheetify(require('./styles.js')) // import a string that's defined in JS
sheetify('./styles.css') // import a CSS file (so-so about this; haven't used it a lot)
sheetify('tachyons') // Get the `tachyons` module from npm. This turned out to be *super* useful
I've never heard of HTTP/2 PUSH until your comment, so if anyone else is in the same boat this article helped me!
Ok gotcha I think I have a better sense of what you're hitting on now!
you need to know which CSS was inlined for what route. It'd be really useful to have that information too somehow; but perhaps it's not possible.
Ah interesting! Hmmm I bet now that that's in our heads from day one we can structure our early approaches to not prevent us from being able to get much smarter in the future... maybe.. hopefully
i.e. structure things so that it'll be possible to do some static analysis on this in the future.. or something.. Cool stuff!
Another thing that we do with Sheetify which I haven't seen brought up here before is being able to reuse styles through a package manager.
Yup I hadn't thought about that at all so thanks for bringing that up. Any early thoughts for how that could / should look in Rust? Same as how sheetify handled things or is there a v2.0 of imported styles that you have in mind?
And in terms of HTTP2 PUSHing only above the fold styling... I wonder if we could allow people to just manually specify the above the fold cut off (would be different for different devices of course so I guess they'd just opt for a reasonable desktop cutoff).
I haven't thought about any of this NEARLY as long as you have so apologies for any ignorance or just plain wrong things that I've said!
I think step 1 will still be just getting a basic macro in place that works and feels ergonomic and then over time we can add in awesomeness / optimizations / etc.. But it's still super helpful to paint the picture of what this could look like so thank you for sharing!!
Mmm I'm noticing a mistake that I made.
When I said "inline stylesheets" I really meant "a macro for writing CSS next to my source code".
I was misusing the word "inline" - forgetting that it also (and more commonly..?) means "served with your HTML"..
I'm almost done with a crate in the workspace that does that .. the name was inline_stylesheet buuuuut I can see that causing confusion so will rename...
Maybe a name that communicates that you can write your css in rust..
I mean there's already the unfortunately named virtual_dom_rs
so... css_rs
might do here .. :shrug:
To make the final output of the CSS file even smaller it would be nice to convert the CSS into "atomic" CSS. So for every CSS declaration you're turning that into a class name e.g.
:host {
width: 4rem;
height: 1rem;
background-color: black;
}
Turns into:
.a {
width: 4rem;
}
.b {
height: 1rem;
}
.c {
background-color: black;
}
For a bigger project with a lot of CSS this would help immensely.
@deamme thanks a lot for the resources just took a look at all of them!
Curious if you already have a sense of how:
:host:hover > div {
display: none;
}
Might look with atomic css?
@chinedufn I don't see any obvious good solutions. I would either just let it be like you normally would or perhaps look into the descendants of :host
and apply the generated atomic class name of display: none
Hmm interesting. Yeah we’d have to explore the edge cases a bit but if we could get a clean solution in place that was objectively better that could be sweet!
But even if atomic CSS with no material trade offs was impossible (I have no idea haven’t thought about this much) we could still point people towards another crate that supported atomic CSS (maybe in another repo?) since a big goal behind Percy is being able to swap out what you don’t want for other implementations.
Just some off the cuff thoughts!
Alright so I think that #11 introduced the core of this and we can explore optimizations / improvements one by one in their own issues / PRs. So closing this one!
In a recent project I played around with sheetify, a JavaScript browserify transform that lets you write CSS write next to your view components... and I absolutely loved it.
A big win for me was not needing to think about class names. If I had a
some-component-view.js
I just made my CSS block for that filesomeComponentCSS
and called it a day. With a code generator this meant zero thinking about where to put or how to name styles.Another big win was not needing to think about where / how to organize styles. When the style is just next to the component there is no "what CSS file to I put this in?" question to answer.
Of course there are downsides to inline stylesheets. The first that comes to mind is that it is much less popular than sass/css so less people know it so it's non-standard.
But, support for inline stylesheets in Percy would be completely optional. If you don't want them you can still just do
With that background out of the way... here's how I think we can do inline stylesheets in Rust: