aurelia icon indicating copy to clipboard operation
aurelia copied to clipboard

[RFC]: Enhance the CSS support

Open Sayan751 opened this issue 5 years ago • 2 comments

💬 RFC

At present in Aurelia 2 there are two ways to deal with CSS.

  1. The usual <el class="foo bar"></el>, <el class.bind="${expression}"></el> or <el class.bind="expression"></el>. This is dealt with class-attribute-accessor out of the box.
  2. Then there is support for the CSS modules. And this is done via the class custom attribute, combined with defining the CSS module dependencies, ...
import { customElement } from '@aurelia/runtime';
import { styles } from '@aurelia/runtime-html';
import * as css from './my-element.css';

@customElement({ dependencies: [styles(css)] })
export class MyElement {
}

... and registering the CSS module processor.

import { StyleConfiguration } from '@aurelia/runtime-html';

container.register(StyleConfiguration.cssModulesProcessor());

The support for CSS modules enables us to use the local CSS class names as-is without worrying about the morphed/mangled class name by CSS module processor (commonly css-loader for webpack).

<!--the class '.local-class-name' is defined in 'my-element.css'-->
<el class="local-class-name" />

Note that the class CA correctly recognize the .local-class-name to be a local style, and would apply the actual style which may take following form: my-element_local-class-name-hash .

Problems

There are couple of problems with that.

  1. The implementation of manipulating the class attribute of element is distributed. Thus, any new feature will need to be duplicated in worst case.
  2. In Aurelia 2 following syntax are supported.
<el class.bind="{'class1 class2': 'truthy_expression', 'class3': 'falsy_expression'}" />
<el class.bind="['class1', 'class2']" />

These results in following.

<el class="class1 class2" />
<el class="class1 class2" />

However, when using CSS module, the above mentioned syntax are not supported yet.

  1. When using CSS module, the class CA will always prefer the classes defined in CSS module over the class name coming from global scope (a global styles defined for the app, for example Bootstrap). This makes sense most of the time. However, if there is a global class and a CSS module class (local class) with the same name, it will not be possible to use the global class in the CE. Because the class CA will always apply the local class.

🔦 Proposal

Consolidate the accessor and the custom attribute into one single entity. The best alternative I think is the binding command, or more appropriately the attribute-pattern, binding-command, and instruction-renderer trio, coupled with a ClassBinding. In the binding, the CSS dependency of the containing element can be fetched to prepare the local class lookup.

First of all, this should eradicate the need to duplicate logic between the accessor and the custom attribute.

Secondly, now different attribute patterns can be put in place to handle different scenarios involving local and global styles.

The new binding will by default check if there CSS module registered. If yes, then those will be favored.

Example#1:

<el class="local global" />

This will apply the .local from CSS module, and .global from global styles, assuming there is no .global defined in the CSS module. If yes then it will use the local .global class.

Example#2:

<el1 class="local" />
<el2 class.global="local" />

This will apply the .local from CSS module to el1, and .local from global styles to el2.

Example#3:

<el class.local="local" />

This example is bit redundant. This obviously apply the class defined in the CSS module. As this is also the default behavior, this can be ignored.

Example#4:

<el class.foo-bar="true" />
<el foo-bar.class="true" />

Applies local .foo-bar class if defined, else applies the global class of the same name.

Sayan751 avatar Nov 01 '19 23:11 Sayan751

How would you get the per element CSS modules lookup into a location where the custom element can apply it? In the current implementation, we capture the css modules import and turn that into a dependency of the element which is a custom attribute that can map class. How would it translate in this case.

To be more concrete, today we do something like this:

export function cssModules(lookup) {
  return class ClassCustomAttribute {
    valueChanged(value) { ...use lookup here...  }
  }
}
import css from './my-component.css';
import template from './my-component.html';
import { cssModules, customElement } from 'aurelia';

@customElement({
  name: 'my-component',
  template,
  dependencies: [cssModules(css)]
})
export MyComponent {

}

How would you implement the cssModules function?

EisenbergEffect avatar Nov 01 '19 23:11 EisenbergEffect

I would prefer that part of the css dependency registration to be there. Then it will be easy to get the metadata in Binding. I have not explicitly mentioned that part assuming that registration to be there. Sorry for the confusion.

Sayan751 avatar Nov 01 '19 23:11 Sayan751

At the moment .class binding works with css module like standard static css. I think we can close this.

bigopon avatar Feb 01 '24 03:02 bigopon

related PR: #1690

bigopon avatar Feb 01 '24 03:02 bigopon