Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style (CSS) Architecture #4

Open
kirlat opened this issue Oct 19, 2018 · 20 comments
Open

Style (CSS) Architecture #4

kirlat opened this issue Oct 19, 2018 · 20 comments
Assignees

Comments

@kirlat
Copy link
Member

kirlat commented Oct 19, 2018

I decided to move this from #3 to keep the latter manageable. I think architecture of styles is not directly related to the component architecture. Both topics can be voluminous. In order to keep issues clean and manageable it's probably better to keep discussion separate (please let me know if you do not agree).

Design Goals

Listed in order of importance:
a. Styles for one component should not interfere with styles of the other component or with styles of the page. Styles that are defined on a web page should not inadvertently change the look of Alpheios components, no matter how complex a web page is.
b. Styles need to be able to be specific to target language (for example, we want to be able to optimize fonts for Arabic, etc.)
c. Components should be skinnable. Third party or us should be able to create a skin to change visual appearance of Alpheios components.
d. Style files should have the smallest size possible.

Is there anything else I forgot to mention here? If we agree on the goals, we can discuss the best solution to achieve them.

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

A copy style-related information transferred from #3:

@kirlat wrote:

I think styles are a very important part of an architecture (even though it might be orthogonal to the JS objects it does not matter). We probably need to have a different doc on styles too. I agree that it would probably make sense to separate styles from components (even though components, especially reusable ones, should probably have its own minimal styling to be of any value) but have no idea so far how it's best to be done. it's a complex topic as styles goes through a UI of several apps.

The big issue in styles is naming. We're using BEM approach, but that requires to identify and separate visual components form one another. Should pay attention to that.

We probably need to have at least two layers of styles: default ones and the ones that represent skins. We should also try to minimize rendered CSS if possible. Maybe we can peek at what large CSS frameworks (Bootstrap, UIkit, etc.) do here to borrow their expertise? Or maybe there is something even better than that?

Another interesting technology that might benefit our visual components are CSS modules: https://css-tricks.com/css-modules-part-1-need/. Maybe we should research this topic too when we can.

@irina060981 wrote:

About Css Modules - from my point of view the Vue approach is better than CSS Modules described in the article.
All sytles in components could be created with scoped parameter and it would be rendered this way:

  1. everytime you compile the Vue application - it creates an additional data parameter to every html tag (for components that has scoped styles parameter)

  2. this data parameter is added to styles definitions
    For example,
    image

This parameter re-inits each compiling.

@irina060981 wrote:

From my experience our approach to styling is close to used in Bootstrap:

  1. there are all styles divided to separate files (less/scss) - some of them are core and should be used in any build and some of them are specific to used components. So when you want to use some custom build - you should decide and those you neeed.

  2. There is a constant parameters files (colors, base-fonts, base layout sizes)

  3. All files are used with these parameters.

  4. So if you want to apply some skin - you are creating an additional skin class and change constants according to the skin.

In our case It seems to me that we couldn't really optimize styles amount - as they should be compiled before and append through the only css file to head. And BEM approach makes additional amount of memory that needs to save our styles (because of long classes name).
For our project that uses really not so much html markup we have 287Kb styles only in components repo.

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

Technologies

Are there any other technologies beyond above that can be beneficial to us?

Syntactic Comparison

Everything is in one line so that it will be easier to compare

BEM

Style Definition: <style> .component-name__button { color: red; } </style>
Use in Component: <template> <button class=”component-name__button” /></template>
Rendered Style: .component-name__button { color: red; }
Rendered Component: <button class=”.component-name__button { color: red; }"></button>

Scoped CSS

Style Definition: <style scoped> .button { color: red; } </style>
Use in Component: <template> <button class=”button” /></template>
Rendered Style: .button[data-v-f61kqi1] { color: red; }
Rendered Component: <button class=”button” data-v-f61kqi1"></button>

CSS Modules

