Hacker News new | past | comments | ask | show | jobs | submit login
UnCSS: Remove unused styles from CSS (github.com/giakki)
181 points by rodrigocoelho on March 9, 2017 | hide | past | favorite | 48 comments



Have used tools like this before, and invariably end up accidentally stripping styles from an <insert JS UI lib> component with a dynamically assigned className or something equally silly.

It's such a hard problem to solve and the Stylesheet Snowball is one of the most frustrating pieces of technical debt a large site can pick up.

It makes me wonder if scoped CSS modules, despite fundamentally breaking CSS with their hyper specific hybrid BEM-class-ids might be the best preemptive solution even when you're not scaling up to massive SPAs.

Edit. Meant CSS modules, wrote CSS components. I am not a clever man.


I used BEM everywhere on the front end of a project, at the time I didn't really like it very much.

I came back to that project 7mths later and I could find everything exactly where I left it, I realised that past me had made a reasonable choice for the future.

Now I hold my nose and use it (or something like it) everywhere.

Even b-<somefoo> { <otherstuff> } helps hugely, I just wish there was an easier way of stopping accidental stomping from other selectors outside b-<somefoo>.


> with a dynamically assigned className or something equally silly.

How is that silly? Assume e.g. a very simply dynamic list, where you just want highlight rows after clicking on them. Should it set the CSS properties of the <tr> directly? It makes more sense to dynamically add/remove a CSS class such as "selected-row". However, since initially no row is selected that class does not appear in the initial HTML.

> scoped CSS components

Not sure if this is the same, but I had very good experience with component-oriented frameworks where each component has an HTML snippet and CSS snippet attached to it. The CSS is written in a way that it affects only the responsibility scope of the component, nothing outside the component, nothing inside child components.[1] Then, these CSS snippets are simply collected into a large (and perhaps compressed) CSS file.

[1] This is usually done be either prefixing all CSS rules with a component-specific class name, or by using a component-specific custom HTML element and selecting on that. Stepping into children is avoided by using only child selectors ("a > b > * > * > c" instead of "a b c"), so the rules don't reach deeply except for the cascading properties of CSS itself.


Oh dynamically setting class isn't silly, but the times i accidentally removed the 'unused' style because it's not explicitly coded into that app and was flagged by the tool, I felt pretty silly!

I got as far as starting to build us a tool to traverse our raw jsx to identify these programmatically generated classes, but my implementation was a bit flakey for various reasons and required too much config to make it worthwhile so I binned it off.


> Oh dynamically setting class isn't silly

Oh, so I misread your sentence. Thanks for the clarification. Sorry for the noise.


> Have used tools like this before, and invariably end up accidentally stripping styles from an <insert JS UI lib> component with a dynamically assigned className or something equally silly.

The only way I can think to handle this is if you've got a really thorough test suite in Selenium or similar. Do a before and after, capture screengrabs of everything and flag up where the 'new' CSS has caused visual differences.

It's a horrible solution but then it's a horrible problem.


Alternatively, you could dump the DOM of each rendered test case and use that to construct a single document that has all the classes and ids that are in use in it and run UnCSS on that. Maybe. Not saying it will be easy (in particular rules which are about certain hierarchies might get hairy) but I think it's worth a shot.


Huh, pulling it out the unit tests is a pretty good idea actually - if the test coverage is good enough it might be worth a bit of investigation.

Especially since it'd be an optional build step you'd only want to run infrequently.


I agree, it's a hard problem. Perhaps run a test-suite with UnCSS? If you still accidentally strip styles then the test-suite is not complete.


Not a bad idea.


This is already theoretically solvable with CSS Modules. Because you 'statically' import your CSS (class names) as an object, which can't/shouldn't change after build, you could run an analyser to remove any unused classes from the stylesheets. Essentially tree shaking for CSS.


My experience with CSS modules is that it has the same problems, just at a smaller scale. And because of the local scope I also see a lot more code duplication. It also doesn't play very well with global CSS libraries like bootstrap.


For me, BEM takes much of the pain out of reusing and (especially) refactoring big CSS.


The advent of Sass and Nesting helped an awful lot, but there is still much to be done.


I've been hoping someone would write a similar tool, but using a browser, so that I could run a webapp, click around (using a checklist, to cover all of the views) and get a resulting list of used CSS rules from the browser.

That would have the advantage of supporting complex webapps written in any language/environment.


There's a "Coverage Support" experiment in Chrome DevTools which does css + js coverage in a way you describe it. The CSS part was shown on Chrome DevSummit 2016 [1]

[1] https://www.youtube.com/watch?v=HF1luRD4Qmk


Starts around the 18min mark in the video.


Ideally it would be a feature in the browser itself. Should be simple. The browser already handles the data. It only needs to dump it somewhere.

As important as what classes were used is what classes were in the HTML, but didn't exist.

Instead of manually clicking around a webapp, you could do a test suite. This gives another way to measure test coverage.

I assume we have to wait for a browser vendor to do this? I haven't seen any plugin/extension feature that reaches this stuff.

Then again, we have good open source browsers...


States can quickly become complex. Like styles for a logged in user with a 3-years-membership badge or styles for a placeholder that only new users see.

Or styles for a CMS module that is currently not in use (e.g. for open positions in a company).

I wonder what the least used, yet still used CSS is I ever wrote (relative to project traffic). Maybe some feature a Client requested, I knew he would never really use, but he insisted on (deep linking to some modal for a facbeook post or something).


It might be hard to do for an existing site of considerable size, but for new sites it could be done incrementally as features are added.



Sounds pretty similar to Dust-Me Selectors https://addons.mozilla.org/en-Us/firefox/addon/dust-me-selec...


Etsy did something like that, but on the production site.

Every visitor had a small probabillity it would run the code, and after a couple of million visitors, they had a pretty good idea of what styles were unused.


That would actually solve the use case above (hidden styles triggered by Javascript).

A large user base would give you decent coverage.


There is one built into Chrome's dev tools, in the audit tab: http://imgur.com/a/Va2d0

It is, though, limited to the context of a single page. You can't browse several pages and then see what the net unused css is.

I believe, though, extensions can talk to dev tools, so there's some plausible way to extend it.


I wonder what the performance increase would be of loading one big CSS file on the first page load and serving from local cache from then on, vs loading a smaller but different CSS file for each page you visit.

My impression is that number of requests is a more critical optimization than file size (at least when serving CSS sized files)


It's going to depend a lot on the type of site you're building.

Thankfully frontend pipelines like React + Webpack makes it fairly easy to test this out. We spiked out generating individual JS + CSS for each view but ultimately found it wasn't worth it - individual views amounted to very little code with the bulk of the filesize taken up by the common 'core' of our application, and dependencies. We scrapped this specific idea.

What we did end up doing, however, was create a seperate stylesheet just for things required on the very minimal landing page and inject that straight into the <head>, which gave us significant speed improvements.


Just to clarify (after I've re-read my comment) - we didnt explicitly create a landing page stylesheet, instead we were able to use Webpack to automatically create a seperate stylesheet at build time containing just the styles used in the dependency tree of the landing page.

Webpack is super handy like that - no need to be aware of this and manually keep track of a seperate stylesheet :)


Really depends. How big is the once CSS file you're loading and how much of the CSS is common from page to page? If 20kb is for a specific component on one or two, less-visited pages, then it probably doesn't make sense to lump everything together.

A fairly safe default starting point would be:

1. Tiny subset of inlined critical CSS (See https://www.smashingmagazine.com/2015/08/understanding-criti...) 2. Asynchronous + rel=preload loading of the main CSS of your site 3. Then, if there are hefty chunks of CSS for specific pages only, bring those in on those pages.

This changes a bit if you're using HTTP/2. Then the multiplexing makes it beneficial to break the CSS into a few smaller files instead of one big one.

In the end, you probably just have to experiment and test. :) No guaranteed "this is best" approach here.


From what I've heard, the answer depends greatly on whether we're talking http 1 or 2


HTTP/2 is pretty much everywhere now, including virtually all mobile devices. So targeting HTTP2 is pretty sensible IMO.


Serving the page with inlined CSS also has the compression benefit of many html ids and classes being repeated in the CSS.


AFAIK, this could also be written as a transform in AssetGraph [https://github.com/assetgraph/assetgraph], which does all of the loading, parsing and traversing for you. At least the given description reads like the internals of the population/loader-system in AssetGraph.

As an added benefit, you'll get de-duplication, minification, inlining (with fallbacks) and, with a little work, image-spriting thrown in almost for free.

If you want to play around easily, take a look at https://github.com/assetgraph/assetgraph-builder


I've done something similar nearly two years ago[1]. The problem is rather difficult, especially with all the JavaScript frameworks in existence right now. I've tried to build a state machine for handling those frameworks but never got around to finishing it. There's basically and endless amount of combinations one can do, thus you probably want to limit depth or something. You also have to be able to detect ::after and similar kind of selectors, which isn't always straightforward with querySelector and needs quite a bit of cleaning. I ran into a few other (edge) cases, which I cannot remember right now.

Nonetheless, it is a challenging and fun problem to solve.

[1]: https://github.com/Kevin-A/css-detector


My naive opinion is that this should be done by the browser, perhaps in the developer tools. You would be marking a session as "integration tests" and at the end it would give you a simplified css file without the unused rules during testing.


I wholeheartedly agree with you. That would be the ideal solution, because the browser is already doing the passes over the CSS rules.


This is a problem I thought would be solved by 2017


Clutch timing! Been working on several projects amassing a multi-purpose stylesheet (currently ~600 lines) having already conceded that a tool which accomplishes exactly this would be needed.

Edit: seems overkill, the theoretical solution I had in mind would just take a few arguments;

ie:

trimCss mybloated.css mypage.html > mytrimmed.css


I've been wanting to tie this up with Casper automation in order to get coverage on dynamically generated content. The hard part is just ensuring the content path is adequate. We do something similar with PhantomCSS already, but it's definitely a tighter scope.


Finally! I've been wanting this for a decade - but not badly enough to try to build it myself.


I made a similar tool, although it doesn't load the page in a browser to remove unused styles.

https://www.npmjs.com/package/uselesscss


How easy would it be to get this to work with something that spiders a site, so it generates a CSS file dynamically for a whole site instead of manually feeding it URLs?


This is great! I'm happy to see PostCSS proving to be a creative platform for web development.


Would this work if you print classnames with php?


Sure, you'd need to point the files argument at your website on a server, though - you couldn't just target a .html file.


Am I missing something? UnCSS has been for about 3 years now. http://bit.ly/uncss by addyosmani


I guess this is being downvoted because he links to the wrong repo (i.e. the grunt edition of the same project) but the point seems fair. I've been using unCSS about 2 years so it is interesting that this discussion is happening now and that people seem surprised by its existence. Perhaps just a symptom of too much JS everything.


Different projects, duplicating work is also big issue that need to be solved IMO.




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

Search: