rstacruz / rscss

Reasonable System for CSS Stylesheet Structure
https://ricostacruz.com/rscss
3.91k stars 176 forks source link

Overuse of child selectors (>) make refactoring a nightmare #7

Closed localpcguy closed 8 years ago

localpcguy commented 9 years ago

I really want to love this, it is really close to many of the things I like. But there is way too much use of direct child selectors. The CSS should not be so heavily tied to the DOM structure, plus they increase specificity, which is best to keep as low as possible. Yes, you have to give up the potential of having nested class names, but that seems like an anti-pattern.

From the MDN section on efficient CSS:
Tag category rules should never contain a child selector Avoid using the child selector with tag category rules.

and

Question all usages of the child selector Exercise caution when using the child selector. Avoid it if you can.

While I know that specific article was written a while ago, there is still the argument that the child selectors too tightly ties to the DOM structure. Sometimes you may not have 100% control of the markup (i.e. when writing templates for a CMS) or you may want to refactor the HTML. Child selectors make that extremely difficult. I don't believe the benefits of using the child selector outweigh the maintainability issues using them presents.

rstacruz commented 9 years ago

Actually, that document was part of the reason why the > was recommended: it mentioned that descendants (.tree .branch .leaf) are very expensive. Things have changed indeed and they should be faster now.

One reason the > is recommended whenever possible is that it prevents "bleeding" the rules when you're nesting components together.

localpcguy commented 9 years ago

Speed for CSS selectors is absolutely not a concern these days, I would not sacrifice maintainability for a negligible benefit.

I realize the nesting is the main benefit, but again, I don't believe nesting like that would be good practice in the first place. On Feb 11, 2015 12:39 AM, "Rico Sta. Cruz" notifications@github.com wrote:

Actually, that document was part of the reason why the > was recommended: it mentioned that descendants (.tree .branch .leaf) are very expensive. Things have changed indeed and they should be faster now http://calendar.perfplanet.com/2011/css-selector-performance-has-changed-for-the-better/ .

One reason the > is recommended whenever possible is that it prevents "bleeding" the rules when you're nesting components together.

— Reply to this email directly or view it on GitHub https://github.com/rstacruz/rscss/issues/7#issuecomment-73836534.

darsain commented 9 years ago

I find it that when changing the markup of a component, some adjustment of styles is always necessary anyways, because markup and styles are related. This will be the case until we have a proper constraint-based layouts in the browser. And last time I checked the standards body rejected the notion of ever implementing that :/

So, I don't see a big hit to maintainability here.

localpcguy commented 9 years ago

I have had to refactor large projects that use child selectors and it can be a nightmare. If the class structure is setup appropriately, refactoring HTML (i.e. moving a template into a CMS, for example) should not require edits to CSS.

kmaida commented 9 years ago

A few years ago, I authored a custom WordPress theme and in its development, I relied fairly heavily on child and sibling selectors (>, ~, +). This year, the client returned to me and requested that I convert the site to responsive. It should have been a simple task, but the extensive use of child and sibling selectors made the process a nightmare-- and it was even code I'd written myself and was intimately familiar with. If I hadn't relied on those selectors, it would have been a very straightforward task. I've also run into this in the agency setting / workplace as well; I thought I was being clever and slick using those selectors, and found out through direct experience that they lead to code that is increasingly difficult to maintain over time. Now I stay away from them unless they are specifically appropriate (ie., in CSS dropdown menus). I wouldn't really go so far as to recommend that nobody uses them ever because there are certainly cases where it's appropriate and extremely useful; but I steer away from judicious use. Even if there is no "immediate" maintainability issue that you can foresee, things tend to come up in the fluid environment of web engineering, and sticking with classes when I've been occasionally tempted to rely on descendent and sibling selectors has never done me wrong. :)

localpcguy commented 9 years ago

Here is an (admittedly contrived) example of why this wouldn't work well in practice regardless of maintainability issues:

<div class="module-name"> <p>Stuff <strong class="highlight">strong</strong></p> </div>

The only way to target that while utilizing child selectors would be to do: .module-name > p > .highlight { … }.

Much better (simpler, lower specificity) is just: .module-name .highlight { … }.

johanalkstal commented 9 years ago

In this case, I think it's better to do it the BEM way by including the parent component name in some form in the component element class name.

.component-name

.component-name_element

Not as pretty though, I'll give you that.

denisborovikov commented 9 years ago

Some updates later you'll get your own BEM.

rstacruz commented 9 years ago

Some updates later you'll get your own BEM.

This exactly. rscss admittedly has its pitfalls, but .component--element will never happen in rscss because, well, you might as well be using BEM.

With that said, I'm considering revising the guidelines to only prescribe > for direct descendant elements (eg, .user-info > .name) and not any farther (.dashboard-menu > ul > li > a) to avoid maintenance hell. Comments welcome.

denisborovikov commented 9 years ago

I'm considering revising the guidelines to only prescribe > for direct descendant elements

In this case it's impossible to use components with more than one level of nesting.

localpcguy commented 9 years ago

In fact, nested components is the ONLY time you should recommend the > selector. Regardless, I'm happy to see this is under serious consideration, thanks! On Feb 17, 2015 4:57 AM, "Denis Borovikov" notifications@github.com wrote:

I'm considering revising the guidelines to only prescribe > for direct descendant elements

In this case it's impossible to use components with more than one level of nesting.

— Reply to this email directly or view it on GitHub https://github.com/rstacruz/rscss/issues/7#issuecomment-74642850.

rstacruz commented 9 years ago

@kmaida, thanks for chiming in with actual real-world experience on the issue!

mjio commented 9 years ago

I have been enjoying reading this guide, I always felt the BEM modifier syntax looks too blown up. However I soon run into the child selector issue. In my case I run into scenarios like the one from @localpcguy. I don't have a solution to it yet but I clearly don't want to use just a nested class.

ghost commented 9 years ago

I've found a selector performance test which checks android, io, chrome, firefox and ie. http://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/

Performance is no issue.

mcgwiz commented 9 years ago

@localpcguy the whole point of RSCSS, and of component-oriented CSS methodologies in general (such as BEM), is to trade away some of the capabilities of CSS in order to create stylesheets that are more resilient to change. The fundamental insight of these methodologies is that CSS value propagation mechanisms are too powerful and complicated: initial values, value inheritance, cascade by specificity, cascade by source order, and cascade by origin/author. Having all of these mechanisms in play at once, in a large codebase, is impossible to understand or predict.

The use of the child combinator in RSCSS is its central tactic in achieving resilience. It consciously disables two of the three types of CSS cascading (by specificity and by source order). (The third type, cascade by origin/author, is rarely used.) This greatly simplifies the mechanics of the stylesheets. This also has the advantage over BEM in that it's clear by looking only at the CSS how values are inherited between subjects. That is because the child selector forces the CSS to be organized around selectors that mirror the HTML and DOM structure. Again this is a feature, and it comes at the cost of selector "looseness", which is considered to do more harm (in terms of risks to resilience) than good. If you don't need these features, you don't want RSCSS.

As far as refactoring goes, component-oriented front-end development brings with it a more methodical way of refactoring. First, when re-organizing HTML structure within a component, the use of pre-processor rule nesting makes stylesheet maintenance a breeze. For example, say we need to wrap the title and text in the following my-component template with a new DIV:

<div class="my-component">
  <h1 class="title"></h1>
  <p class="text"><p>
</div>

CSS before:

.my-component {
  > .title { }
  > .text { }
}

In terms of source editing, all that we do is indent the middle two lines, and add the opening and closing lines of the enclosing rule.

.my-component {
  .wrapper {
    > .title { }
    > .text { }
  }
}

Likewise, moving an HTML subtree from one part of the template to another requires a predictable, simple equivalent movement of the corresponding LESS rule "subtree".

Second, the component-oriented approach indicates a methodical, predictable way to manage ever-increasing front-end complexity. Say our component template has evolved into:

<div class="my-component">
  <h1 class="title">
    <em class="subTitle"></em>
    <em class="status">
  </h1>
  <div class="content">
    <div class="author">
      <img class="avatar"/>
      <span class="name"></span>
      <span class="memberSince">
        <span class="date"></span>
      </span>
    </div>
    <p class="text"><p>
  </div>
</div>

(The CSS organization just mirrors this, so I'll omit the code sample--which is an inadvertent but nice demonstration of the predictability of RSCSS.) In order to keep this component from getting unwieldy, we can factor out the author subtree into a new component author-block.

<div class="my-component">
  <h1 class="title">
    <em class="subTitle"></em>
    <em class="status">
  </h1>
  <div class="content">
    <!-- (Code to render partial.) -->
    <p class="text"><p>
  </div>
</div>

<!-- New component. -->
<div class="author-block">
  <img class="avatar"/>
  <span class="name"></span>
  <span class="memberSince">
    <span class="date"></span>
  </span>
</div>

This is the magic of RSCSS. As our front-end accumulates complexity, RSCSS gives us a recursive way to keep it highly-cohesive and loosely-coupled, which is the foundation of maintainability. The child combinator is crucial to all of this.

rstacruz commented 9 years ago

@mcgwiz, thanks for your well-written insight.

localpcguy commented 9 years ago

@mcgwiz My entire point was kinda proven by your post - the specificity is what makes refactoring a nightmare, not the mechanics of refactoring. The mechanics are easy. Trying to mimic the "structure" of the front end HTML is a (admittedly, IMO) very bad idea. I also disagree that this keeps the CSS "loosely-coupled" - it actually very tightly couples the CSS and the HTML.

The ONLY advantage is the readability because it mimics the HTML, and as I already stated, I don't believe the disadvantages of that outweigh the small advantage gained in readability. (Personally, I don't even find it more readable, but I can accept that argument as valid.)

mcgwiz commented 9 years ago

@localpcguy thanks for the reply. Can you elaborate more specifically about what was nightmarish about the refactoring examples in my comment? I'm not seeing it. Some of these ideas, when used in isolation, are certainly questionable and run counter to general advice. But as a package of ideas, I find them to offer the best solution to the problems of large-scale CSS. (I've used almost the exact same methodology on several large projects.)

There are different schools of thought here and I don't want to speak on @rstacruz 's behalf, but in my view, the tight-coupling between HTML and CSS (and JS) follows directly from recognition that these artifacts each represent only a partial description of a common DOM tree, and that the particulars of a given tree can alter the interpretation of those descriptions. E.g. many CSS values are inherited down the tree; JS events bubble up the tree. The DOM therefore should be elevated in the source code, where both CSS and HTML conform to it, lest you obscure an important source of information.

Of course holding the structure, presentation and behavior of an entire website in the head at once is usually too much to handle (that's what I call the "large-scale threshold") so componentization supports dividing a site into cross-functional chunks with logical boundaries, e.g. a Login form vs a Footer vs a Header vs an Info Panel vs a Search Result Item and on and on and on. WebComponents is based on the same premise. This is in contrast to the few technological boundaries available, HTML vs CSS vs JS, which do little to methodically break down a codebase into small abstractions.

While component internals are tightly-bound, as are class implementation details in object-oriented programming (and module implementation details in general or functional programming), the components themselves are loosely-bound to each other. This makes them re-usable and composable with zero effort, and endlessly factorable with minimal effort. More importantly, it means that changes to the implementation details of one component have no effect whatsoever on others, and a team can therefore iterate rapidly and confidently as CSS regressions become much more difficult to introduce.

Large codebase and single page apps are where these traits really show their value. Elsewhere, not so much.

localpcguy commented 9 years ago

It appears we have completely different philosophy's regarding how tightly coupled the HTML/CSS should be, so we'll chalk it up to that, @mcgwiz. Personally, I find the more tightly coupled the HTML/CSS is, the hard it is to refactor, and that is from multiple project experiences. In the example you gave earlier - the very fact you have to change the CSS when adjusting the HTML is what I object to. I know that sometimes it's inevitable for major refactors, but adding a containing element shouldn't force you to touch both the HTML and CSS, IMO.

altsanz commented 9 years ago

IMO, using it on Angular projects where files are splitted in a component oriented way, it makes sense to have a high coupled html/CSS . Maybe the discussion here is related to how we build websites. El 13/5/2015 15:52, "Mike Behnke" notifications@github.com escribió:

It appears we have completely different philosophy's regarding how tightly coupled the HTML/CSS should be, so we'll chalk it up to that, @mcgwiz https://github.com/mcgwiz. Personally, I find the more tightly coupled the HTML/CSS is, the hard it is to refactor, and that is from multiple project experiences. In the example you gave earlier - the very fact you have to change the CSS when adjusting the HTML is what I object to. I know that sometimes it's inevitable for major refactors, but adding a containing element shouldn't force you to touch both the HTML and CSS, IMO.

— Reply to this email directly or view it on GitHub https://github.com/rstacruz/rscss/issues/7#issuecomment-101670107.

localpcguy commented 9 years ago

It really doesn't have anything to do with Angular. Even in a highly modular system though, I don't want tightly coupled HTML/CSS. And I believe that is the disconnect here. Personally, I also believe that is better "standard practice", but I'm fine with other disagreeing with that.

mcgwiz commented 9 years ago

@localpcguy yeah, I understand where you're coming from. I readily admin there's enormous convenience, flexibility and compactness in keeping HTML and CSS organization separate. It also feels "cleaner" or more "elegant" in a way. So writing that kind of CSS is enjoyable, but personally I limit it to small projects with little chance of growing (e.g. prototypes) because I find it becomes too unpredictable and I can rarely afford any extra risk.

You're right that it doesn't strictly have anything to do with MV* frameworks or SPAs. However, the object-orientation of, and arbitrary (theoretically unpredictable) recomposability allowed by, components written with those frameworks make using this "high-precision" style of selector construction extremely attractive with them.

gibatronic commented 8 years ago

Hi guys! That's something BEM already solves, and in conjunction with preprocessors it's awesome:

<div class="my-component">
  <h1 class="my-component__title"></h1>
  <p class="my-component__text"><p>
</div>
.my-component {
  ...
  &__title { ... }
  &__text { ... }
}

In this case, SASS will output a single css selector for each element. The source is visually nested, but the output is perfectly optimised.

rstacruz commented 8 years ago

For those who feel > is a bit too cumbersome, feel free to use BEM instead. As others have mentioned, BEM solves this in a practical way.

Also, RSCSS are guidelines and you're free to not use > if you feel it's too restricting for you! That's fine and in fact makes sense in certain circumstances.

> is going to stay in RSCSS. See below my rationale on it. At the end of the day, the pros still outweigh the cons. Thank you all for the discussion.


Deep components

With BEM, you're allowed to have deep components where the DOM subtree can go down many levels deep. While this is true in RSCSS as well, the dependence on the child selector (>) makes this impractical.

I view this more as a feature than a defect: it forces you to create new components for deep hierarchies, making this more manageable. RSCSS's use of > also makes it easy to visualize a DOM structure based on CSS styles, leading to self-documenting code.

/* BEM: ✓ allowed */
.site-search {
  &--icon { /*...*/ }
}
/* rscss: ✗ confusing impractical */
.site-search {
  > .top > .info > .label > .icon { /*...*/ }
}
/* rscss: ✓ better to create sub-components */
.site-search-label {
  > .icon { /*...*/ }
}