CSS in JS. Rockey.
To cut long story short:
- Flexible CSS in JS approach
- Repository — https://github.com/tuchk4/rockey
- React application with implemented different CSS in JS ways https://tuchk4.github.io/css-in-js-app/
Examples:
- Card example
- Warning Card example
- Buttons looks
- Input styles for specific value
- Div background depends on mouse X and Y
Why do we need CSS in JS?
Every developer with whom I told about CSS in JS asked me — why should we do this stuff if it is possible to use usual CSS / Less / Sass?
CSS in JS approach — is native JS. You don’t need additional tools to use or build it.
Also I’m tired of large classnames conditions. Just write dynamic CSS rules (that depends on component props) and keep only business logic code inside component.
1. For Components Libraries
Or when going to share components between applications.
With CSS in JS approach it is more simpler to build components bundle. Just transpile code with babel and that is all.
Including component to the application immediately includes code and styles. And there is no extra CSS code — each component define only its own styles.
Means that if you need to include only Button — just import button component and its already defined with styles.
No needs to include additional CSS files.
2. More Simpler Application Configuration
This is not a problem to configure bundler but for now this is became very boring. Each time, each application with same configuration. Each time, each application — update all dependencies and configuration if new major release.
CSS in JS approach available for all bundlers, for each architecture because there is zero configuration. And I am very happy to is this with Create React App.
3. There is No Custom Loaders
I was happy and delighted when started to use webpack and discovered the power of loaders. Such WOW to JS capabilities. I always told with our backend developers and said something like — “Yo! Look and your hulking backend technologies! Using JS you can write in CoffeeScript, TypeScript and JS at one application, require CSS or Less files and then bundle all these modules together!” Such WOW I though. How could I be wrong…
CSS in JS approach — is native JS.
As I wrote at “Why I Love Create React App”:
Always remember that using not usual loaders (like yaml, markdown, dsv loaders etc.), additional plugins and features from drafts and proposals makes your application more complex, maybe with dead syntax features and it is become impossible to migrate from current webpack configuration.
That is the reason I even do not like webpack CSS loaders. I always try to require only js or json modules. From my point of view — CSS files are OK only at webpack entry point configuration but not for require.
4. More Cleaner File Structure
There is no extra CSS / Less / Sass files. All styles inside JS file.
During last few years I followed experience that each component should have JS and CSS files located at one directory. This approach provide application to be very clear but problems comes when there are a lot of components of you want to share components between applications.
5. Easier to Run Tests
Don’t need to setup context, mocks or require hooks to load CSS / Less / Sass files.
Rockey and Integration with React
Api documentation: tuchk4/rockey
Stressless styles for components using JS. Write CSS with functional mixins according to the component structure using components names.
Framework Agnostic
Rockey could be used in any application.
import rockey from 'rockey';
const rule = rockey`
Button {
color: black;
${rockey.when(props => props.primary)`
color: blue;
`}
Icon {
margin: 5px;
}
}
`;
const classList = rule.getClassList({
primary: true
});
const className = classList.Button;
Small Size
- rockey — 7kb gzip
- rockey-react — 10kb gzip
Uniq Class Names
Each time generate uniq class names with randomly generated hash. Same as css-modules.
Component based selectors
Write CSS according your components structure. Use real components names for CSS rules instead of classes.
Means that if you have component Card — use its name as CSS selector. If you have component PrimaryCard — use its name as CSS selector. Use nested selectors according to components structure.
Demo: Card and PrimaryCard
import rockey from 'rockey-react';
import look from 'rockey-react/look';const CardHeader = rockey.div('CardHeader')`
font-size: 18px;
font-weight: bold;
padding: 5px;
`;const CardBody = rockey.div('CardBody')`
padding: 5px;
`;const { Card, PrimaryCard } = look.div`
Card {
margin: 5px;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 1px 1px 3px #ccc;
font-family: 'Roboto', sans-serif;
width: 200px;
} PrimaryCard {
CardHeader {
background: rgba(0, 0, 255, .1);
color: #3399ff;
}
}
`;
Readable CSS Class Names
Each generated classname is clear and readable. The same components renders with same class names. It is very useful and сompatible with browser dev tools — change styles for one component will always apply changes for the rest of the same components.
~100% CSS Syntax
There is no needs to import specific function to render @media, keyframes, font-faces or pseudo classes like :hover or ::after. Support nested and multiple selectors.
Demo with complex selectors: Material TextField
Fast. And Will be More Faster!
Rendering CSS string, generating CSS rules and inserting them into DOM is really fast. There is example React application with implemented different approaches: fela / jss / glamor / styled-components / rockey.
I was really inspired by styled-components and want to say great thanks to Max Stoiber.
- Live: tuchk4.github.io/css-in-js-app
- Github repo: github.com/tuchk4/css-in-js-app
Benchmarks from parsing 10000 generated CSS classes:
npm run best-results -- --size 10000
- Rockey Parse Optimized — 3.325sec
- Rockey Parse — 3.841sec
- Postcss with Nested Plugin — 14.204sec
- Postcss Safe Parser with Nested Plugin — 16.404sec
Note that rockey and postcss were developed for different tasks. Rockey parser configured for specific syntax and will never be able to replace postcss
Class Names
rockey uses separated CSS classes for each rule and for each mixin. That is why it is very сompatible with devtools. When change CSS values of parent component via devtools — it will be applied for all children.
import rockey from 'rockey-react';const Button = rockey.button(‘Button’)`
color: black;${rockey.when(‘LargeButton’, props => props.large)`
font-size: 20px;
`}
`;const PrimaryButton = Button('PrimaryButton')`
color: blue;
`;const SuperButton = PrimaryButton('SuperButton')`
color: red;
`;
Inserted CSS (after component is rendered):
.Button-{{ hash }} {
color: black;
}.PrimaryButton-{{ hash }} {
color: blue;
}.SuperButton-{{ hash }} {
color: red;
}.Mixin-LargeButton-{{ hash }}.Button-{{ hash }} {
font-size: 20px;
}
And for <PrimaryButton large={true}/>
className prop will equal .PrimaryButton-{{ hash }} .Button-{{ hash }} .Mixin-LargeButton-{{ hash}}
.
That is why it is very сompatible with devtools. When change CSS values of parent component via devtools — it will be applied for all children.
If prop large is changed to false — only mixin class will be removed instead of all styles re-calculating. This is another reason why rockey is fast.
Demos:
Dynamic CSS
Demos:
import React from 'react';
import rockey from 'rockey-react';
import look from 'rockey-react/look';const CardHeader = rockey.div('CardHeader')`
font-size: 18px;
font-weight: bold;
padding: 5px;
`;const CardBody = rockey.div('CardBody')`
padding: 5px;
`;const { Card, PrimaryCard } = look.div`
Card {
margin: 5px;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 1px 1px 3px #ccc;
font-family: 'Roboto', sans-serif;
width: 200px; ${rockey.when(props => props.warning)`
CardBody {
background: rgba(255, 0, 0, .7);
color: white;
}
`}
} PrimaryCard {
CardHeader {
background: rgba(0, 0, 255, .1);
color: #3399ff;
${rockey.when(props => props.warning)`
background: rgba(255, 0, 0, .9);
color: white;
`}
}
}
`; CardHeader {
background: rgba(0, 0, 255, .1);
color: #3399ff;
${rockey.when(props => props.warning)`
background: rgba(255, 0, 0, .9);
color: white;
`}
}
}
`;
Rockey React Features
Flexible Rockey Higher Order Component
import rockey from 'rockey-react';const Component = rockey(BaseComponent)`
color: green;
`;
Now Component could used as React component:
<Component/>
Or extend it and create anonymous child component with additional styles:
const Child = Component`
text-decoration: underline;
`;
By default rockey-react try to use BaseComponent.displayName to generate classname. But sometimes it is more useful to set name manually.
const Child = Component('MySuperChild')`
text-decoration: underline;
`;
And same usage for Child component.
There will be generated two CSS classes for Child:
Child component className will be composed of class with its own CSS rules and all parents class names.
Shortcuts
Available all valid html tags. Create anonymus component from shortcuts:
import rockey from 'rockey-react';const Block = rockey.div`
padding: 5px;
border: 1px solid #000;
`;
Create named component from shortcuts:
import rockey from 'rockey-react';const Block = rockey.div('Block')`
padding: 5px;
border: 1px solid #000;
`;
Dynamic CSS — props
import when from 'rockey/when';
Write CSS that depends on components props.
const Button = rockey.div`
color: black; ${props => props.primary && `
color: green;
`}
`;
Or use helper when:
const Button = rockey.div`
background: white; ${rockey.when(props => props.primary)`
color: blue;
background: green;
`}
`;
For more happy development process it is possible to set mixin name:
rockey.when('isPrimary`, props => props.primary)`
color: blue;
`;
Dynamic CSS — Event Handlers
import handler from 'rockey-react/handler';
Write CSS mixins that are triggered along with events handlers.
Demos:
const Input = rockey.input`
font-size: 25px;
border: none;
border-bottom: 2px solid #000;
padding: 0 0 5px 0;
outline: none;
font-family: monospace; ${rockey.handler('onChange', e => e.target.value === 'rockey')`
color: green;
`}
`;
Component Looks
import look from 'rockey-react/look';
Split component into different looks.
const { Button, SuccessButton, WarningButton } = look(BaseButton)`
Button {
font-size: 15px;
}
SuccessButton {
color: green;
} WarningButton {
color: red;
}
`;
First component will set as parent. All next looks will extends first look. All shortcuts also available at look function. For example — look.button
.
Demo: Buttons looks
import look from 'rockey-react/look';const { Button, PrimaryButton, SuccessButton } = look.button`
Button {
padding: 5px;
background: none;
} PrimaryButton {
color: blue;
} SuccessButton {
color: green;
}
`;
This is the same as:
Demo:
Difference in these demos only in generated CSS class names
const Button = rockey.button`
padding: 5px;
background: none;
`;const PrimryButton = Button`
color: blue;
`;const SuccessButton = Button`
color: green;
`;
And:
Demo: Buttons static look method
const Button = rockey.button`
padding: 5px;
background: none;
`;const { PrimaryButton, SuccessButton } = Button.look`
PrimaryButton {
color: blue;
} SuccessButton {
color: green;
}
`;
<PrimaryButton />
// or
<Button.PrimaryButton />
Component Feature Table
Most component features could be implemented as component’s prop or as Higher Order Component.
<Button primary={true}/>Yo!</Button>
or
<PrimaryButton>Yo!</PrimaryButton>
Correct way — depends on you and on your team :) Just use what you like best. But if you use <PrimaryButton/>
I will ask: “So same for disabled button? Use <DisabledButton/>
Higher Order Component? Huh?”. Last one looks really ugly.
Example with Button component is really simple. In real application there are a lot of much more complex situations and to make more correct decision 0-I use such tables:
- ripple — could be used in any state. So it should be used as
- disabled — could be used in any state. So it should be used as prop.
- success — could not be used along with warning and primary. So it should be implemented as Higher Order Component.
If there is no ❌ — feature should be implemented as props.
If there is a least one ❌ — feature should be implemented as component.
According to this table we get these components:
<Button disabled ripple raised>
I'm Button
</Button><SuccessButton disabled ripple raised>
I'm Button
</SuccessButton><WarningButton disabled ripple raised>
I'm Button
</WarningButton><PrimaryButton disabled ripple raised>
I'm Button
</PrimaryButton>
And rockey look feature helps with this.
Demo: Look: raised Button / PrimaryButton / SuccessButton
const Button = rockey.button('Button')`
font-size: 20px;
padding: 5px;
margin: 5px;
background: white;
border: 1px solid #ccc; ${rockey.when('raised', props => props.raised)`
box-shadow: 5px 5px 5px rgba(0, 0, 0, .3);
`}
`;const { PrimaryButton, SuccessButton } = Button.look`
PrimaryButton {
color: blue;
} SuccessButton {
color: green;
}
`;
To keep imports and exports cleaner — all looks available as static attribute at parent component:
<PrimaryButton>I'am primary button</PrimaryButton>
// same as
<Button.PrimaryButton>I'am primary button</Button.PrimaryButton>
Recompose shortcut
Currently we use recompose in each React application. Recompose helps to write less code and share features between components. This shortcut helps to save time and code when using rockey + recompose.
Great thanks to Andrew Clark for recompose!
import rockempose from 'rockey-react/recompose';
import withProps from 'recompose/withProps';const Line = rockempose.span(
withProps(props => ({
long: props.value && props.value.length > 140
})
)`
font-size: 15px; ${when(props => props.long)`
font-size: 10px;
`}
`;
Experimental plans
Render Cache with Service Workers
If you had not use or even read about Service Workers — watch this great talk by Jake Archibald “Instant Loading: Building offline-first Progressive Web Apps” at Google I/O 2016.
Idea is to cache rendered CSS rules at Service Worker after application was initialized. After page reload — rockey will insert CSS link:
<link rel="stylesheet" href="/rockey.css?v1" />
And Service Worker will catch this request and return cached styles.
Why Service Worker? Why not LocalStorage or IndexedDB? Because when rockey will support Server Side Rendering — CSS files could be generated at server and then just applied at client.
One of the main point is that developers should not think about how this works. This should work out of the box and could be disabled if needed.
Rockey Browser Extension
This is the shortest features list that could be added to the extension:
- Change theme values without page reload
- Number of created and used components
- Number of undefined refs
- Number of undefined refs and unused animations
- Number of inserted CSS rules
- Manipulate with Service Worker cache
Conclusion
I can not put all what I want to talk about CSS in JS and rockey in one article.
This is a very new approach and library and not all features are implemented yet. Feel free to file issue or suggest feature to help me to make rockey better.
Upcoming plans:
- Make disadvantages list as shorter as possible
- Medium post “Rockey Under the Hood”. Topic about how rockey works — how to make batch CSS rules insert, how to parse and auto optimize parser, how dynamic CSS works.
- Medium post “Rockey — tips and tricks”. There are too lot of tips and tricks that I want to share with you.
- “Components kit” — library with easiest way to develop React components using rockey and recompose.
All used examples
- Card example
- Warning Card example
- Primary and Raised Blocks
- Buttons example
- Anonymous Buttons example
- Buttons look
- Button / PrimaryButton / SuccessButton with raised mixin
- Anonymous Extending: raised Button / PrimaryButton / SuccessButton
- Material TextField
- Input styles for specific value
- Div background depends on mouse X and Y
- Animated divs
- Themed Buttons
- look shortcuts
- Anonymous Buttons Extending
- Named Buttons Extending
- Buttons static look method
- Look: raised Button / PrimaryButton / SuccessButton