With styled-components into the future
Preprocessing is dead, long live preprocessing!
This is a transcript of a talk I gave on the 11th of October, 2017, at WEBdeLDN.
styled-components has changed a lot in its past. And we are not even done yet!
Since the beginning of the year styled-components has grown in popularity like no other CSS-in-JS library.
It was also around this time that I joined the core team. Together with Glen and Max, we have improved a lot in the library and have arrived at an API in v2 that we are quite happy with.
Let’s discover what we have planned for it in the future! Most of what I’ll be talking about are the internals of styled-components and hopefully I can convey where we think some improvements can be made and what can be gained from them.
What puts us in a position to drive change in the community?
Most of what we are trying to build now has nothing to do with our core ideas. Those remain the same and intact.
While this sounds like no more can be achieved from a library that was mostly about best practices and common patterns in CSS, we are still in a position to drive change in the community.
And this is because of the ecosystem that people have created around the library this year and the size we’ve reached.
In fact, it is fair to call it the most popular CSS-in-JS library to date.
With over 10K stars, lots of great projects building with and on it, and with 147 contributors it is clear that it’s here to stay, and has even greater potential.
One could go so far as to saying that since there are more libraries now which build on the same ideas and similar patterns, it is more than just a library now.
I’d call styled-components a paradigm at this point.
Since we have influenced how people write CSS in their React applications, it has turned into a pattern that isn’t limited to just “the way this one library works.”
Obviously here comes the scepticism, and you might be thinking: “But wait — All I have to do is this.”
And you’d be right, of course!
We’re very proud of the simplicity of styled-components, and it’s extremely easy to get started with. Even more so with our new docs that we’ve published a few months ago.
But the real question is, what makes it the library you know and love?
But to summarise the ideas behind it: It is meant as a successor to CSS Modules and a new way of writing dynamic CSS for the CSS folk, and it makes components the fundamental way to build a styled UI, making them styling constructs.
This builds a bridge between best practices that have been applied for years — like BEM, and components as what a designer might use them for.
And the implementation not only enforces and suggests these ideas, but it also results in a great developer experience.
Put simply, we let you focus on building your app or shipping that feature.
So this covers what idea makes up styled-components, but what actually makes it up?
What actually happens when you create a styled component and render it?
There’s a couple of steps involved, when you create a styled component, and when you render it.
When you create it we have to flatten the tagged template literal input, and inline all non-function interpolations. 
Then we need to inject a marker into the stylesheet.  This is important to preserve the order for specificity.
No matter what order you render your styled components in, the CSS has to be in the order of their creation, otherwise when you’d use the higher-order component pattern you’d run into specificity issues and the order of rules would be unpredictable.
But since we can’t inject the component’s CSS yet, because we’re still missing its props, we can only mark their position for now and move on.
When you render your styled component, we can finally resolve all your function interpolations by passing it the props. 
Then we have to transform the CSS to be able to inject it , and finally inject your CSS into the stylesheet, at the position that we’ve marked earlier.
In v2 and onwards we’ve focused a lot on optimising all of the different steps involved here for performance, but one step that is essentially a black box even for us is the Transforming the CSS  step.
This is where the future of styled-components potentially starts, and what we’ll focus on next.
The CSS that is written with styled-components supports all of CSS, since that’s just what we inject. But there’s a couple of features that we need to add on, to make it more accessible. These are essentially:
- Auto-prefixing properties
- The ampersand / parent selector
- Nested CSS rules
You might know the latter from SCSS or LESS. Since it concerns a single element, the nested CSS is what makes styled-components a pleasure to write.
The drawback is that we need to change and transform your CSS before we can inject it into the stylesheet as something your browser can understand.
We need to auto-prefix and unnest your CSS.
In v1 we’ve used PostCSS — a very popular tool for both CSS-in-JS and CSS tools & build pipelines — to transform your CSS.
We’ve used three packages with it to parse the CSS tolerantly, unnest it, and autoprefix it.
And this setup does exactly what we need, as mentioned before.
This part of styled-components was also the first part we started to optimise in v2.
Since styled-components can just be installed and used immediately, all of our CSS pipeline lives in the runtime bundle, that you ship to your users.
And our PostCSS pipeline was really big. It brought our bundle to 21kB minified and gzip’d. An impressive size for a CSS-in-JS library.
Luckily we were able to replace PostCSS with stylis in v2. It does the same that our pipeline did before, but in one package.
It is highly specialised, small, and extremely fast. It works without an AST and transforms CSS in a single pass.
This brought our bundle size down to an attractive 9kB!
So to get back to the original question of this talk:
How can we make styled-components better?
The question came up a lot of times: “How can I customise styled-components to do what I want?” And our answer remained quite brief most of the times. We want it to be as simple as possible, so we have zero configuration and no build steps, so… You can’t!
This raises an important question outside of our original scope and vision for the project: It is easy to use, but at what cost?
There’s actually some good reasons to allow you to change how the CSS is going to be transformed in styled-components.
The left-to-right transform is one feature that is requested frequently, but hard to pull off without configuration.
Changing the autoprefixing behaviour can be useful for development, or if you’d like to target a different set of browsers.
And last but not least, what if you could implement upcoming CSS features and “polyfill” them?
How are you going to do this in styled-components?
The idea that becomes increasingly important here is, that we can’t build a library that works for some special use cases, but what we can build is a CSS infrastructure that allows you to change the CSS yourself.
Let’s see how we could approach this.
The interesting thing is that with v1 and PostCSS we already had the ability to transform CSS freely!
Since PostCSS parses CSS and outputs an AST, we had a tool already to do this.
And a frequent request from users was for us to open up the PostCSS transformation pipeline, to enable just this. Crazy!
The problem again is: We don’t want to ship a whole CSS pipeline to the client that is this powerful. Also, how much code would we allow the user to run? How slow could it become?
But what if we decided to change and transform our CSS ahead of the runtime, before it is sent to the client? We can run our transformations during Babel’s transpilation, or during build-time in general.
We can build a CSS-in-JS pipeline!
This would result in nothing being shipped to the runtime, and as long as we can provide an “opt-in” system, you would still be able to decide whether you’d like to ship the CSS pipeline with your runtime bundle, or not.
This is also an idea that I originally joined the styled-components team for, and while we weren’t able to bring this to you in v2, we will now be working on it for v3!
It would be a powerful system, and you’d be able to change the pipeline and transform CSS in a build step.
But if you’re working on a prototype, or something that is not production-optimisied to the teeth, you won’t have to set it up and have your v2-like style of styled-components.
Just install and use it, if you don’t need to be able to do this, or move the CSS pipeline into a build step, like a Babel plugin, if you want to. Neat!
Now, while we could do all this with PostCSS, some fundamental problems with it remain.
There’s still the problem of shipping it to the client. It bloated our bundle before and is slower than stylis.
That’s quite ironic, and while we’ve used PostCSS in the past, we don’t want to and can’t go back to it.
We need something like PostCSS that is able to work for CSS-in-JS. Something that:
- provides a format for CSS-in-JS
- that we can put in our pipeline and run during either the build- or run-time
- and something that is faster than PostCSS on the client
We searched for something like it and unfortunately there’s just nothing out there yet, that can tick all the boxes for us.
What has actually happened is that Oleg Slobodskoi has started working on a specialised format that is just that: A CSS-in-JS format, that will work a little like the PostCSS AST.
We call it the “Interoperable Style Transfer Format” — abbreviated to ISTF. And this perfectly suits the vision for styled-components.
This is a format that we can parse CSS to, while putting our JS interpolations into it as well. We can then transform it and put it into our bundle, sending it to the runtime. There we can do some more transformations if we need to and then turn it back into normal CSS.
So I started my work on a CSS parser that takes the tagged template string input, and outputs ISTF.
And what I learnt is — reading through the CSS 3 spec is not something I’d like to do every day. Seriously, it’s long!
Let’s look at the actual format. As you can see it’s not quite an AST and a nested tree structure, but it’s a flat array with some arrays as nodes.
These are markers for all the CSS nodes, so you can find rules, selectors, properties, and values in this example.
At the bottom though you will find a “JS_FUNCTION_VALUE”. This is a special node that we will use to encapsulate our interpolation.
This is just a marker so we are able to put in actual functions on the client during runtime, or Babel AST during build time. Anything really.
This allows us to mark the precise position of an interpolation without a workaround. We get the confidence we need that both out input and output are valid, and that we know exactly where interpolations go.
We have several nodes for several types of interpolations. Since you can put interpolations almost anywhere in styled-components, we can now start to differentiate between them.
So from top to bottom, we have interpolations in values, properties, selectors, and entire mixins that expand to more rules.
We have node types for all of them in ISTF, which makes it easy for us to transform the CSS, and make sure that everything’s also more secure.
To sum this up, ISTF is the first CSS-in-JS format!
We will be able to implement a universal build tool on top of it that does understand JS as well. (Or maybe even other languages and interpolations?)
Finally, what do we get in return?
As said, we’d be able to make styled-components more customisable without the drawbacks. Let your creativity go wild!
We’d retain our fast performance, especially when you opt-in to the CSS-in-JS build pipeline.
We’d also be able to make styled-components smarter and handle some edge cases with interpolations more gracefully.
And the last point is quite exciting: Cross-library compatibility?!
This is a quote from the ISTF description.
The biggest issue at scale we currently have with all CSS-in-JS implementations is that they are not sharable without the runtime.
One of the main goals right from the start, that Oleg has defined, is to make this format part of multiple CSS-in-JS libraries. Once we achieve this goal, we’d have the same or similar pipelines for CSS in multiple libraries.
The idea proposed by Mark Dalgleish in this issue is to use this cross-compatible format to enable library authors to build sharable third-party libraries with it.
You, as an author, would be able to write a component library with a specialised dependency for CSS-in-JS, for example, and if it supports ISTF, you as a user can use it together with styled-components (or another library of your choice) without paying the runtime cost twice.
You would only end up with a single CSS-in-JS runtime on the client! Maybe this is the most literal implementation of Mark’s idea of a “Unified Styling Language”?
This is definitely not only about styled-components. It’s a project for all CSS-in-JS libraries.
So what can you take home and dwell on after hearing about this idea?
We’ve often heard from some people that CSS is dead, and we killed it with CSS-in-JS! But aren’t we just exploring more possibilities?
If anything, CSS is more alive than ever. We are trying to enhance it, build new ideas around it, and are trying to experiment!
And this resurgence of approaches in the CSS community is partly thanks to CSS-in-JS, and recently styled-components. Let’s build our favourite tools of the future.
Last but not least, I’ve mentioned a lot of people in this talk, but I’d like to give some my special thanks!
Thanks to Oliver, who helped me write this talk and get it into shape! (Late in the evening at a pub of course)
Thanks to Mohammad, who has recently spent an insane amount of time to improve our docs and implement new menus!
And also thanks to Ryan, who is helping us out a lot with making our library even faster on the JS side of things!
And thanks to you, dear reader, for taking the time to learn about what we have planned for the future!
There’s a lot of opportunities to contribute to styled-components! Now, and even more so in the future! Please check out our repository and search for some issues, if you’re interested.
And do leave a 👏 if you’re just as excited as I am!