kentcdodds / ama

Ask me anything!
https://github.com/kentcdodds/ama/issues?q=is%3Aissue+is%3Aclosed
684 stars 75 forks source link

Does writing css in js really bring you many advantages? #132

Closed mattijsbliek closed 8 years ago

mattijsbliek commented 8 years ago

I read your tweet in which you say you don't care anymore about:

At the end you included a link to Aphrodite, which allows you to write css in your js components. I was hoping you could clarify some of your points, because I'm having a hard time seeing how Aphrodite is a better solution than something like Sass or PostCSS.

CSS Specificity This is (almost) never a problem when using a naming convention such as BEM, and defining styles on a per component basis. Of course there will be some global styles like typography, grid etc. But in my experience these rarely—if ever—conflict.

CSS Linters Could be I don't get what you mean here, but I don't see how linters are related to whether you use css in js or something else. To me all they do is enforce a consistent writing style, which I feel is important regardless of the technique used.

CSS Preprocessors It seems to me Sass allows much easier use of variables such as $primaryColor: #f00 to re-use across components than something like Aphrodite. Am I missing something here?

Post-processors like PostCSS even offer you the possibility of doing something like this:

:root {
  --box-padding: 20px
}

@media (min-width: 600px) {
  :root {
    --box-padding: 30px;
  }
}

Changing variables depending on the breakpoint is a very powerful feature, and keeps your site more consistent than having to add that breakpoint to all components that use a variable that changes.

CSS Vendor Prefixing Nobody cares about this anymore right? Whether you use Sass, PostCSS or Aphrodite, it's all auto-prefixed.

kentcdodds commented 8 years ago

Hi @mattijsbliek, Thanks for the question. Here's the tweet for those interested.

screen shot 2016-05-26 at 1 25 36 pm

CSS Specificity

I think BEM is great. But you have to enforce it to really benefit you at all. With aphrodite, I don't care/think about specificity. I don't have to follow a convention. I just use the technology and it works things out for me automatically and in a very explicit and straightforward way. And it's true that if I follow BEM I generally don't need to worry about specificity. But I've been in enough projects that "followed BEM" to know that it's really common for people (myself included) to not use BEM properly and then you start fighting specificity wars. The thing with aphrodite is that I don't have to care/think about it at all. That's what I like.

CSS Linters

The biggest bang for your buck on linters is just to make sure that the code will run (no syntax errors to mess up other style rules in the case of CSS). I already use ESLint for my JavaScript and it's great. By moving my CSS to JS, I benefit from my existing linter and don't have to worry about having an extra linter for CSS. Now, right now I don't know of anything that checks that your styles are using legit properties. This might exist though because CSS in JS has been popular for a while now. If you find an ESLint plugin for this, let me know!

CSS Preprocessors

I think this tweet from @markdalgleish sums up my feelings pretty well:

screen shot 2016-05-26 at 1 31 02 pm

JavaScript is a super powerful language and has great support for way more than you'd find in any made-up language like Sass, Less, PostCSS, Stylus, etc. Composing styles together with object spread ... (Object.assign) is a thing of beauty. Variables, well, yeah, it's JavaScript. There's nothing (that I'm aware of) that a preprocessor can do that I can't do simpler in JavaScript.

For your specific example with changing the value of variables through media queries, that'd likely require a bit of work with JavaScript, but aphrodite has a really nice API for media queries and I can accomplish a similar thing, in a different way, and it'd still be super awesome.

I should probably note here that I'm not a huge fan of JavaScript hacks to make things like media queries and/or pseudo elements/states work. Adding an event handler (even with delegation) is not my idea of a solid solution. But aphrodite creates actual style sheets. Which means that I can get all the benefits of CSS AND JavaScript together. And I can do it at runtime AND/OR build time. This is super awesome.

CSS Vendor Prefixing

Right. Remember, this wasn't necessarily a rub on any particular pre-processor or pre-processors in general. I'm just glad that we don't have to worry about this anymore!

Other thoughts

I should mention something else here. I've been thinking about what it would be like to build something like Bootstrap or Foundation using aphrodite. I can see pros and cons:

Pros:

Cons:

I'm mostly concerned with scaling component CSS in an application. So this doesn't concern me as much. I'm migrating JavaScriptAir.com to aphrodite and getting closer to 100% aphrodite. There will be some things that wont make sense with aphrodite (like reset styles). But that stuff isn't where we have scaling problems. It's mostly on reusable components.

So maybe aphrodite isn't the right tool for a CSS framework, but I'll bet you can't find something better for a component library (whether that be for React, Angular, or Ember). Providing style overrides is super straightforward too. It's just so simple.


I hope that helps answer your questions. If you haven't tried aphrodite yet, I recommend you give it a shot. It's great :-)

gc-victor commented 8 years ago

CSS Components, Elements and Utils

CSS Components, Elements and Utils proposal is based on concepts such as ITCSS, SUITCSS, OOCSS (Object-Oriented CSS), SMACSS, BEM and Atomic Design.

C, E and U are the prefix used for the CSS classes. Are used as a way to identify and localize the classes.

Utils are abstract, Elements are atomic and Components are complex.

Components Classes, has the purpose to give style to components. Most of the time are not necessary because of the use of Utils Classes. As there are not used inheritance of classes on the components, the double dash (--) is used to define elements.

Examples of such classes are the following:

.c-custom-select
.c-custom-select--inner
.c-custom-select--element
.c-custom-select--outer

Element Classes, has the purpose to give style to the atoms. The double dash (--) is used to define elements or modifiers when OOCSS is used.

Examples of such classes are the following:

.e-button
.e-button--success
.e-button--link
.e-icon
.e-icon--task-light
.e-icon--task-grey

Utils Classes, are used every time, normally with a single property. Their purpose is reduce the amount of CSS extending objects and components. The double dash (--) is used to define the values.

Examples of such classes are the following:

.u-float--left
.u-display--inline-block
.u-margin--small (medium/large/xlarge)
.u-color--a
.u-background--a

The goal is reduce complexity and repetition.

Further class prefixes for child elements of a component should start with the name of the component. For example a child class for the element '.c-tabs' would be '.c-tabs--element' or '.c-tabs--inner'.

Never use inheritance, being ideal always a 0010 specificity.

Using this paradigm not only affects the CSS. It is the architecture that should lead the way in developing the templating. Using a modular structure, regardless of the language used. Reusing the components as often as necessary, always bearing in mind principles like DRY(Don't Repeat Yourself) or KIS (Keep It Simple).

mattijsbliek commented 8 years ago

@kentcdodds I definitely see were you're coming from, and appreciate the benefits Aphrodite gives you. I don't want to go too much into specificity, there are ways to avoid those issues, but I get your point that not having to worry about is better than working around it.

I still have some concerns regarding css in js however, interested to hear your thoughts on these:

Styles become harder to trace and modify

Currently, if I find a styling bug in production I can very quickly trace that back to a certain class and fix it. By transforming your css into abstract classes this becomes impossible to do, and you need a dev environment with sourcemaps to see what's going on. Also the use of !important everywhere means if I'm hacking on styles in the browser I have to use !important there too, not very convenient.

Made up programming language and long term dependance

Well PostCSS isn't exactly made up, it's basically Babel for css. So you should be able to use less and less of it over time.

Build tools change all the time. I still have to maintain some projects running Grunt and even Compass. The further you abstract your code away, the harder it becomes to change tools at a later point in time. That's what I really like about post-processors in general, they're really easy to swap out if something better comes along. By writing your css in your js with this specific notation, you're locking yourself into Aphrodite.

Caching

This is actually kind of a big one for me, from the looks of it styles won't be cachable ever. Aphrodite renders an inline style tag to the html. Since in most situations it's preferable to not cache the html, or cache it with a very short expiry time, this means no caching for the css.

Pro's vs. cons

In the end, for me personally, the only big gains are avoiding specificity and tree shaking my css. The linter is nice to have, but given the huge stack of build tools that come with any SPA it's the least of my worries.

I think for avoiding specificity, prefixing classes with a unique string, or using the upcoming all property would be better solutions.

Which leaves tree-shaking. I'm wondering whether a tool like PurifyCSS wouldn't be sufficient here?

kentcdodds commented 8 years ago

Hey @mattijsbliek, You brought up great points. Here are my thoughts...

Styles become harder to trace and modify

This is true. Especially with regards to the inconvenience of the use of !important everywhere. That is a bit annoying. As for finding the styles, it's definitely nice to be able to have source maps and see exactly where the styles are defined.

But like everything else, it's a trade-off. When using normal CSS (or a pre/post-processor), there's this implicit dependency between the component and the CSS used to style it. Sure I can ask myself and my team to keep to our standards, but that is really hard to enforce and often doesn't happen. With aphrodite, I'm no longer looking for the CSS used to style a component, I'm simply looking for the component.

I now debug CSS situations just like I would debug markup or view logic situations. I find the code responsible for the component I'm looking at. Then I can see the explicit dependencies it has on styles. I feel like this is an improvement in my own workflow.

Made up programming language and long term dependance

Well PostCSS isn't exactly made up, it's basically Babel for css. So you should be able to use less and less of it over time.

Good point, and I should apologize for calling these languages "made-up" because it sounds derogatory to the creators and that's not at all my intention. PostCSS is definitely my favorite of the bunch. I should note that there are a slew of plugins for PostCSS that will likely never make it in the language (like the ability to have loops and nesting). But I don't have much room to talk here because I use a few Babel plugins that will never make it into the language because of convenience 😅.

This is another trade-off. I trade the flexibility to migrate to another CSS preprocessor for the flexibility to share my components with other applications (and open source them) in a way that's totally different and in many ways better. Providing style overrides now doesn't require people use the preprocessor of my choice or battling specificity. Instead they can simply pass in an object when using my API. This requires absolutely 0 tooling on their part, which I think is a big win.

I should note also that even though I can't migrate to another css preprocessor without friction, neither could I when I was using less and wanted to move to stylus and then to PostCSS. With each of these I had to change my CSS code drastically. Also, it's not like aphrodite is the only CSS in JS game in town. It just happens to be my favorite right now.

Caching

If this becomes a bottle neck for you application (and not before), then you can use Aphrodite's support for server-side rendering which works really well. If caching is important to you, then as part of your build you could have it generate a CSS file and serve that (and have it cached). Then instruct aphrodite to "rehydrate" your application when it loads (so you can have dynamic pieces) and then you're off to the races.

Pro's vs. cons

I'm wondering whether a tool like PurifyCSS wouldn't be sufficient here?

I've never used PurifyCSS before, but I probably wouldn't trust it if some of my CSS is used only when my application is in certain states (like when the delete modal is open for example).

Like I said, I don't have to worry about this. In CSS applications, nobody feels comfortable removing any CSS because tracking down dependencies is a manual process and often you get it wrong. So you wind up with a CSS file that's bloated with a bunch of stuff you don't need/use. With aphrodite, when you delete a component, you delete the CSS with it and don't even have to think about it.

I should have put - Unused CSS in my list of things I don't have to care/think about anymore in my original tweet. This is suuuuper nice. I realize that this shouldn't be a problem if you follow a "convention" like BEM, but like I said in any serious application I've worked in on a team of developers, that convention falls apart because it's just a convention.

I keep coming back to implicit vs explicit dependencies, but I think this is a big part of what makes aphrodite (or CSS modules for the most part) awesome. It's because the component using the CSS is now explicitly tied to the CSS it's using. This is a great thing because it makes the styling in your application so much easier to think about. Generally explicit dependencies leads to more maintainable code, and that's definitely been my experience.

If you're interested in why I don't just use CSS modules, listen to this.

I hope this is helpful!

mattijsbliek commented 8 years ago

I now debug CSS situations just like I would debug markup or view logic situations. I find the code responsible for the component I'm looking at.

This is indeed a big win. We rely on the convention that a css file = js/html module, so if you have button.js, there should be a corresponding button.css file. But as you stated, this is an implicit convention and hard to enforce.

It would be great if we could have that explicit coupling without losing the ability to easily tweak styles in the browser.

Instead they can simply pass in an object when using my API. This requires absolutely 0 tooling on their part, which I think is a big win.

I can see how this is very convenient. Having to overwrite stuff is often my main gripe when using third party modules that contain both js and css. For the css you often have to overwrite a bunch of styles, and are forced to deal with whatever naming convention the developer of the module decided on.

When working on internal code it's a problem I come across less often. I basically do the same, passing an object to a module, which triggers the appropriate css classes. Of course you do have to wire up some logic for this, but a few ternary operators usually suffice.

If this becomes a bottle neck for you application (and not before)

Unless you're only serving only a couple KB's of CSS per page, why not take advantage of caching? Easier on your users and your servers and great for performance :)

as part of your build you could have it generate a CSS file and serve that (and have it cached). Then instruct aphrodite to "rehydrate" your application when it loads

This is a really nice feature to have! I would actually prefer if it were flipped though. So the server inlines only critical css, and Aphrodite rehydrates the rest via an external stylesheet which can be cached.

If you're interested in why I don't just use CSS modules, listen to this.

I briefly looked in to CSS modules, but I feel it only solves the problem of scoping/specificity while requiring you to drastically alter your build flow. Also the way they use composes: ... is very error prone and relies on source order, I tried breaking it by and was able to do so on the first try. I would definitely prefer a simpler solution.

I hope this is helpful!

Absolutely! I think this is a very interesting discussion. We should definitely try new approaches and see what works and what doesn't :)

kentcdodds commented 8 years ago

It would be great if we could have that explicit coupling without losing the ability to easily tweak styles in the browser.

Yeah, there's a PR to expose an API to specify that you don't want !important on styles. That should solve that problem :-)

Unless you're only serving only a couple KB's of CSS per page, why not take advantage of caching? Easier on your users and your servers and great for performance :)

Definitely a good point. It's a trade-off for sure. But something that should probably be measured. How much CSS are you sending to users that isn't ever being used by them? With lazy-loading of JS (and with aphrodite, CSS) you can avoid sending it altogether. So yeah, definitely differs per use-case.

CSS modules

Yeah, that's not really something I considered. I honestly didn't use many of the features of CSS modules. But as I mention in that 3 minute podcast I referenced, I'm not a fan of how it requires a change in the build flow either. I reeeally like that aphrodite requires 0 build at all.

Thanks for chatting!

kentcdodds commented 8 years ago

@gc-victor, kind of rude of me to ignore you like that. Apologies. The reason that I didn't really address you though is because it appears that you're mostly interested in just pushing your own opinion and you didn't actually respond at all to what I said. And as this is my AMA and the question was directed at me, I wasn't quite sure how to respond to you.

I'll just respond by saying that it looks like you've got a cool convention there. But that's something that I don't even have to think about with aphrodite, and I think that's great.

marcusradell commented 8 years ago

Great debate you two! 👍

claudiordgz commented 8 years ago

@kentcdodds Woah I never have heard of Aphrodite before, it looks awesome. I've been using Polymer for a few weeks and I've been avoiding anything other than vanilla CSS. But this looks like it can work wonders. Thanks

alansouzati commented 8 years ago

this was a very constructive discussion that helped me a lot during my CSS in JS exploration. I use some of the topics described here as a reference in a recent blog post about my journey trying to use CSS in JS for component libraries:

https://twitter.com/alansouzati/status/763473912793600001

I would appreciate your feedbadk @kentcdodds and @mattijsbliek

I just think that component libraries has a big challenge when using CSS in JS that few people are exploring so far.

kentcdodds commented 8 years ago

Interesting post. Thanks for sharing. I think that the theming solution can be fairly easily solved in JS just using modules. You have a module that has all of your variables and expose a method for updating those. Then everywhere that needs to key off of that theme can just use that module. It's pretty straightforward :)

alansouzati commented 8 years ago

Totally agree with that. this is what I did in grommet-simple project, and I even created a theme library that uses that strategy: https://github.com/grommet/grommet-hpe-theme.

I have the modules with colors, fonts, and everything.

CSS-in-JS seems pretty promising, but the cons listed by @mattijsbliek is a reality for us as well.

Aphrodite is on top of my list of things to investigate in evolution of the CSS in JS in Grommet. :)

mattijsbliek commented 8 years ago

I agree with Kent that you can just expose all of your variables for theming. This gives you the added benefit that you’re not dealing with specificity conflicts between your base styles and your theme by overwriting selectors (e.g. Bootstrap specificity hell).

While CSS-in-JS is indeed promising, I find that all tools I've come across so far give up too much—in my opinion—on the CSS side to make it easier on the JS side. Either the form of syntax and/or by just applying a subset of CSS (no pseudo elements for instance).

This means everybody who touches CSS has to know what the tool does to their CSS before it ends up in the DOM, and how to reverse engineer a selector in the DOM to the corresponding CSS in a source file. Some tools make this easier than others, but it can still be quite tricky people less familiar with JS. One of the strengths of CSS is that it’s so easy to grasp and apply, it would be great if we could lower the complexity of the CSS-in-JS tools to make them less mind boggling for beginners.

kentcdodds commented 8 years ago

@mattijsbliek, there's definitely some area for improvement on the CSS in JS front, but I think that for the most part the issues are with familiarity, not easiness/simplicity. I think that aphrodite is quite straightforward with how it translates to CSS. There's not much searching around to find where the responsible code is for any given file. I don't know, I could have reached the level of my experience with it that makes it hard for me to empathize with newcomers though. I appreciate the discussion :)

mattijsbliek commented 8 years ago

@kentcdodds Yes that could very well be the case. I was looking into CSJS and it’s actually pretty close to what I'm looking for. However, I'm wondering if it would be possible to get rid of CSS-in-JS altogether and just write CSS files which get transpiled to something JS understands on import.

Right now Webpack already converts your styles to JS via the style-loader/css-loader, so wouldn’t the step where you can actually utilise that CSS in your JS be pretty small? That way your editor just sees a regular CSS file—which is great for syntax highlighting and auto-completion—and there’s no learning curve for newcomers.

Any thoughts on that?

kentcdodds commented 8 years ago

I'm wondering if it would be possible to get rid of CSS-in-JS altogether and just write CSS files which get transpiled to something JS understands on import.

I feel like that would eliminate everything that I like about CSS in JS. The thing I like about it is the fact that it is in JavaScript, a real language.

You could definitely write a loader like the css-loader to convert your CSS to CSS in JS, but I don't think that'd be useful. If you want to write CSS, just write CSS and use existing loaders.

mattijsbliek commented 8 years ago

It would explicitly couple CSS to JS, and make specificity issues a thing of the past. Those are the biggest problems I have right now. But I come from a CSS background, so I'm happy to keep writing CSS. It makes sense that different devs prefer different tools :) Thanks for clarifying!

Shepless commented 7 years ago

@kentcdodds I appreciate this thread is closed but I've been investigating CSS in JS and unless I've completely mis-understood I don't see the "no caching con" as a problem. If you're caching your JS with (for example) an MD5 hash query string (or some other approach) then the CSS is...well...cached, just within the JS?

Please let me know if I'm being a tool here. Thanks.