vercel / styled-jsx

Full CSS support for JSX without compromises
http://npmjs.com/styled-jsx
MIT License
7.71k stars 261 forks source link

Using styled-jsx to merge traditional CSS with modern JS?? #394

Closed KingScooty closed 6 years ago

KingScooty commented 6 years ago

So i really want to get into using CSS in JS, but i really feel a lot of the time it's an "all or nothing" solution to a problem that mostly goes away if you just write well structured, namespaced CSS. It's great for components like a "button", but gets quite clunky when doing bigger things. Things that would be quite fast to write in vanilla HTML and CSS end up needing a React component and then tests, and it just gets a bit too unnecessarily abstract (like grids etc).

I'm a big fan of NextJS. And normally i include a base.css full of reusable patterns (written in Sass) in the head, and then start writing my app—only really using CSS in JS (styled-jsx) to enrich the patterns used in my base.css.

But a lot of the time, 50% of that css file goes to waste. It's not used in the app, which results in unnecessary downloaded bytes. This is why CSS in JS appeals to me, as you get dead code elimination for free.

I'd really like to convert my CSS pattern library (a group of reusable CSS objects, that just structure content, and don't add any visual styling like colours etc) to use something like styled-jsx. The idea would be if i exported each block small enough, I could just import the sections each component needed and then i'd get a ton of dead code elimination as i visit each page.

I'm trying out a few things, and would really like someone's input on whether i've completely lost the point with this CSS in JS stuff, or whether there's some sense in what i'm doing.

Here's an example of a CSS pattern called the block pattern that i've attempted to port over.

import css from 'styled-jsx/css';
import { globalSpacingUnit, spacingUnit } from '../settings/settings';

/* ==========================================================================
#BLOCK
========================================================================== */

/**
 * Stacked image-with-text object. A simple abstraction to cover a very commonly
 * occurring design pattern.
 */

export base css`
.o-block {
    display: block;
    text-align: center;
}

.o-block__img {
    margin-bottom: ${globalSpacingUnit}px;
}

.o-block__body {
    display: block;
}
`;

/* Size variants
====================================================================== */

export const flush = css`
.o-block--flush > .o-block__img {
    margin-bottom: 0;
}
`;

export const tiny = css`
.o-block--tiny > .o-block__img {
    margin-bottom: ${spacingUnit.tiny}px;
}
`;

export const small = css`
.o-block--small > .o-block__img {
    margin-bottom: ${spacingUnit.small}px;
}
`;

export const large = css`
.o-block--large > .o-block__img {
    margin-bottom: ${spacingUnit.large}px;
}
`;

export const huge = css`
.o-block--huge > .o-block__img {
    margin-bottom: ${spacingUnit.huge}px;
}
`;

/* Alignment variants
========================================================================== */
export const right = css`
.o-block--right {
    text-align: right;
}
`;

export const left = css`
.o-block--left {
    text-align: left;
}
`;

This could then be consumed in a component like so:

import React from 'react';
import { base, small, right }  from '../jss/objects/block';

export default () => (
    <div>
        <style jsx global>{base}</style>
        <style jsx global>{small}</style>
        <style jsx global>{right}</style>
        <div className="o-block o-block--small o-block--right">
            <div className="o-block__img">
                <img src="https://placeimg.com/400/200/animals" alt=""/>
            </div>
            <div className="o-block__body">
                <p>This is some text.</p>
            </div>
        </div>
    </div>
);

I see these patterns as global styles. Then custom styling like colours can be scoped on the component.

What i notice straight away is there would be a hell of a lot of style tags if i did this. Is that scalable? Or am i going to hit problems?

Have i got the right idea? Or am i using styled-jsx wrong?

Another way i could do it is be a bit more clever with the composition of my styled jsx rules, as each of the variants in that css-jsx file depend on the base class/variable. So is there a way i can merge the rules?

Example:

export const base = css`
.o-block {
    display: block;
    text-align: center;
}

.o-block__img {
    margin-bottom: ${globalSpacingUnit}px;
}

.o-block__body {
    display: block;
}
`;

/* Size variants
====================================================================== */

export const flush = css`
{ ...base } /* <--- Some way to describe css class dependencies?? */
.o-block--flush > .o-block__img {
    margin-bottom: 0;
}
`;

flush requires base in order to work. I'm keen not to lose my syntax highlighting by removing the css function call, so it would be cool if i could say that flush requires base as a dependency.

That way, i'd only need to do:

<style jsx>{flush}</style>

Instead of:

<style jsx>{base}</style>
<style jsx>{flush}</style>

What are people's thoughts?

I'm aware styled jsx is string based, and not object based (is that still true?), but if the styles are broken down small enough, importing just the bits you need should still be really beneficial.

I can definitely see that there's power in CSS in JS, but i'd really like to merge old world with new world. Traditional CSS enriched with JS to take advantage of the added benefits without losing the flexibility of writing raw HTML and CSS.

a-ignatov-parc commented 6 years ago

Are you sure you really need css-in-js? If you trying to solve the problem of unused styles elimination than I think you should check react-universal-component first. Especially how they handle styles splitting.

In my opinion, css-in-js is useful when you need dynamic styles based on theme or component's props. If you need to encapsulate your styles then CSSModules is to the rescue. Otherwise, all these tools can be overengineering 😉

KingScooty commented 6 years ago

Are you sure you really need css-in-js?

Ha! Well this is it. I don't know! 😄 I've never really thought too much about it, but it seems to be all the rage at the moment, so i thought i'd explore what it does, and what it's good at. The problem it solves is quite particular. To me it really makes sense to be able to import just the bits of css you want for a particular component. Similar to how a dependency in JS would work. CSS would really benefit from being object based, so tools can intuitively strip out what's not being used.

And when i say CSS, i mean reusable patterns. Not styling like colours. Just underlying structure. It's the same code repeated all the time. So it kinda makes sense to have as an npm package with sensible exports that you can just import.

I think you should check react-universal-component first.

I'll look into react-universal-component! I've not seen that before. I wonder if it'll integrate nicely with nextjs?

In my opinion, css-in-js is useful when you need dynamic styles based on theme or component's props. If you need to encapsulate your styles then CSSModules is to the rescue. Otherwise, all these tools can be overengineering 😉

Yeah, i tend to agree! The encapsulation is nice, but the dead code elimination is really what tempted me to try it!

giuseppeg commented 6 years ago

Hi @KingScooty styled-jsx is string based only and composition as you want it is not possible because of the way how we transpile our styles (it might be possible though in the future with this syntax https://github.com/zeit/styled-jsx/issues/383#issuecomment-358287330).

The cost of external styles right now is double see https://github.com/zeit/styled-jsx/issues/345 We will fix that in v3.

Doing dead code (CSS) elimination with string based libraries is not that easy and can only be done for colocated and static styles only. That said when you use something like Next.js that does automatic code splitting for you having CSS in JS modules is nice. For completeness you could achieve the same using CSS Modules and including a common.css and page-name.css (page specific) bundles, but if you have ajax navigation etc you'd then need to add/remove link tags on page transitions (I've built apps like this in the [very] past).

FYI styles in styled-jsx are de duped, meaning that the styles for .o-block will render only once in your page (even if you have multiple instances of the consumer component).

Looking at your examples I would say that maybe you can make a component library rather than importing the styles all the time. For instance I would use a Block component:

export default () => (
  <Block size="small" align="right">
    <Block.Image>
      <img src="https://placeimg.com/400/200/animals" alt="" />
    </Block.Image>
    <Block.Body>
      <p>This is some text.</p>
    </Block.Body>
  </Block>
)

// or

export default () => (
  <Block size="small" align="right">
    {{
      image: <img src="https://placeimg.com/400/200/animals" alt="" />,
      body: <p>This is some text.</p>
    }}
  </Block>
)

Open to discuss on the merits of Object literals based CSS-in-JS solutions (something like React Native for Web's StyleSheet is next level imho).

KingScooty commented 6 years ago

Hi @giuseppeg! Thanks for the reply! #383 looks interesting! It would certainly add more power to styled-jsx.

I think you're right regarding building a component library as opposed to importing styles. The more i've played with various CSS in JS implementations, the more i've realised what i was doing was very unconventional.

I really like that syntax in your first example. I've not seen that before:

export default () => (
  <Block size="small" align="right">
    <Block.Image>
      <img src="https://placeimg.com/400/200/animals" alt="" />
    </Block.Image>
    <Block.Body>
      <p>This is some text.</p>
    </Block.Body>
  </Block>
)

How are those children blocks defined? (<Block.Image> etc.). If you could show an example of what the definition of this component looks like that would be amazing.

giuseppeg commented 6 years ago

@KingScooty not sure if that's the best pattern but I would implement that like this https://codepen.io/giuseppegurgone/pen/bLpaGW

KingScooty commented 6 years ago

@giuseppeg you're a star! Thanks so much for that example! I'd actually been trying out some of @jxnblk's tools this weekend, so it's nice how this fits in with Dan's comments over at macro-components!

giuseppeg commented 6 years ago

Nice! I am going to close this issue but feel free to reply here if you need further help/want to brainstorm.