webark / ember-component-css

An Ember CLI addon which allows you to specify styles for individual components
MIT License
540 stars 115 forks source link

ember-component-css 1.0 game plan #168

Open ebryn opened 8 years ago

ebryn commented 8 years ago

Note: This issue is a WIP, it will be updated over time


I wanted to start collecting my thoughts and gather feedback from the community for ember-component-css 1.0.

Here are some of the high level plans:

I think this is self explanatory πŸ˜›

Auto-BEM

ember-component-css currently works via automatically nesting your rules underneath a generated class. This was easy implementation wise, but it was never my intent for that to remain the primarily implementation strategy. I instead would like to switch to auto-BEM'ing rules (see http://getbem.com/naming/ for more info). Using BEM naming ensures styles don't leak outside of the component's template.

Here's an example showing input and current / future output.

input:

.foo {}

current output:

.my-component .foo {}

future output:

.my-component--foo {}

(there will probably also be a hash included in the class name)

Linting

We should devise a set of best practices and warn folks when they've gone off the happy path. Things that come to mind are using IDs and unsafe descendent selectors.

Shadow DOM forward compatibility

I would like users to be able to turn off auto-BEM'ing if they're shipping to browsers that support shadow DOM. I would also like to explore being able to generate individualized component CSS builds that can be loaded into component's shadow roots.

I don't think I want to polyfill any Shadow DOM functionality, instead I just want to make sure we're not making any decisions that make it hard to take our styles in the future and use them inside of a shadow root.

JS module API

It would be nice to be able to import CSS files from inside of JS files, ala css-modules. This can be useful for accessing the rewritten/generated class names for usage within JS code.

addyosmani commented 8 years ago

I would like users to be able to turn off auto-BEM'ing if they're shipping to browsers that support shadow DOM.

Feel free to reach out if there's input on Shadow DOM learnings that might be helpful here. My understanding (correct me if wrong) is ember-component-css does build-time namespace scoping but for browsers with SD you might have external stylesheets scoped to the shadow tree:

#shadow-root
  <!-- Available in Chrome 54+ -->
  <!-- WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=160683 -->
  <link rel="stylesheet" href="ember-component-styles.css">
  <!-- internals of your Ember component -->
ebryn commented 8 years ago

@addyosmani yep, my thoughts are at build time we could potentially inline the component styles into the shadow root via <style> or generate an actual css file per component and use <link>

mellatone commented 8 years ago

Would there be any performance benefit to uglification?

davewasmer commented 8 years ago

Awesome stuff @ebryn - long time user of ember-component-css, appreciate all the work so far!

Some general feedback - I'm not really using shadow DOM stuff yet, nor have I found a huge need for accessing CSS class names inside JS (with perhaps the notable exception of dealing with wormholes, but there are acceptable workarounds for that). Not knocking additional functionality/support along those lines (I'm sure it would be awesome), just offering a data point for your consideration.

We should devise a set of best practices and warn folks when they've gone off the happy path. Things that come to mind are using IDs and unsafe descendent selectors.

I'm a big fan of linting across the board, but this feels a bit orthogonal to the functionality of ember-component-css. If I already use ember-cli-scss-lint - why would I need to use ember-component-css linting instead?

I instead would like to switch to auto-BEM'ing rules

Any chance you could elaborate on how this would work? Would there still be a random ID as part of the component scoping class? How would templates end up with the correct class names (since it seems like this would no longer rely on descendent selectors, which means the individual elements would need to include their parent component name in their classes).

As one other data point, although perhaps a less popular one: I'm personally not a fan of BEM. I know CSS methodologies lend themselves to bikeshedding, and I won't argue BEM is a bad choice for everyone, but enforcing BEM would be a dealbreaker for me at least. Ideally, I'd be able to disable BEM, or pick a "strategy" for how scoping should happen.


One last thanks - this addon has improved my development experience more than probably any other single addon. Thanks for all your work!

ebryn commented 8 years ago

@mellatone there could be a file size reduction win, i'm interested in exploring these kinds of optimizations

@davewasmer i'd like ember-component-css 1.0 to become a default package in ember-cli and i believe that adding css linting should be there by default as well. whether it's implemented directly in ember-component-css or another package isn't that important to me but the reason i think of them together is that ember-component-css will introduce some constraints and conventions so the linter needs to be acutely aware of those.

regarding auto-BEM'ing, the idea is we'll automatically add a namespace prefix your css classes and also rewrite your templates as well, so you can use the original unnamespaced name while authoring.

BEM itself isn't the high order bit, it's just that you need some naming convention that is used to avoid naming conflicts across components. i'm open to hearing other suggestions for "strategies" but honestly i don't think anyone should really care, it's going to be something that is automated for you underneath the hood, you get to write the naively named unnamespaced classes and the system makes sure they're globally unique for you.

davewasmer commented 8 years ago

also rewrite your templates as well

How would this work with dynamic class names, i.e. <div class="{{currentStateClassName}}">?

and the system makes sure they're globally unique for you.

In a pods/unified modules structure, how would this handle "cousins" with the same name? I.e. pods/foo/my-component and pods/bar/my-component? Would the namespace include all parent directories, i.e. foo-my-component--some-child-element? Or would each component's namespace continue to include a UID, i.e. my-component-ad81gk--some-child-element?

i'm open to hearing other suggestions for "strategies" but honestly i don't think anyone should really care, it's going to be something that is automated for you underneath the hood

Totally agree. If it's transparent to the developer, then it shouldn't really matter much (my "dealbreaker" comment was based on the assumption that I would be forced to write BEM syntax myself as a consumer of this addon). One small argument in favor of multiple strategies might be the different behaviors supported. Personally, I would enjoy the BEM approach solely for it's ability to prevent CSS from leaking into child components. But others may want that behavior. But that may be more work than is worthwhile for a potentially small percentage of users.

alexlafroscia commented 8 years ago

How does ember-component-css differentiate itself from ember-css-modules? Especially with "auto-BEM" it seems like the goals are really closely aligned; map a CSS file to a module, and have those styles scoped to that module.

One differentiator used to be the fact that this addon scopes with a namespacing class in front of the selectors, but with an auto-BEM approach that would no longer be the case. Right now, ember-component-css can support Sass/Less/etc. while ember-css-modules is powered by PostCSS and can leverage arbitrary PostCSS plugins, but has support for being used with other preprocessors too. Again, it seems like the goals are aligned with just a different approach taken.

I guess what I'm getting at is... Is there good reason for not trying to unify things here? Having a single solution for isolating styles at the component/view level seems like it would be more closely aligned to the Ember spirit of supporting one approach to solving these kinds of problems.

ebryn commented 8 years ago

How would this work with dynamic class names, i.e. <div class="{{currentStateClassName}}">?

We'll transform it to a helper call that prefixes the dynamic class names. We're discussing our opt-out strategy still, but we've been knocking around the idea of having a sigil like ! mean "do not prefix." For example: <div class="!foo"> or a dynamic class name assignment of the value !foo would not be prefixed.

In a pods/unified modules structure, how would this handle "cousins" with the same name? I.e. pods/foo/my-component and pods/bar/my-component?

I haven't decided on the exact namespacing algo, but I'm open to any suggestions. My initial thought is we'd use component name and some unique hash. Not sure we want to prepend the directories in the case of pods.

ebryn commented 8 years ago

@alexlafroscia Good question. I would say ember-component-css doesn't prescribe to any existing CSS module system design as I haven't seen one that satisfies all the design constraints that I've had in mind.

I'd love to unify, but I don't know that it's going to be possible. @dfreeman was an early contributor to this project but ended up creating ember-css-modules. At the moment, I believe most of the Ember core team believes Sass is the preprocessor we should focus our support on, so a PostCSS-based solution is unlikely to succeed in becoming a default in Ember CLI.

davewasmer commented 8 years ago

We'll transform it to a helper call that prefixes the dynamic class names

I assume the helper/transform would handle things like currentStateClassNames containing multiple class names (i.e. "foo bar"), as well as things like <div class="foo-{{bar}}">?

How would you handle something like trying to add styles to a jQuery plugin? I.e. my date-picker component uses some jQuery lib to render a date picker, and I want to style some deeply nested element inside that? Would I need to wrap it in a container element whose class would get rewritten, thus allowing me to use descendent selectors off that container (i.e. .my-component--jquery-wrapper .some-class-inside-the-plugin)?

We're discussing our opt-out strategy still, but we've been knocking around the idea of having a sigil like ! mean "do not prefix."

My gut reaction here is πŸ‘Ž - this feels like yet another magical Ember syntax that has no relationship to any standard (that I'm aware of at least), similar to stuff like <div {{bind-attr class="foo::bar"}}> from the early days. If I had to choose between the scoping benefits of BEM style namespacing with an opt-out sigil, and descendant selector namespacing with no opt-out / template rewriting, I'd choose the latter. But once again, just one data point for consideration.

webark commented 8 years ago

in master there already is the componentCssClassName computed property which is the dynamic class name. Although I got the syntax wrong with the BEM tests (should update these) there already are tests using this in master allowing for you to do something like

<span class="{{componentCssClassName}}__element"></span>

& {
  &__element {
    color: rgb(0, 0, 4);
  }
}

we could add a helper that would let you do <span class="{{BEM 'element'}}"></span> or just prefix any class that starts with two underscores with the class name in the templates.

ebryn commented 8 years ago

I assume the helper/transform would handle things like currentStateClassNames containing multiple class names (i.e. "foo bar"), as well as things like <div class="foo-{{bar}}">?

Yes :)

How would you handle something like trying to add styles to a jQuery plugin?

This is where the JS API (that you didn't think you needed :wink:) comes into play, you'd be able to import your CSS module and get access to the rewritten class name.

My gut reaction here is πŸ‘Ž - this feels like yet another magical Ember syntax that has no relationship to any standard

I understand your POV, and trust me I'm pretty gunshy on inventing syntax. I've spent hours and hours thinking about it. My initial thought was that we'd magically transform only the classes that exist inside of your component's stylesheet, but that's a pretty tricky thing to pull off right now with Ember CLI's build pipeline. An obvious alternative would be to use a helper or alternative attribute to opt-out (eg. <div class="{{g "foo"}}"> or <div global-class="foo">, but those are noisier. I'm trying to find the right balance so folks feel like they can iteratively adopt ember-component-css inside of an existing app.

webark commented 8 years ago

and @alexlafroscia I think two are somewhat in tandem. https://github.com/salsify/ember-css-modules you need to be a little more explicit, using the local-class attribute, and with inheritance, which this project hasn't gotten to yet, they chose to go the composes route. I personally find those two strategies somewhat cumbersome, but agree, it would be nice if the two could be unified in some way.

oligriffiths commented 8 years ago

@ebryn What about something this:

&.bem {
  // all styles in here get BEM'd, .component-name-asd123--foo
 .foo {}
}

// all styles outside are regular prefixed styles .component-name-asd123 .foo
.foo {}

then in template or component.js

<div class="bem">

or

classNames: ['bem'],

The bem part could all be removed at build time. And in addition, if a auto-bem config option were introduced, people could opt in to not needing the .bem class.

ebryn commented 8 years ago

@oligriffiths One important objective is to be able to write component CSS that is forward compatible / transferrable to Shadow DOM without significant modification of the CSS or template source. I don't think your proposal satisfies that objective.

oligriffiths commented 8 years ago

@ebryn Good point :/

davewasmer commented 8 years ago

I'm trying to find the right balance so folks feel like they can iteratively adopt ember-component-css inside of an existing app.

Makes sense - that's a tough balance to strike. I'd personally favor the helper approach to a custom syntax, but I see arguments both ways.

This tension seems to stem from the need to rewrite the templates, which itself stems from the choice of BEM-style namespacing to achieve CSS scoping (rather than the current descendant selector approach). Are there reasons to want the BEM-style namespace other than it's ability to prevent CSS from leaking into child components?

ebryn commented 8 years ago

Are there reasons to want the BEM-style namespace other than it's ability to prevent CSS from leaking into child components?

This is the primary motivation. Another is enforcing the inability to use CSS defined in a component's stylesheet outside of that component. These things will provide an easier on ramp to Shadow DOM as well.

webark commented 8 years ago

BEM'ing also doesn't even have to happen in this addon. You could write an addon to auto BEM your templates, and if it was able to pull the root class name of the component off of a computed property that you where able to define, you could just alias that property to already existing 'componentCssClassName' property.

davewasmer commented 8 years ago

@webark but the key value prop of this addon (for me at least) is exactly that - the namespacing (effectively, the scoping) of CSS. Not sure what this addon would do if not that.

@ebryn - makes sense. I think I'd still favor the current descendant selector approach over the problems introduced by BEM style namespacing, but I understand the motivation to align more closely with future standards.

webark commented 8 years ago

@davewasmer totally.. right now you could do

<span class="{{componentCssClassName}}__element"></span>

with the scss of

& {
  &__element {
    color: rgb(0, 0, 4);
  }
}

and the html/css would end up as

<span class="__your__component__name__a34ef__element"></span>
.__your__component__name__a34ef__element {
  color: rgb(0, 0, 4);
}

What I was saying was, you could write an addon (if one doesn't exists) that rather then needing to have the {{componentCssClassName}} in there you could just write the html as

<span class="__element"></span>

all it would have to do is be able to alias componentCssClassName to what it uses for it's "root BEM identifier"

dfreeman commented 8 years ago

@ebryn I suspect we each still have some specific design goals that don't line up, but as @alexlafroscia points out, the two addons start to look a little less different with some of the changes outlined here πŸ˜ƒ

I would say ember-component-css doesn't prescribe to any existing CSS module system design as I haven't seen one that satisfies all the design constraints that I've had in mind.

Are those constraints documented anywhere? I'd love to be able to see what kinds of "musts" and "must nots" you have in mind for a perfect styling solution.

I believe most of the Ember core team believes Sass is the preprocessor we should focus our support on

Is existing market share and familiarity within the Ember community as a whole a primary motivator for that? And/or are there specific features of Sass you and the rest of the core team find particularly compelling?

webark commented 8 years ago

@dfreeman @ebryn I think a shared set of design constraints would be awesome. Would it be worth while to define and refine these somewhere, and then propose them in an ember rfc to cast a larger feedback net?

musaffa commented 7 years ago

I completely agree with @webark. Here are my pain points with ember-component-css and ember-css-modules. I like both of them, but unfortunately I cannot use them right now.

I use Emblem which provides a Haml like syntax for Htmlbars. So instead of this:

<div class="container">
  <p>..</p>
</div>

I will write the template like this:

div.container
  p
// or
.container
  p

I cannot use ember-css-modules because it requires non standard local-class instead of standard class. In ember-component-css, I need to use string interpolation to fetch component's local class. It looks like they are trying to solve the same problem with just different syntax. What could be the solution to this problem? Perhaps we need make every class declaration local inside a component template. The standard class attribute will only refer to the classes declared in the associated component css. If one wants use a global class, that class should be explicitly imported first to make available for local use.

webark commented 7 years ago

@musaffa you should be able to use this project with emblem easily. you could target that container class by just doing a

.containter { color: blue; }

in a style file. If you need the computed class for some reason, that's found under the computed property of componentCssClassName.

But maybe you have a use case that i'm not flowing.. :/ Would you mind elaborating a little more?

musaffa commented 7 years ago

For this template:

<button class="{{componentCssClassName}}__button">
  Normal button
</button>
<button class="{{componentCssClassName}}__button {{componentCssClassName}}__button--state-success">
    Success button
</button>
<button class="{{componentCssClassName}}__button {{componentCssClassName}}__button--state-danger">
    Danger button
</button>

I want to write like this:

/ emblem
button.button Normal button
button.button.button--state-success Success button
button.button.button--state-danger Danger button

Where button, button-state-success and button--state-danger are local to the component. Yes I can do interpolation with componentCssClassName but this will make the template ugly. :disappointed:

mikkopaderes commented 7 years ago

Should we be looking at other symbols besides ampersand? The & keyword looks like it'll be used for CSS nesting.

buschtoens commented 7 years ago

I believe most of the Ember core team believes Sass is the preprocessor we should focus our support on

I would also like to know, why you've come to that conclusion. Is it because of the Ruby background? :stuck_out_tongue:

I would also suggest going for PostCSS, instead of Sass. PostCSS is a JavaScript project. As such, it seems to be more popular in the JS community and it's easier to extend for the typical frontend developer. Also, if GitHub stars mean anything:

As far as I'm concerned, PostCSS has full feature parity with Sass. There even are some addons that enable sassy syntax. :slightly_smiling_face:

webark commented 7 years ago

@rmmmp The ampersand is being used as a nesting selector in component namespaced files. In sass (not scss) and stylus we actually treat it exactly like that and basically just wrap the whole style file in a .__namespaced__class {

webark commented 7 years ago

There has been talk of adding a ::--namespace or something like that to facilitate some more tricky styling needs.

mikkopaderes commented 7 years ago

@webark, I'm in favor of the namespace. Specifically :host just like in shadow DOM. However, I'm pretty sure that this has been considered before. May I know why it wasn't preferred?

I think adding ::slotted() and is also feasible. We can do the current process for it and then BEM for the rest. This way, you can still rewrite our templates to match it with the generated BEM classes during build time and still allow for dynamic blocks which don't require them to know the BEM generated classes. The other benefit is that this makes us closer to future-proofing our CSS classes for shadow DOM.

e.g.

A component style of this:

:host {
  color: black;
}

:host(.foo) {
  color: red;
}

:host(h1) {
  color: green;
}

.foo {
  color: white;
}

.foo.active {
  color: brown;
}

.foo.active.awesome {
  color: gold;
}

::slotted(.foo) {
  color: blue;
}

::slotted(.foo) h2 {
  color: pink;
}

::slotted(.foo).active {
  color: yellow;
}

Would result to this:

.my-component {
  color: black;
}

.my-component--foo {
  color: red;
}

h1.my-component {
  color: green;
}

.my-component__foo {
  color: white;
}

.my-component__foo--active {
  color: brown;
}

.my-component__foo--active--awesome {
  color: gold;
}

.my-component .foo {
  color: blue;
}

.my-component .foo h2 {
  color: pink;
}

.my-component .foo.active {
  color: yellow;
}
webark commented 7 years ago

@rmmmp hmm. Ya. I'll look into the host and slotted key words. I don't ever see converting from one assumed syntax to something else like that (the .stacked.class.names to .stacked--active--awesome becoming a responsibility for this particular addon. But could definitely be an addon that compliments and can be used in conjunction with this. I, personally speaking, feel that you should be able to have a clean "convention averse" option, that other addons can build upon. Even the namespaceing that this addon does i feel should/will eventually be split off into its own addon separate from the co located styles.

mikkopaderes commented 7 years ago

@webark, I agree. That makes sense. πŸ˜€

t4t5 commented 7 years ago

I have to agree with @buschtoens, I think the push towards making ember-component-css into a SCSS framework might be a mistake.

PostCSS is awesome for the same reason that Babel is – it lets you use some of the latest specs and new features in a backwards-compatible way.

SCSS on the other hand feels more like CoffeeScript, and could lead to an app being tedious to rewrite as the rest of the community moves forward with more widely adopted standards instead.

webark commented 7 years ago

@t4t5 ember-component-css has no desire or direction to becoming a sass framework. It in fact uses post-css to do its parsing.

But it also is not a post css plugin, nor does it desire to be, at least at this time.

In the css community there are mixed feelings and attitudes about many things, one being the world of css pre processors and plugin architecture is one of them.

A desire of this addon, at this time at least, is to be convention/preprocessor/plugin agnostic, and be a stepping stone for other solutions to potentially be built off of.

Future (hopeful) support for shadow dom, or even a js module api, would be inline with this desire. Auto BEM support would be something that would live in its own addon (in my opinion).