Hacker News new | past | comments | ask | show | jobs | submit login
70% Repetition in Style Sheets: Data on How We Fail at CSS Optimization (meiert.com)
111 points by gmays on June 26, 2017 | hide | past | favorite | 50 comments



As someone who has gone great lengths to improve this by creating a smaller bootstrap [1], I have to say that in the end of the day it will probably not matter. CSS is hardly the bottleneck and the same time spent optimizing it would probably be better spent:

- Optimizing your images.

- Concatenating+minimizing+gzip the JS and CSS.

- Optimizing Javascript.

Likely in order; mileage might vary. If those 3 are already in place, then it might make some difference to optimize the CSS.

First meassure what you actually want to improve (it is not page size per se, it is loading time). Then make sure that you optimize it and not something else.

[1] https://picnicss.com/


Agreed. Css optimization is a drop in the ocean compared to the shitload of 3rd party scripts the marketing team will load through gtm anyway. Readability is the key. Performance improvement is a red herring.


Reducing the overall size of CSS by cutting repetition isn't even that relevant given how well gzip handles that repetition. Unless your CSS is freakishly huge, selector count is not even likely to have much perceptible impact.

I'm completely with you on the 3rd party stuff too. If I run ublock on the relatively JS heavy React site I work on, load times creep in at sub-second, I reckon I can get that down to half a second by hammering the code and applying some smoke & mirrors, but there are obviously diminishing returns on client side optimisation.

Disable ublock and that loadtime triples - likely due in no small part to the three (3!!!) different versions of jQuery the adverts & misc 3rd party scripts are currently loading.

I can't help but wonder if the secret to fixing the internet for the less tech savvy general public isn't messing about with stuff like AWP, offline first, whatever, but to force code going through DFP and GTM to respect the browser and the host site a little more.


Oh yeah, I also wrote about how GZIP handles repetition: https://medium.com/@fpresencia/understanding-gzip-size-836c7...


That's a pretty succinct summary of the gzip thing.

There is still worth mentioning the issue of executing code once it's decompressed - so properly scoping a var is always better value for money than global.ancestor.foo.bar etc.


Could you not just run uncss [0] on the framework and your own CSS? Or does it get tedious to exlcude classes that are used by js? Only ever used it (and other optimizations) for static sites in order to get 100 points on pagespeed.

[0] https://github.com/giakki/uncss


It might work for static sites, but as soon as you start to get fancy and use JS+CSS correctly [tm] it's more difficult because, as you say, there will be state classes modified with JS which changes the style but wasn't previously displayed.

That said, my point still remains the same time invested in learning+using+tuning uncss would probably be better spent on any of my points or some others suggested here. AFTER that, sure! I got the whole page for the library I linked down to 10kb in TOTAL including images [in svg] for a competition. It has gained a bit since then though, mainly with analytics.


In terms of performance, you’re probably right.

In terms of developer productivity, keeping stylesheets uncluttered and easier to maintain is a good idea anyway.


If you are working on a big project then it is important to keep your CSS maintainable.

Quoting the article:

"CSS with this much repetition is also expensive: It’s expensive to create, and it’s particularly expensive to maintain (not even variables will save those who, on average, repeat each declaration nine times, like the Engadget and TED teams)."


This. CSS isn't usually what slows your page down, unless your framework is absolutely gigantic to a ridiculous degree.

In fact, I'd go further and say in a lot of cases it's not even something you're hosting.

It's what third party resources you're relying on. Got ads? That will be the biggest bottleneck here, since ad networks load hundreds of KBs worth of scripts and images that most people block or ignore anyway.

Same with third party social networks and media sites. That YouTube video or embedded tweet is probably using a lot more of your user's bandwidth than anything else.

Not sure what the solution would be there, especially if you're reliant on the ad money or need to use videos or third party media to illustrate your article.


Would throw in there "Use CDNs as much as possible"


Nah. With http2 you're seriously better off having everything in one place. Avoids overhead of multiple TCP connections and (more importantly) overhead of TLS handshakes.

Might not always be true but in tests I've done on my company's sites the http2 load is always faster with no CDN, even from far away locations.


With plain CDNs, sure. With reverse-proxy caches like Cloudflare, the CDN can be the HTTP2 endpoint.


I would bet it still adds latency unless your site is completely static. For anything that needs to hit your servers you->cloudflare->server is still slower than you->server. On the round trip that's added two extra hops so maybe 100ms.

The golden age of CDN's is over. Their primary advantage was being able to open up many parallel connections using multiple subdomains and the ability to cache across domains.

With http2 multiple connections have become a bad thing and https is mandatory so CDN caching is irrelevant.

On http2 and a tiny AWS instance I can get sub 200ms page loads from anywhere in the US if I host in the midwest. The gain from avoiding multiple HTTP connections, TCP slow starts, and TLS negotiations negates all the speed advantages of using a CDN. Cloudflare might be worthwhile if you get massive traffic but a small Nginx instance can handle hundreds of megabytes per second of traffic.


I just don't feel comfortable using them in certain situations, like SaaS web apps. What if they go down, for any reason, and since I depended on the CDN for a core framework (say, React), now my entire site isn't going to load. Queue customer e-mails.

That being said, for less mission-critical projects, they do have their place.


CDNs aren't an either-or thing. It's common to fall back to a copy on your server if the CDN version doesn't load.

CDNs involve a big set of tradeoff choices you have to make, they don't make sense for every circumstance. I don't think I'd ever throw in "Use CDNs as much as possible" into an optimization advice list.


Yeah, keep the dynamic stuff on your webserver, static stuff on the CDN. Part of the reason I say "as much as possible" is because I've seen significant conversion improvements by enabling a CDN.


Typically the CDN is used for all static assets does not include the HTML. In the event that your CDN is not working, you can always fallback on your own static.example.server.

CDNs are one of those cases where the cloud vastly outperforms whatever kind of server you have.


I would add to this, use a dumb but performant CDN. Cloudflare's Rocket Loader has rewritten working Javascript into broken Javascript for me. If I use Cloudflare I turn off all the gizmos to be sure.


Ah sure, but that'll depend a lot more on the type of website. For small websites I normally include the main library through a CDN and just include it into the <style> tags if the total HTML+CSS size is under 14kb. For medium-large websites totally agree.


this. optimizing images using webp seem to be pretty cheap way to save lots of lots of bytes. i wish apply had support for it, though.


I find problems with both the author's methodology and hypothesis.

Counting unique declarations doesn't really tell me anything. CSS isn't simply a declarative language. CSS rules are ordered. Sometimes (and oftentimes within webapps) you need to repeat a declaration because you're using it to overwrite an earlier declaration but only when the second selector is valid. For example, it's very common to have lots of `display` declarations that turn on or off elements by overwriting earlier rules. Because of this, you're going to have lots of "repeat" declarations, but they're not repeated in their location within the order. With complex pages, you're going to wind up twisting yourself in knots trying to keep everything logically ordered if you also want to minimize declarations.

Additionally, from my naive understanding of browser engines, the performance cost of multiple rules applying to an element is greater than the cost of multiple declarations within a rule. Why minimize them?

But my largest concern is the mental overhead of trying to simplify rules in code. Co-locating declarations affecting a logical unit is extremely helpful. The tool we have to accomplish this is called a selector. Minimizing declarations increases selector count, which kind of defeats their purpose. Pre-processors can't solve this for you either, due to CSS rules being ordered. If all you're building is a static page then, sure, I can see minimizing declarations being some kind of goal. But CSS gets used for a lot more than static pages.

As other commenters have noted, if the author's concern is overly-large CSS files, run-length encoding alleviates a lot of that, at least if the declarations are in the same file (a reason to not use http2 as a silver bullet and use concatenation instead, if that's a concern).

EDITS: Added a couple sentences.


I agree completely. This just seems like this reordering/grouping of properties makes the css a nightmare to manage and moved scatters related properties all over the stylesheets. It's would be like grouping the same coloured/shaped parts of a car together because they are the same colour and shape, and not grouping them by what they are - it might reduce the number of times you need to write the word red, but when you need to update at how a part looks on the car then it would be a mess!


gzip takes care of the repetition and I don't think CSS parsing has any meaningful impact when you consider all the freak show of resources and scripts a typical page loads (I have no data on this though, just intuition).

A maintainable CSS is way, way more important. Writing complex CSS is very easy, but debugging can be a nightmare.


If it is repeated then it is harder to maintain. If you have worked on a big project you know that it is way harder you have to change things in a few dif. places instead of just one, and there will be much harder to maintain consistency accross your style.

Can you give a counter-example where repeating CSS improves maintainability ?


When you have a base style sheet (it can be a framework or a base style sheet for your main application and sections overwriting them or custom style sheets for some tenants of your application or whatever) that you want to customize?

Many component-based designs also prefer repeating styles for each component because of maintainability but I guess they wouldn't be counted in the article as they use generated root names.

My point is, not repeating styles should not be the target, but it may of course be the side-effect.


And http2 takes care of concatenation.


Are there any tests anywhere to see if concatenated and separate files over http2 take the same time to be delivered? It would be very interesting if the times were equal in practice too.


brotli ftw.


If you condense everything that can be condensed, the resulting style sheet could be harder to adapt to changing requirements.

One example of this is when some styles are "accidentally" the same for two elements. If you condense them into one rule, then you lose the independent control.