Style Definition: <style module> .button { color: red; } </style>
Use in Component: <template> <button :class="$style.button” /></template>
Rendered Style: .ComponentName__button__2Kxy { color: red; }
Rendered Component: <button class=”ComponentName__button__2Kxy”></button>
Additional Notes: Class names are accessible via the $style object. They also can be exported using :export. Can specify a custom inject name: <style module="a">/* identifiers injected as a */</style>

Examples are taken from this great article comparing Scoped CSS and CSS Modules in Vue.js: https://www.netguru.co/codestories/vue.js-scoped-styles-vs-css-modules

@irina060981
Copy link
Member

Great information, @kirlat! :)

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

BEM

Advantages

  • Class names clearly specifies its purpose and to what component it belongs.
  • Uses a single class name as a selector. This has the lowest specificity possible.
  • The easiest to understand and modify approach - anyone could easily read CSS files, understand what is happening and add its own file. It doesn't need any additional tools to use it.

Disadvantages

  • Naming conventions are not enforced and have to be followed manually. This is tedious and error prone.
  • Syntax is more on the verbose side.
  • It is really hard to follow the same logic at any part of the code - it becomes nearly the same. So it increases the amount of time for developing, increases the amount of CSS files and couldn't solve all the problems with style conflicts (as we are injecting to others pages).

Scoped CSS

Advantages

  • Class names are generated by Vue tools automatically. It enforces a proper syntax and simplifies code maintenance.
  • Class names are relatively short.
  • Easier to understand and to use. It is the common technique that is introduced in frameworks Vue, React. Does not need Webpack. It could be easily understood by any developer who is familiar with Vue.js and could be easily modified.

Disadvantages

  • Adds one or more extra data attributes to HTML elements. Purpose of those attributes is not clear from the first look. Due to separation of concerns it's also better stick with classes only for visual styling and avoid using data attributes for that. Also results bigger CSS files because of extra parameters added. Data attributes are added to child component HTML elements too, this can be messy: <div class=”base-panel pricing-panel” data-v-d17eko1 data-v-b52c41> content </div>
  • If user specifies a class with the same name in a child component, parent style may leak to a child component.
  • Class names are not expressive enough. The do not tell us where to what component this class belongs.
  • Multi-attribute selector (class name + data attribute) increases it's specificity and requires even higher specificity in order to override it. This leads to specificity wars. But we might have specificity wars anyway because of "aggressive" selectors of a host page so this might not matter.

CSS Modules

Advantages

  • Class names are generated by Vue tools automatically. It enforces a proper syntax and simplifies code maintenance.
  • Class names clearly specifies its purpose. Vue component name is part of the class name, so it's easy to say what component this class belongs to.
  • As all classes are available through the $style object we can now pass them to child components and use there easily.
  • CSS Modules have great interoperability with JS, Using :export keyword, we can export additional things to our $style object. For example, we can keep all color variables in CSS, and additionally export them for use in our components.
  • Uses a single class name as a selector. This has the lowest specificity possible.

Disadvantages

  • Syntax is the lengthiest of all.
  • Increased complexity because it require more complex Webpack configuration. And it needs more developer skills.

That's all I was able to come up with. Is there anything else that is forgotten? Can anyone offer any corrections?

@irina060981
Copy link
Member

@kirlat , if I understand correctly the the CSS modules technology - it operates only css classes?
We are also using injected html files with each one classes, for example, for definitions - how will it be handled with ordinary class names?
And how css modules could be used with the aim of creating skins?

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

@irina060981, great questions!

If I understand correctly, it's all pretty straight forward. Once we add module attribute to a style declaration:

<style module>
.red {
  color: red;
}
.bold {
  font-weight: bold;
}
</style>

Vue adds a $style object into a component with two props: red and bold. The value of red is ComponentName__red__hash and the value of blue is ComponentName__red__hash. Here the magic ends and the rest is pretty straightforward. So when in a template we use one of those styles, it's like

<template>
  <p :class="$style.red">
    This should be red
  </p>
</template>

So instead of $style.red it outputs the value of a red property, which is a generated class name.
In this simple case like this Vue just generates class names automatically, and renames classes in a style block with a module attribute, and that's all.

If in definitions we inject an HTML code with "regular" class names, then we should use "regular' class names to style them. We can control what a component name can be and we can have several different style blocks side-by-side: https://vue-loader.vuejs.org/guide/css-modules.html#custom-inject-name. I think we can also have blocks without module attribute and it will produce "regular" class names (did not try that though).

For skins, I think, we can probably combine two class names together using a usual approach.
So if a component defined like:

<template>
  <p :class="$style.red" class="skinnable-widget">
    This should be red, but will be blue
  </p>
</template>

It will render as:

<p class="ClassName__red__hash skinnable-widget">
  This should be red, but will be blue
</p>
</template>

Then in a separate skin file we define .skinnable-widget { color: blue }. So as long as skinnable-widget file is loaded AFTER component styles, the text will be blue because .skinnable-widget class will be defined later and thus have a priority over whatever a component has.

This approach simple with CSS modules, but will be harder with scoped CSS because there we have to override a double selector such as red[data-v-f61kqi1]. As a result, we would have to come up with something like p.skinnable-widget which will be less elegant and will stop working once we decide to replace <p> with <span> or <div> in a template. I believe multi-part CSS selectors are trouble (but trouble sometimes unavoidable).

What do you think? Will the above work for us?

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

An approach above would require us to assign "regular" class names to component elements so that they be used in skin styles. That's more complexity, but as side advantage we can define what can be skinned and what can not (i.e. if it does not have a "regular" class, it cannot be skinned). This could be important if we allow third-parties to provide their own skins. This would allow us to divide HTML components into "private" and "public" in terms of skinning (of course this can be overcome with more specific selectors or !important but still).

Another approach is that we could tell Vue loader to generate class names without hash, so they will be constant, such as ComponentName__red. Then we could use those names in skins directly. But I need to check if it's possible.

@irina060981
Copy link
Member

We have multi-part selectors in any case because we have

  1. #alpheios-popup and #alpheios-panel in the most root element for isolating styles and increasing their weight
  2. skin, colors, sizes classes
  3. we have overall .auk classes for base elements

Also we won't be able to garantee the same styles always becase there are sites that applies styles directly to tags and could overide it with some combination of classes (that's why it is added ID selectors I think)

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

Actually, class names are seem to be generated not by Vue, but by Webpack's CSS loader: https://vue-loader.vuejs.org/guide/css-modules.html#usage, https://github.com/webpack-contrib/css-loader. So we could make those names whatever we want them to be.

localIdentName: '[path][name]__[local]--[hash:base64:5]' should generate src-styles-main__world-grid--R7u-K, but localIdentName: '[name]__[local]' should result in main__world, if I understand correctly.

@balmas
Copy link
Member

balmas commented Oct 19, 2018

Design Goals

Listed in order of importance:
a. Styles for one component should not interfere with styles of the other component or with styles of the page. Styles that are defined on a web page should not inadvertently change the look of Alpheios components, no matter how complex a web page is.
b. Components should be skinnable. Third party or us should be able to create a skin to change visual appearance of Alpheios components.
c. Style files should have the smallest size possible.

Is there anything else I forgot to mention here? If we agree on the goals, we can discuss the best solution to achieve them.

I think we should add: styles need to be able to be specific to target language (for example, we want to be able to optimize fonts for Arabic, etc.)

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

I think we should add: styles need to be able to be specific to target language (for example, we want to be able to optimize fonts for Arabic, etc.)

Thanks for the comment, updated the Goals section.

@balmas
Copy link
Member

balmas commented Oct 19, 2018

this is a very interesting discussion. Thank you both for your initiative and valuable contributions to it!

I have a couple more questions:

  1. is the scoped CSS solution specific to Vue.js?
  2. similar question for CSS modules - to what extent is this dependent upon the framework? It sounds like maybe it's less framework-specific than scoped css, but does requires a code bunlder like webpack to turn it into standard syntax. is that correct?
  3. Which of these approaches is most similar/compatible with the CSS Scoping draft (https://drafts.csswg.org/css-scoping/) that is part of the W3C Webcomponents effort?

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

Here are answers based on what I know so far:

  • is the scoped CSS solution specific to Vue.js?

I believe it was modeled in Vue.js after an HTML's draft of <style scoped> the same way Vue.js component idea was based on a concept of web components. Unfortunately, scoped was removed from HTML specs in 2016: w3c/csswg-drafts#137. However, Vue still has it.

  • similar question for CSS modules - to what extent is this dependent upon the framework? It sounds like maybe it's less framework-specific than scoped css, but does requires a code bunlder like webpack to turn it into standard syntax. is that correct?

CSS Modules is not part of any W3C standards. It is an independent framework-neutral initiative that has specs of its own: https://github.com/css-modules/css-modules. It is currently implemented by Webpack via a CSS loader (https://github.com/webpack-contrib/css-loader) and by React and Vue via Webpack.

According to documentation, "A CSS Module is a CSS file in which all class names and animation names are scoped locally by default". Just that. And a way to import styles form other modules. My impression is it's more just a convenience tool than any new HTML spec.

I think neither. CSS Scoping Module Level 1 uses slotted items and Shadow DOM and seems to be much more ambitious venture than both Scoped CSS and CSS Modules (that's IMHO).

@balmas
Copy link
Member

balmas commented Oct 19, 2018

ok thank you for this.

While I really like the simplicity of the scoped attribute I am hesitate to go with something that is framework specific.

so if the complexity is too much for the BEM solution, and it sounds like it is, my vote would be for CSS Modules if it provides what we need. Perhaps the best time to move to this for existing components would be when we implement the concept design (whenever we get it) and to apply it from the outset to any new components from this point on. What do you both think?

@kirlat
Copy link
Member Author

kirlat commented Oct 19, 2018

BEM is simple, but really painful to maintain in large projects (IMHO) and impossible to enforce.

CSS modules are similar to BEM by their naming schema except they're generated automatically. Looks like the same thing, but better. Plus, it has some additional features, if we would like to use them. @irina060981, what do you think?

Any CSS schema described here can be combined with any other. It's not all or nothing approach. We can try a new schema on some small and unimportant component and see how well it will work. It it won't, we can try the other one or scrap it all together. No rush here.

@irina060981
Copy link
Member

irina060981 commented Oct 20, 2018

I think that we could use any approach but we should think about the following:

One of the common problems for the free-distributed open-source tools is that they are hardly customizable and appliable in a real world to some out-side projects. According to our choices:

  1. BEM
    - (advantage) It is the most easiest to understand and modify approach - anyone could easily read css files, understand what is happening and add its own file with additional ID attribute (of course we should not have any !important signs). It doesn't need any additional tools to use it.
    - (disadvantage) It is really hard to foolow the same logic at any part of the code - it becomes nearly the same. So it increases the amount of time for developing, increases the amount of css files and couldn't solve all the problems with style conflicts (as we are injecting to others pages).

  2. Scoped CSS
    - (advantage) It is the common technique that is introduced in frameworks Vue, React. it appears in first lessons about them It is really easy to apply inside frameworks - it doesn't even need webpack. So it could be easily understood by any developer who is familiar with Vue.js and could be easily modified. Also it is easy to customize our components and to change them to any other one with styling at the same time.
    - (disadvantage) It creates additional params in HTML tags and creates bigger Css files because of adding those parameters. We could controll the difference between parents class names and children class names. (I am not quite agree with another advantages because a) I didn't see benchmarks that shows that data selectors are slower than class selectors; b) I am sure that specificity wars would be in any approach (and the same is true for expressive classnames) because it depends much more on a developing quality than approach quality)

  3. CSS Modules
    - (advantage) - I didn't have experience with this technology so I simply agree with @kirlat arguments, that it is cool!
    - (disadvantage) - But I should mention that its complexity to create, use, change and upgrade is more than previous approaches. And it needs more developer skills. And I don't see how it could be upgraded and changed easil;y by someone who has less skills than for example @kirlat.

And the both 2. and 3. would need cascade of classes and ID identifiers and some long class names.

So I think that we should think what developers would use our extension and would try to apply to there need. What way would they like and would be able to change styles.

If to be honest, I don't know whom in IT community we are appealing with this great linguistic project. (It is really great I think. Because my several tries to easily find some inflection tables for testing show that it is not an easy task)

P.S. Of course as a developer I would love to work with CSS Modules as it is unknown for me and would increase my skills :)

@kirlat
Copy link
Member Author

kirlat commented Oct 22, 2018

2\. I didn't see benchmarks that shows that data selectors are slower than class selectors;

Actually you're right here. I've checked the data I was able to find on that and it seems that selector performance is almost the same in any case: https://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/ Don't know from where I had it in my head. I'll remove it from list of advantages.
Will also try integrate your other points too (those are very good points, thanks!).

@kirlat
Copy link
Member Author

kirlat commented Oct 22, 2018

That's a very interesting topic Irina brought: what are our requirements in terms of complexity that defines barriers to entry for people who will use it or work on its development. We probably should separate persons who will contribute to developemet of the Alpheios "core" libraries and persons who will use it, but might adjustit to their needs (i.e. a company that would use an embedded lib might want to customize it or create a custom skin for it). Those two groups are probably different so we should consider them separately. @balmas, what do you think?

@balmas
Copy link
Member

balmas commented Oct 22, 2018

Yes, this is a very good topic for discussion.

And yes, I think we have to talk about 2 types of contributors:

1 - other developers who will work on the core Alpheios code
2 - people who will customize Alpheios for their needs

We could further break down the 2nd category into

2a - people who will do cosmetic customization - e.g. skinning for branding
2b - people who will customize by adding languages or changing back-end services

For 1, based upon past experience I think we will not likely get people contributing to the core code unless they are professional developers who are able to manage complexity (and likely, paid to do it). So our priority there should be to make sure our code is clean and well documented and follows best practices (such as having unit tests, etc.) so that new developers coming on board have a a path to follow to understand it and add to it. And I think trying to reduce complexity is a best practice, but so is minimizing dependencies upon any specific framework and/or bundling system.

For 2a, I would like it to be dead simple to skin the UI for things like colors, fonts, icons, etc. Ideally not requiring any knowledge other than CSS and HTML.

For 2b, I would like it to manageable for a novice programmer to do.

@irina060981
Copy link
Member

Moving the discussion from alpheios-project/webextension#135
@kirlat:

That's maybe not related to the topic directly, but since we're discussing CSS organization and processing I think it might be worth discussing here. The question I want to bring is whether a UIKit is an asset or a liability in our current design.

I have a feeling we do not follow UIKit styles extensively throughout our apps because our custom styles (and our style requirements) are so different from what UIKit is now. So would there be any value of continuing using a UIKit? This is an extra dependency and extra CSS weight we have to carry.

AFAIR we're using UIKit to style just a couple of elements such as buttons and for those we can copy out UIKit styles and make our own out of them. I also see no value in UIKit JS because we're using Vue.js instead (that's a change from before where UIKit JS interactions could be valubale). Dropping UIKit may also simplify some styling where we do override UIKit styles; we could remove unnecessary selectors if we won't have a UIKit on board. It's always better to make things simpler slightly_smiling_face

I don't have any strong opinion on this, but I think it might be worth discussing here. So I'm wondering what do you think.

@balmas

I was wondering that too, as I looked at the styles today. I think it might be more of a liability right now. Simplification prior to implementing a design refresh in coming months is also probably a good idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants