Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Styletron – Virtual CSS (ryantsao.com)
109 points by rtsao on Dec 8, 2016 | hide | past | favorite | 32 comments

This isn't specifically to do with Styletron, but I'm wondering: what is the use case for building arbitrary CSS from JavaScript? I've never written an app where this seemed necessary; the most I've ever needed is some semantic classes to apply, like this:

    .request-pending {background-color: $yellow;}
    .request-acknowledged {background-color: $green;}
It seems like mixing the actual styles in with the JS violates separation of concerns and clutters up the actual logic.

There are a few things that I think could help put it in perspective:

* For one people using React already have there "html" (jsx) in their javascript. This is not necessarily a bad thing.

* Many people use React with a library called redux, which is a very simple state-container. They then break up their application into components and containers. The data in the redux store is one giant tree that contains the entire application's state. Sections of the state are connected to the containers, which have various components inside of them. These components are generally just a function of the properties that they are initialized with. All the business logic happens in redux, and all the presentational logic happens in the components, with the containers in the middle passing the data.

* Components are meant to be isolated, composable, reusable, and easily testable. As a result people began using css-modules so that the names of the style classes could be isolated per component and not pollute the global namespace. This is enough for most projects.

* CSS in JS is particularly useful when you are building a library of components that are meant to imported individually and/or used together. When building a library with the following goals:

1. Have a lot of quality components that are deeply customizable view various properties without having to directly override the style, but still allow styles to be overridden

2. Isolate the components and their styles from each other so that you don't have to import the entire library to use 1 component.

3. Make it easy to import and use them in your project.

CSS Modules would fit, except that they make importing the components much more difficult for the end user. They basically would need a webpack config and loader to import the styles. That or they would have to link the css on their own.

As a result you had libraries like Material-UI use inline styles for the entire library. This has its own disadvantages. Not all CSS properties are available inline, computing the styles on every render can be slower than having normal CSS, and it looks kind of sloppy when you see the end result. As a result people are creating or improving alternatives to inline styles such as Glamor, JSS, Styletron, and aphrodite.

Personally, I use styled-components. It's really great so far.


I'm just using CSS Modules myself. That combined with CSS variables using root{--var} is everything I need for my project. I have the luxury of targeting just electron for this one and having a greenfield project like this is great.

I want the CSS coupled with my React component and I want to use JavaScript to calculate style properties depending on the props. I don't want to bother with naming classes or CSS scope.


I also don't want to bother with switching languages just to color a border or center align some text. Or have static styles in one place/language and dynamic styles elsewhere despite both setting up the exact same component. And when working as a designer I like seeing the style code literally right there along with the component nesting code, data handling logic, etc. (In my personal opinion, the real benefit of this comes when designing using an inline workflow similar to jsx-style, but that's an argument for among converts!)

From my perspective anyway, generating CSS from JS is just to get these benefits and more without various performance problems. It's a practical compromise given the reality of how browsers work in 2016 and our current tooling. It's treating CSS as something like a compilation target (because it's a standard which happens to already exist) rather than writing the "assembly code" by hand. If browsers provided other ways of interacting with their internal style structs or whatever like setting default styles through json or a special js file then a different solution might make sense---perhaps even one which gives whole new powers!

It makes no sense for 99.9% of the apps out there. If you are running at Facebook or Google level, maaaybe. This whole madness started with a FB dev presentation about running CSS "at scale". Then folks started to copy each other and this is what we have now.

Just look at https://github.com/rtsao/styletron there's a whole "how to use it" page, but zero info WHY to use it. Not even a section about valid use cases.

IMHO SASS covers almost all cases of CSS "at scale".

> It seems like mixing the actual styles in with the JS violates separation of concerns and clutters up the actual logic.

It does not just seem like it... it does violate it for no good reason.

I think the argument is that separation of UI components is a more true "separation of concerns" than separating CSS, JS, and HTML.

The implementation of a UI widget is ultimately a combination of CSS, HTML, and JS which are coupled to some degree, so by colocating them together, the component is easier to manage. But to do this effectively, you need some way to get around the global nature of CSS. CSS modules and CSS-in-JS are means of achieving this.

A good blog post about this topic is: https://medium.com/seek-developers/the-end-of-global-css-90d...

> the component is easier to manage

But you can still keep CSS in a CSS file inside a module folder. SASS or any other CSS build took could grab it from there.

> get around the global nature of CSS

That can be done with proper scoping supported naturally by CSS.

Local CSS is still not as bad as moving CSS declarations to JavaScript.

An additional problem is that even when CSS is properly scoped, you end up with wasted bits over the wire by sending down duplicated declarations. I did some cursory research using cssstats [1] because I was curious how many CSS declarations are unique. Turns out it's about 20-25% for most "big tech" websites.

Gzip helps reduce the impact of this, but Styletron's gzipped CSS is still much (40% or so) smaller than the other CSS-in-JavaScript implementations [2]. In some cases, the cost of switching to using a tool like Styletron might be justified by faster loading times in low-bandwidth markets.

[1] http://cssstats.com

[2] https://ryantsao.com/blog/virtual-css-with-styletron

Bandwidth was the major motivation for building Aphrodite, too: Khan Academy wanted to decrease the number of bytes required for the initial pageload. http://engineering.khanacademy.org/posts/aphrodite-inline-cs...

> We must deliver the absolute minimum amount of CSS necessary by only sending down CSS in use by components in the server-side rendered body. This rules out extracting all the CSS used in all of our components at compilation time, as much of that CSS would not be needed for the initial page load.

Colocating styles with components is a win, but, more importantly, styles-as-JS-objects was the simplest way to get this perf feature out the door.

Faster load times is bit of a stretch. While it's true that up to 40% sounds good, but we are talking about a few kbytes here meanwhile images penalize you much much more on load time. And what do we sacrifice? Mixing concepts that shouldn't be mixed in the first place.

Duplicated styles also compress better with gzip as you said. Do you think stripping a few kbytes is worth turning your whole process upside down?

You're absolutely right that you can do it with pure CSS. But doing a pure CSS solution usually involves some CSS methodology that you need to apply extremely strictly and consistently, which honestly can be a challenge, especially in large apps. Sometimes it's easier to just throw in a new selector at the end of your stylesheet and "make it work". Without discipline, things can get messy pretty quick.

The point of CSS-in-JS and CSS modules isn't that it's necessary, just that it makes avoiding these sorts of problems much easier.

> IMHO SASS covers almost all cases of CSS "at scale".

Absolutely right. I covered this in a blog post I wrote over a year ago (which still applies today): http://hugogiraudel.com/2015/06/18/styling-react-components-...

TBH React itself is another thing I don't quite "get". Isolated components make sense when you have a big webapp like Facebook with a lot of isolated parts, but everything I read about it encourages slicing your code into lots of teensy little modules (which you then have to keep track of, and frequently are used only once), and then goes on about how the amazing Shadow DOM keeps everything fast despite this massive abstraction. Meanwhile I have my view model(s) plus a single big HTML document with bindings in it (and sometimes a few includes for particularly complex bits), bound together with Knockout, and I've never noticed this monolithic approach causing any problems.

Basically, it seems like everyone is talking about how amazing it is and how much it improves their productivity, and this makes me feel like there must be something I'm missing but I can't figure out what that is.

Couple corrections. React uses a "virtual DOM", which is a tree of plain JS objects that can be diffed between renders. "Shadow DOM" is a browser feature related to the Web Components spec.

React's biggest advantage is really not the virtual DOM, but rather how it enables you to think about your application. The phrase "easy to reason about" is kind of overused by now, but there's a lot of truth to it. React encourages use of small components that can be composed together as needed, top-down data flow, and Functional Programming principles. Also, its rendering approach lets you think almost exclusively in terms of "given this state data, what do I want my UI output to look like?", rather than "_If_ this div was visible, I want to toggle it off when there was a click here, but only if..."-type transitions. Overall, that makes it a whole lot easier to understand what is going on inside the application.

There was a recent Reddit thread labeled "What were the biggest 'aha' moments you had in learning React?" ([0]), which has a lot of good comments. You might also be interested in reading through some of the posts on "Thinking in React" ([1]) out there, as well as some business case arguments for using React ([2]). Both those sections are part of my larger React/Redux links list ([3]), which has links to numerous tutorials, articles, and other resources on React.

[0] https://www.reddit.com/r/reactjs/comments/5gmywc/what_were_t...

[1] https://github.com/markerikson/react-redux-links/blob/master...

[2] https://github.com/markerikson/react-redux-links/blob/master...

[3] https://github.com/markerikson/react-redux-links

Great work! Styletron looks awesome and the performance definitely fits well with some of the apps I work with. I'll give it a try :)

I'm curious about this limitation:

> Descendant and child combinators are unsupported

Are conditional applications of rules also unsupported, like `:hover` and media queries? That's a big deal in terms of runtime perf, too.

Yeah, media queries and pseudo selectors (such as :hover) are supported. These tend to be really important for critical CSS. Descendent combinators are possible but to a greater degree involve tradeoffs. I posted my thoughts on this on this issue: https://github.com/rtsao/styletron/issues/27#issuecomment-26...

Am I having a hard time understanding this CSS in JS concept, and therefore Styletron, if I've never worked with React before? Let's say at a basic level, I have compiled stylesheets -- could you explain in simple web developer layman's terms how this could be implemented, knowing the benefit of reduced file size and increased performance is what were aiming for?

Looks nice but I don't like the lack of pseudo selectors:

From the footnotes:

> The one area where this is useful is combining descendant combinators with pseudo classes, the most common use case being where hovering a parent triggers a style change in a descendant node. However, this behavior be implemented in JavaScript with event listeners and explicit state changes if needed.

Plain pseudo classes are supported (e.g. :hover), but since descendant/child combinators aren't supported, a parent :hover can't change a child style. But any element can still have :hover styles.

That seems like it would be a big roadblock to doing much useful with it. Is it impossible, not yet supported, or supported but not easy to do?

Someone submitted a proposal on how to support descendent/child combinators. It's certainly possible, but I wrote a response on why it would would potentially involve tradeoffs: https://github.com/rtsao/styletron/issues/27#issuecomment-26...

tl;dr: deduplication processing for CSS speeds CSS compilation and produces smaller file sizes. Cool!

The article only has compilation performance tests, and there's a small mention in the docs: How does Styletron affect render speed in the browsers?

Also, what tool are you using to convert existing CSS to JS objects?

I actually wrote a separate benchmark suite [1] that uses Paul Irish's pwmetrics [2], so using real Chrome to check render performance. From those numbers, Styletron performed really well in terms of time to first paint and time to first meaningful paint. But I didn't include it in the blog post because I wasn't sure how best to separate the transfer time factor (pwmetrics is set up to throttle network speed) since Styletron critical CSS was so much smaller.

[1] https://github.com/rtsao/styletron/tree/master/packages/benc...

[2] https://github.com/paulirish/pwmetrics

Sounds awesome.

I got the same idea, when I looked at javascript style sheets.

"We don't do inline styles, we generate classes!"

If they already generate, why not merge the whole stuff?

I love how your blog looks.

Thank you! It was built with Styletron actually (you can inspect the source).

It was also built with the excellent Inferno.js [1] library to render static pages while also having a super fast full client-side app, kinda like what Gatsby.js [2] does with React.

[1] https://github.com/trueadm/inferno

[2] https://github.com/gatsbyjs/gatsby

Why overengineer something as simple as a blog?

For fun mostly. It's a passion project I did in my free time. I wanted to dogfood ideas, experiment, and learn.

Why not?

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact