callstack / linaria

Zero-runtime CSS in JS library
https://linaria.dev
MIT License
11.69k stars 417 forks source link

Classname optimization: shorten CSS class names #218

Open ivan-aksamentov opened 6 years ago

ivan-aksamentov commented 6 years ago

Introduction

Due to its powerful compile-time CSS and JS preprocessors, and, more importantly, read-write access to both, Linaria has a great potential for further CSS optimizations.

One of the best size reduction optimization is shortening of class names. That is, in production Linaria could rename the default .classname_ab0c0da8ra into shorter, simple, potentially sequential, unique identifiers, like .a_a or .z_1 (for example via incstr)

This optimization is especially important for style-heavy projects, with external CSS, and CSS frameworks. It reduces size of not only CSS, but also of rendered HTML, by reducing bytesize of class names in tags and in inlined syles.

State of the art:

This article describes significant CSS size reduction from classname optimization.

The coolest kid in this area is probably LinkedIn's CSSBlocks (see 3-step code optimization teaser). Similarly to Linaria, they parse both, JSX and CSS. However, CSSBlocks is very opinionated: they force a specific CSS flavour in specifically named files. Also they don't support Webpack 4 yet.

There are also standalone Webpack CSS classname optimizers, for example: optimize-css-classnames-plugin. But this one only works with HTML extraction, so no SSR.

Design decisions

Related

208


Do you think it is something that Linaria can implement?

satya164 commented 6 years ago

Thanks! I'll look into this once I've the pending items sorted.

One problem with using something like incstr is that the Babel plugin only has access to one file at a time, which make it non-trivial. We also need deterministic class names, which is important during evaluation. With this it might be impossible.

satya164 commented 6 years ago

I think it have an idea of how to make it deterministic. I need to store a cache somewhere with mappings from full class to short ones. Will see.

ivan-aksamentov commented 6 years ago

@satya164 On linaria/next you currently have both, babel preset and webpack loader. I don't know for sure, but maybe it is more convenient to access all the chunks from webpack? It is also not uncommon to use webpack loader+plugin (like mini-css-extract-plugin), so why not.

In any case, I will gladly test any implementation prototype.

Thanks!

brandonkal commented 5 years ago

One way to implement this would be to just prefix and suffix all generated classnames and CSS variables:

Mode Before After
Development const Title = styled.h1`` .Title_ab0c0da8ra
After Babel (prod) .Title_ab0c0da8ra .linaria--Title_ab0c0da8ra--linaria
After Webpack .linaria--Title_ab0c0da8ra--linaria .a

Linaria could provide an array of these sorted generated names and expect the user to define a callback that returns the array with modifications.

Alternatively, it should be pretty simple for the bundler to use regex string replacement safely on javascript and and linaria.css imported files. In that case, the prefix and suffix could be disabled by default and if enabled, it would be up to the user to handle the string replacement.