linkedin / css-blocks

High performance, maintainable stylesheets.
http://css-blocks.com/
BSD 2-Clause "Simplified" License
6.33k stars 152 forks source link

RFC: Per-Block Namespaces #332

Closed chriseppstein closed 4 years ago

chriseppstein commented 5 years ago

Summary

Instead of using a single namespace state for the attributes, we will formalize the concept that the block classes and states live in a shared namespace that is defined by the block. While the namespace for a block will be the url of that block (file://...) the local references to block styles will use the locally assigned identifier to refer to it via a namespace instead of using a non-standard dotted notation.

This provides significant design benefits, improves the overall readability of the code, and reduces the amount of boilerplate syntax the authors must provide.

Syntax Example

/* section.block.css -- automatically associated with above template */
@export btn from "./buttons.block.css"; /* automatically defines the namespace associated with buttons.block.css and assigns a identifier of "btn" to it. */
:scope { /* ... */ }
:scope[enabled] { /* ... */ }
.foot { /* ... */ }
/* button.block.css */
:scope { /* ... */ }
:scope:hover { /* ... */ }
:scope:active { /* ... */ }
:scope:disabled { /* ... */ }
.icon { /* ... */ }
.icon[inverse] { /* ... */ }
{{!-- section.hbs--}}
<section block:scope block:enabled={{isEnabled}}>
  <button btn:scope>
    <div btn:class="icon" btn:inverse={{isInverse}}></div>
    {{value}}
  </button>
  <footer block:class="foot">
    Copyright (c) 2019 LinkedIn Corp.<br>
    All rights reserved.
  </footer>
</section>

Motivation

Consistent feedback that I've gotten about CSS Blocks is that the state namespace is "weird" and "confusing".

We've used this namespace primarily as a signal that the attribute(s) belong to CSS Blocks instead of HTML so that we can confidently rewrite them.

While the word state was meant to evoke the visual state of the element, it can also be confusing with other "states" going on in a component. By using the name of the block, the code is more clearly associated with that block and so it reads better.

By taking over the html class attribute we've caused ourselves a lot of challenges in terms of legacy support that makes the gradual adoption of CSS Blocks more of a challenge than it needs to be. This change will allow legacy classes to coexist with css-blocks' classes without ambiguity so we do not lose our ability to clearly error when a block class isn't found.

How do we teach this?

Fortunately there's not very many users of CSS Blocks yet šŸ˜….

The "strangest" thing about this new approach is that instead of having space delimited values for the class attribute as is the custom, there can be more than one class attribute each in a different namespace. While this feels strange at first, it derives from some simple and consistent rules:

Detailed Design

Terminology Changes

We will change the name of state to attribute to match the authored syntax.

CSS Syntax Changes

Handlebars Template Syntax Changes

JSX changes

No changes to jsx will be made at this time.

amiller-gh commented 5 years ago

Ohai! Sorry, been a little crazed the last week with other work :) Yes. I still 1000% want to comment here! (And will jump in again with features... soon. I plan to use this in my new project ;) )

I love this proposal. Feels very natural. Brilliant use of the namespace attribute to create a more natural feel. Just a couple callouts:

  1. I assume you will only parse known namespaces from the associated Block. This way, other namespaces used by the document (ex: SVG namespaced attributes) can be used un-interrupted.

  2. IF (big if) the user is using both HTML namespaces (again, ex: SVG) AND is rather absent-minded and uses the same exported namespace from the Block, what will happen?

<html xmlns="http://www.w3.org/1999/xhtml">
  <body>
    <svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
      <script xlink:href="cool-script.js" type="text/ecmascript"/>
  </svg>
  </body>
</html>
/** Imported under the namespace `xlink` **/
[href] { /* ... */ }
.
  1. Can we use a similar syntax for Block passing? (May be a little heavy handed)

    <my-component></my-component>
    <custom:my-component></custom:my-component>
    @block custom from "path/to/custom/my-component/styles.css";
  2. Fortunately there's not very many users of CSS Blocks yet šŸ˜….

šŸ˜

amiller-gh commented 5 years ago

Hmkay, one more! (just a nit)

In your example above, you use the namespace btn from section.block.css without exporting it. Blocks should only be available to the default associated template after re-export:

/* section.block.css -- automatically associated with above template */
:scope { /* ... */ }
:scope[enabled] { /* ... */ }
.foot { /* ... */ }

@export btn from "./buttons.block.css"; /* automatically defines the namespace associated with buttons.block.css and assigns a identifier of "btn" to it. */
/* button.block.css */
:scope { /* ... */ }
:scope:hover { /* ... */ }
:scope:active { /* ... */ }
:scope:disabled { /* ... */ }
.icon { /* ... */ }
.icon[inverse] { /* ... */ }
{{!-- section.hbs--}}
<section block:scope block:enabled={{isEnabled}}>
  <button btn:scope>
    <div btn:class="icon" btn:inverse={{isInverse}}></div>
    {{value}}
  </button>
  <footer block:class="foot">
    Copyright (c) 2019 LinkedIn Corp.<br>
    All rights reserved.
  </footer>
</section>
chriseppstein commented 5 years ago

@amiller-gh Thanks for reviewing!

Blocks should only be available to the default associated template after re-export

Yeah, that change just landed in https://github.com/linkedin/css-blocks/pull/330 so it'll work that way. I've updated my code example above to match.

Can we use a similar syntax for Block passing?

I think passing a block should be like passing a parameter to a component especially because I think it may be necessary to pass multiple blocks in some situations.

IF (big if) the user is using both HTML namespaces (again, ex: SVG) AND is rather absent-minded and uses the same exported namespace from the Block, what will happen?

I'm special casing all the well known namespaces. In HTML5, you can't use other xml dialects under arbitrary namespace identifiers like you can with xhtml. I've currently got html, svg, math in the list of reserved namespace identifiers. I can add xlink and will check if I'm missing any others.

I assume you will only parse known namespaces from the associated Block.

As I mentioned above, I think we can enumerate the list of allowed namespace identifiers that are called out by the html5 spec. Because of that I think we can assume an unknown namespace identifier is a coding error. If needed, we can disable that check by adding a new option to css blocks.

robomalo commented 5 years ago

I can't contribute too much on feedback because my experience is limited, but my first impression is this is going to be easier for devs to digest/analyze on the first pass or three.

chriseppstein commented 4 years ago

This feature is on master now.