Closed mdgriffith closed 7 years ago
A big benefit of having automatic scoping is that developers no longer have to worry about maintaining a global list of all namespaces. Having a global list of namespaces is just marginally better than having a global list of all classes. Yes, our list of globals will be 10 times smaller. But if we have 10,000 classes, reducing this to 1,000 is still not really helpful. (the numbers are contrived). Also, what about libraries?
So, this doesn't solve the problem at its core: classes are global strings and are not a good primitive for building large applications or isolated code (e.g. 3rd party library).
styled-components solves this problem by removing the notion of a class altogether, instead allowing you to create a component with styles directly. The fact that it relies on classes internally is an implementation detail. Here I don't necessarily advocate for doing the same (as in, generating view functions, even though why not?). But I think we should find a solution that does the scoping automatically, even if it potentially means changing core Elm functionality. If performance is indeed an issue, then turning the feature off by default is not a solution. Why? Because most small applications very likely won't suffer from performance issues anyway, while large applications, if they will, are the ones actually in need of this feature.
I think you're onto something great with this library, writing scalable CSS has been the biggest pain point for me in Elm compared to JS.
You make some good points and thanks! The number of namespaces that a developer deals with is definitely one of the core points here; being on the scale of 1000 is very different from on the scale of 10. So lets get idea of what established sites are actually encountering!
I actually put together a repo to study stylesheets in the wild to get a rough idea of how styles are used on established sites. I'll try to get some numbers so we can at least get a sense of the scale of the numbers (how many classes, how many stylesheets, etc.). This isn't 100% representative because these are deployed stylesheets, but it should be useful for ballparking.
Also, I think it makes sense to get some actual numbers for the performance of hash-guards using elm-benchmark. We can see how it scales to absurdly huge stylesheets like 10k classes compared to a guardless stylesheet.
The hash function I'm going to be looking at is murmur3. I'm open to suggestions though if anyone has them.
In regards to the study of existing usage of stylesheets, you need to keep in mind that: 1) stylesheets are often concatenated into a single one during the build 2) most websites I've seen are not nearly close to what I'd consider to be good practices in writing scalable CSS. Often this involves having dedicated people who have a mental map of various classes and how they can be used. They can use only a handful of stylesheets because they don't know any better. They will of course have hundreds, if not thousands of classes per stylesheet. 3) Having many stylesheets doesn't guarantee any isolation. They all share global scope.
So my prediction is that the numbers you'll get will be very low, and I don't think you should base any decision on that number. Instead, we should look at how (in an ideal world) we expect people to structure their styles, and we can take other technologies as an example.
With Ember.js, I'd colocate stylesheet next to a component using ember-css-modules. This enables strict 1-1 mapping between component and stylesheet. In my previous app, we had a few hundred components and the same amount of stylesheets (unless, of course, a component didn't require any styling).
With React, you can do the same with CSS Modules, but since there can be more than one component per file, the 1-1 mapping between components and stylesheets is not strict anymore.
With Elm, the contents of a file is completely arbitrary, but it's not that much different from React. I propose this as a convention: styles should be used in the same file where they are defined, and only there. This is how I structure my React code with styled-components.
Now, let me propose something that's not currently possible but would be with a bit of help from Elm (or perhaps, via a Native module).
Imagine we didn't have to think in terms of stylesheets, just styles. In a way, it is just a low-level mechanism that allows you to add multiple styles to DOM with a single operation. As a user-level abstraction, it has little value. If only the styles could be added automagically, at the moment of definition, just like they are with styled-components...
How could the API look like?
something like this:
title = class
[ width (px 300)
, height auto
]
nav = class
[ width (percent 100)
, height (px 70)
]
view model =
div []
[ div [ nav ]
[ a [href "/profile"] [text "My Profile"]
]
, div [ title ] [ text "Hello!"]
]
This function, class
, would technically be impure. It would have calculate the hash, add styles to DOM and return the class name. Semantically though, there's no side effect. We are simply declaring styles and using them in DOM. Style declaration should be as simple as value declaration — sadly, in DOM it's a side-effect.
This API, I think, is cleaner, since we are avoiding a notion of stylesheet altogether. Classes are now just values. This is exactly how styled-components
works.
I believe it's possible to implement this with a Native module (which is not allowed in libraries). Alternatively, this could be pushed to Elm core, and I think it should. It's slightly more opinionated than pure CSS, but so is Elm's approach to representing DOM with functions.
But before this happens, I think, it's fine to keep the current API with a bit of Stylesheet boilerplate, keep automatic namespacing, improve other aspects of the library, and eventually it'll be more clear how this feature should work.
I'm afraid I'm not familiar with performance traits of various hash functions. Can you tell me how often the hash is calculated? Is it once per style or once per view render? In the latter case, can we make use of Html.lazy
?
I don't see the benefit of having something like an impure class
function that modifies a stylesheet behind the scenes. What pain point would that solve? My initial thought is that it would actually lead to less enforced organization of your styles instead of more, which is one of the main goals of this library.
I'm also hesitant to throw this design decision behind the idea of components when the means of scale for Elm is largely not based on components(though that's a can of worms I'd rather not get into now :))
While caveats apply, getting real numbers for in-the-wild styles is still useful, specifically for the reason you mentioned, to anchor the ideas that should be best practice. I also think you're overestimating how many best practices are used.
Preliminary numbers for amazon.com:
These numbers and thinking about interesting ways to approach them/inspect them offer a pretty interesting look into style being used in the real world.
Ok, so to keep this issue on track, style scoping is obviously important, we're really talking about the details on how to implement. This depends both on what the performance would look like and how many namespaces we're actually talking about. Your point that it should be automated really does appeal to me because it aligns with the "best practices built in" approach of this library.
Also, current scoping adds a hash at the class level. Each class gets it's own hash based on it's properties.
I don't see the benefit of having something like an impure class function that modifies a stylesheet behind the scenes. What pain point would that solve? My initial thought is that it would actually lead to less enforced organization of your styles instead of more, which is one of the main goals of this library.
The pain point of having to worry about and think in terms of stylesheets. Currently I have to manually include the stylesheet in the code, and I have to group my classes under a stylesheet. It's totally possible to define a stylesheet, use the class but actually forget to render the stylesheet. Also, with the current implementation the same stylesheet can be rendered multiple times, if there's more than one “instance” of a view present. Also, we unnecessarily remove styles from DOM when the view is removed.
Also, I do presume that it's a good practice to keep view functions small. This might lead to a the following problem: if a stylesheet is used by multiple views, we have to include it into each single view, otherwise we might end up accidentally rendering a view without corresponding stylesheet. If we use a stylesheet per view, we will likely end up having a lot of boilerplates.
I'd argue that a more natural way to group styles is an Elm module.
I understand that my solution is currently impossible to implement, but I don't think it will lead to less enforced organization. The whole notion of a stylesheet doesn't introduce any semantic entity per se, it is only used to render styles to DOM. Whether you group styles into stylesheets or not will not make your code more or less organized, given that all styles will be namespaced regardless.
I should say that this pain point is relatively small and is mainly about the boilerplate and other issues I've listed above. Existing approach already should scale well.
I'm also hesitant to throw this design decision behind the idea of components when the means of scale for Elm is largely not based on components(though that's a can of worms I'd rather not get into now :))
It's just an interesting idea from another library which happens to be using components. It's in fact not about components, but about 1) making styles 1st clas values 2) turning styles into something that can already render to DOM (whether it's a component or a view).
Ok, so to keep this issue on track, style scoping is obviously important, we're really talking about the details on how to implement. This depends both on what the performance would look like and how many namespaces we're actually talking about. Your point that it should be automated really does appeal to me because it aligns with the "best practices built in" approach of this library.
Glad we agree on this!
Also, current scoping adds a hash at the class level. Each class gets it's own hash based on it's properties.
And I guess we calculate this hash on every call to Style.embed
?
I see what you're getting at by grouping styles as functions in the module and how grouping styles into a stylesheet is largely an operational detail.
A lot of this depends on where you land on using many small stylesheets versus one large one. Maybe this topic of large organizational structure of styles needs to be it's own issue.
I have been playing around with the concept of having a Style.merge
function which would allow you to embed a stylesheet into another. I'm not sure if that's the way to go, but it would provide an avenue of having small style sheets that all merge together into a larger one. We'll see.
As far as hashing, the goal is to have the hash being called once when the page is first rendered.
I've started a topic to cover high level plan for style organization.
I'm making a note here from our discussion that style scoping is a few different considerations
Resolved! We default to guarding styles with a hash of the properties in the style. It can be turned off in, uh, case of emergency?
style-elements
has the ability to add a class "guard" by including the hash of the style properties of a class in the class name. Right now it's on by default but will likely be off by default in the next release because of performance concerns. I'd like to get some real numbers fromelm-benchmark
.I like
elm-css
'snamespace
function just because it's so simple.style-elements
may take a similar approach.