"Hey look, 97% of the users always turn up bass and treble together and turn down the middrange; we can condense the three-band equalizer panel down to one knob!"


This is why I personally like the Vue/React way of scoped CSS -- it promotes loading a simple baseline CSS (like Skeleton) for the full page and specializing in the individual component layer. It's easy to refactor in the case of changing requirements (if the same across components, move style into global CSS, if not, move into component).


Shout to React's Styled Components: https://github.com/styled-components/styled-components

Very nice to work with.


Try an atomic CSS approach. Its clean, its minimal, it works. :)

http://tachyons.io/


The pitfall of using HTML+CSS for content generation/authoring, instead of as a page description output format.


...in which some guy is surprised how a system with 0 modularity, 1 flat namespace, 0 encapsulation, and cascading rules leads to repetition


CSS supports XML namespaces [0], although I've never seen someone using it.

Shadow DOM supports style isolation [1], although I don't think I've never seen anyone using it outside of toy projects.

There's also a newly proposed containment [2] CSS property, but it's only supported on Chrome.

For simpler websites where you skip using any build tool, I don't think it's a problem. For larger applications that support build tools, css-modules is a good alternative [3].

[0] https://developer.mozilla.org/en-US/docs/Web/CSS/@namespace

[1] https://webkit.org/blog/4096/introducing-shadow-dom-api/

[2] https://developers.google.com/web/updates/2016/06/css-contai...

[3] https://github.com/css-modules/css-modules


Particularly hard to fix for a site that's been around a while. It's difficult to identify dead css because you would have to have something like 100% DOM test coverage.

Perhaps that "dead" style is only used for that error div that isn't visible unless some rare condition exists.


The solution to this is to treat CSS as a unit of code, like Javascript and before static analysis on the result.

Solutions like CSS Modules and Styled Components are getting _really_ really close. CSS Modules lets you do this at a component level, and is theoretically possible to do it at an individual class level (providing you use a 'safe' syntax).


PurifyCSS does that: https://github.com/purifycss/purifycss

The only issue I have come across is when you are inserting elements with CSS classes using JavaScript. A workaround for this is importing the CSS in the JS module you are using it in via webpack or PurifyCSS also has an option to manually exclude certain selectors.


As you mention, It does if you somehow click every combination of buttons, invoke every error, corner case, etc. It can't know about HTML that isn't there, but could be.


Unnecessary duplicate "artwork" has always been a problem for digital mediums.

Example from the dark ages: naive (or lazy) CAD drafters would often draw overlapping lines, which then caused the pen plotter to draw that many strokes on the mylar, which would then have too much ink, botching the output. Enough of a problem that 3rd party tools popped up to dedupe plot files.

---

Opera was onto something with server side rendering for their mini browser. They should sell that as a service.

In fact, someone needs to take it even further:

Render to some page description language (maybe PDF), and then automagically rehydrate into minimal, inferred HTML+CSS+images. Kinda like how tabula and others scrape PDFs to reconstitute tabular information.

One benefit is throwing away all the box model layout cruft. True WYSIWYG.

Another, even bigger, benefit is sanitizing content, like removing web bug tracking images.

---

I'm currently test driving the Mercury Reader extension for Chrome. Ignoring all styling also sounds like a terrific idea. I may prefer this strategy. https://postlight.com/


https://github.com/rtsao/styletron generates a pretty thorough de-dupe of CSS rules


Example of low repetition from his page:

view-source:https://meiert.com/setup/default.css

If this is worthwhile doing, a source tool to transform it would make more sense IMO.


I use Tachyons for 99% of my styles, writing minimal amounts of highly specific styles. My productivity has gone way up and I no longer have to worry about CSS pretty much at all.


I think the best thing I got from this article was the discovery of CSS Stats. http://cssstats.com/


As part of our build process, not only do we minify our stylesheets (which makes an attempt to unify nearby selectors, if similar) but we precompress. And our library will route asset requests through a proxy script that adds cache-control immutable, so only the first request will download the asset.

Repetition is simpler than the complexity of managing huge projects that are constantly changing.


> excess of repetition is the definition of bad code

No. The lack of abstraction tools is the definition of a lower level language.

You know it's a lower level language when, as a developer, you feel the need for one higher. I submit Less and Sass as evidence.

Honestly, this alone making it to standard css would have me taking a week off knowing how much time I'd save in the future:

.c {.b .a}

... where a and b and c are all classes.


Seems like CSS-in-JS has solved this problem already.

Libs like styletron consolidate duplicated style declarations.


In my experience CSS would be fairly more efficient if it supported inheritence. When I want multiple similar declarations I have to either cope/paste them all and slightly edit them or apply multiple definitions to the targets.


A few plots would go a long way in this post.




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

Search: