cssinjs / jss

JSS is an authoring tool for CSS which uses JavaScript as a host language.
https://cssinjs.org
MIT License
7.08k stars 399 forks source link

Generating and applying reusable single purpose classes rather than unique id classnames #830

Closed ComLock closed 6 years ago

ComLock commented 6 years ago

Is your feature request related to a problem? No, rather an idea.

Describe the solution you'd like So you may have heard about css tachyons, which is basically single purpose utility classes. Now remembering such tachyon names while programming is not what you want to do.

So while implementing my own html rendering library, I started generating such single purpose classes from style object in js. It also supports media queries. (I did not know about CSS-in-JS at the time). My code for generating css and classnames is spaghetti code :) So I was thinking what if a jss plugin could be written that did a better job than my code.

Example:

{
    display: 'flex'
}

Becomes:

.d-f{display:flex}

This means that any element that uses flex will simply have the class "d-f". The size of css becomes minimalistic, while the number of classes per element grows a bit ;)

There are more advanced examples here:

Perhaps there is some other framework (that I could not find) plugged into react that already does this? It would be nice to not be limited to React and do SSR.

Perhaps even compile time (though thats not to applicable for dynamic components where styling changes depending upon props).

Are you willing to implement it? Yes, but can I :)

HenriBeck commented 6 years ago

You could do that by just providing a custom createGenerateClassName.

Docs: http://cssinjs.org/js-api?v=v9.8.7#generate-your-own-class-names

Quick question: Why don't you generate those styles and simply export the classes object?

styles.js

const stylesheet = jss.createStyleSheet({
  dF: { display: flex },
});

stylesheet.attach();

export default stylesheet.classes;

index.js

import classes from './styles';

// Use classes like classes.dF

You could even strictly type the classes actually.

const styles = { dF: { display: 'flex' } };
const stylesheet = jss.createStyleSheet(styles);

stylesheet.attach();

const classes: {| [key: $Keys<typeof styles>]: string |} = stylesheet.classes;

export default classes;
kof commented 6 years ago

I think you are talking about atomic css. I think you could achieve that with a plugin. The question is why? Usually with this model people try to reduce css payload. Also it allows a different caching strategy. At the same time it brings a bunch of problems.

ComLock commented 6 years ago

@kof I guess its similar to atomic css, but I certainly do not like writing classnames for styling, I write style objects in js and generate css and classnames from it. No unused classes and low specificity is the result.

@HenriBeck Its the other way around. The stylesheet is generated from component style, not component style from predefined stylesheet. The developer never writes df, and should not care much about what the final classname becomes. The developer should also not need to think about vendor/browser prefixing. In case of conflicting frameworks I guess supporting prefixing could be a good idea.

ComLock commented 6 years ago

@kof "At the same time it brings a bunch of problems." Any writeup of this. Very interested in knowing what those are.

kof commented 6 years ago

Yes, its atomic rendering, but user doesn't have to care about it. I was hesitating adding this plugin, because of the debugging problems it would bring. It needs at the same time some linting tools.

ComLock commented 6 years ago

@kof In terms of caching you are correct. Since the css is so small I typically inline it. So there are fewer requests. Reduces time to first paint on first visit, but doesn't affect future requests too much.

ComLock commented 6 years ago

@kof Since everything is generated the arguments here are not so hard hitting: https://medium.com/simple-human/the-problem-with-atomic-css-d0c09c7aa38e

ComLock commented 6 years ago

@kof "I was hesitating adding this plugin"

Which plugin? render-js is not a jss plugin. So you probably meant something else.

kof commented 6 years ago

I ment a plugin for jss that would do this, I can write a bunch of things that are going to be a problem, but I will invest into this discussion only if someone is going to do it.

trusktr commented 6 years ago

@ComLock If I understand what you mean, then instead of thinking about class names, you can think in JS variable names and just compose objects:

const red = {color: 'red'}
const bold = {fontWeight: 'bold'}
const df = {display: 'flex'}

const style = jss.createStyleSheet({
  myEl: { ...red, ...bold, ...df }
}).attach()

const div = <div className={style.classes.myEl} />

@kof, if you make jss.createRule() return an object which is instance of String with a className prop, and if jss.style was alias for jss.createRule, it would become easier to "inline styles":

const style = jss.createRule({ ...red, ...bold, ...df })

const div = <div {...style} />
// or
const div = <div {...jss.style({ ...red, ...bold, ...df })} />
// or
const div = <div className={style} />
const div2 = <div className={'other '+style} />

Also see TypeStyle, it makes excellent use of the pattern of mixing simple pieces of CSS, as well as making style sheet objects!

kof commented 6 years ago

@trusktr I was also thinking of something like this, this interface was initially introduced by glamor. The problem with current createRule is that toString is already implemented and it returns a CSS string of the rule, not just a class name. It is a logical choice and I can't break it.

kof commented 6 years ago

There is also glamor like interface on top of JSS https://www.npmjs.com/package/glamor-jss it has some quirks but can be used and fixed.

trusktr commented 6 years ago

@kof Maybe then jss.style could just be an alternative function? Also, if it receives multiple objects or falsey values, then it'd be even more concise:

// NICE! And still with JSS unique naming and SSR, and even caching!
const div = <div {...jss.style(red, active && bold, df)} />
trusktr commented 6 years ago

There is also glamor like interface on top of JSS

Ah, cool!

Another idea: maybe jss itself could be a function?

const div = <div {...jss(red, active && bold, df)} />
kof commented 6 years ago

Whats the problem with using a separate package like glamor-jss?

I see JSS core as a lower level interface, it does not need sugar APIs, it needs correctness, customizability and performance. Everything else can be built on top.

trusktr commented 6 years ago

Whats the problem with using a separate package like glamor-jss?

Nothing at all. But it's easier to import 1 package than 2, especially if if we don't know about the second package, then people will like JSS more out of the box.

I see JSS core as a lower level interface

Could be true, but honestly I never saw it that way. From the beginning I thought, "cool, a lib that let's me do clash-free CSS in JS easily with jss.createStyleSheet! instead of "cool, I'm going to make another CSS lib on top of this".

I'd prefer to just have some bonus features in the lib I was already using.

kof commented 6 years ago

It has evolved, there are many different interfaces now which use the core. Not knowing something can be fixed with better documentation and landing page. You can as well help here.

trusktr commented 6 years ago

True, it could be as simple as adding glamor-jss to the "Abstractions on top of JSS" section

kof commented 6 years ago

Its there since it exists. Also added on the first readme but not published to cssinjs.org because we are in migration to monorepo.

trusktr commented 6 years ago

Oh okay, I see it in the Projects page. But where do we edit the menu for cssinjs.org to add it to the main menu? Or do you want only cssinjs org projects there?

kof commented 6 years ago

I think we can close this issue since it is not really actionable. The idea of rendering to atomic css is not new and if someone finds time I am happy to restart this discussion.