rtfeldman / elm-css

Typed CSS in Elm.
https://package.elm-lang.org/packages/rtfeldman/elm-css/latest
BSD 3-Clause "New" or "Revised" License
1.24k stars 197 forks source link

(Very!) Long-term plans for going CLI-less #327

Open rtfeldman opened 7 years ago

rtfeldman commented 7 years ago

I've been going around asking various people "If elm-css had One Way to Do It," what should that one way be?

A major part of the motivation for this question is to figure out how people should share code with elm-css.

For example, @abadi199's datepicker uses elm-css. Today, he could have chosen asPairs, compiling to a .css file, or compiling to a <style> element. What if someone who wants to use his package chose differently? If they choose .css files and he chooses <style> elements, then their users are stuck downloading both the compiled .css file as well as the elm-css runtime. If he chooses .css files and they choose <style>, they now have to introduce a new step to their build process and asset deployment if they want to use it.

If we converged on One Way to Do It, the sharing experience would get much more coherent. There are other benefits, but sharing is one case where there is no substitute for converging on one approach. Overall, I'm convinced that One Approach is a goal worth aiming for.

Which Approach?

I've been thinking a lot about what folks said in #302 and #303, and have been talking about it with a bunch of people.

A few comments that stood out to me:

Overall, it seems pretty clear to me that the Html.Styled approach is best for sharing. Users add the package as a dependency and it Just Works. Even beyond sharing, Html.Styled also has the advantage requiring no build process or asset deployment changes; anyone could run elm package install rtfeldman/elm-css and start using it right away.

The research I've been doing in talking with folks in the JS community has been overwhelmingly positive in favor of the "render to a <style> tag" approach that Html.Styled would use. It seems like this approach has been broadly working out very well, and even the creator of CSS Modules says he'd use one of these systems if he were starting a new project tomorrow (instead of CSS Modules, which he continues to use at work; they of course have lots of legacy code using it).

Still, there are some clear downsides to Html.Styled.

  1. It makes you bring the compiled elm-css code into your .js bundle. There are a lot of functions in there! This will presumably be insignificant in 0.19, which has dead code elimination, but today it's a significant factor for anyone who cares about downloaded bundle size.
  2. Html.Styled is not battle-tested the way .css files are. (Especially considering it's still a WIP PR! :smile:)
  3. Although JS libraries which have gone down this road have seen very positive results, they have done so with performance optimizations we can't do yet (using insertRule, for example), and we can't count on having the same performance characteristics until we're using the same internals under the hood. (This is part of the Web API that's been deferred until post-0.19, but I think it's a safe bet that we'll end up having what we need to make this fast.)
  4. You can't run build-time transformations like PostCSS on things in realtime. On the one hand, this is innately true, but on the other hand, I'm not aware of any transformations beyond Autoprefixer that a realtime CSS system cares about. (Minification? Doesn't really matter.) I am not personally sold on the idea that Autoprefixer is worthwhile today in 2017, but I know this is a downside for anyone who still wants to use it or who has existing code that relies on it.

In talking through some other use cases with @eeue56 (theming and other things), it became apparent that elm-css could embrace Html.Styled and that anyone could build a CLI on top of it which used server side rendering under the hood to render the contents of the resulting <style> tag to a .css file.

In a world where such a CLI existed, it would still be very clear that Html.Styled is the way to do it (and in this world, the README for elm-css would probably only link to the CLI briefly), while making it possible (if less convenient than it is today) to continue using .css files even though there would no longer be first-class support for them.

Short-term implications

Having said all of this, even if this is ultimately the right path, I don't want to remove the CLI right now. I would instead prefer to introduce Html.Styled, describe it as the preferred choice in the README, and continue maintaining the CLI alongside it until such time as Html.Styled has become battle-tested enough for me to be comfortable making it the only first-class option.

There would still be short-term implications to going down this path, though. Making a serious commitment to rendering styles at runtime invalidates a lot of my original design assumptions, e.g. that it didn't really matter that none is represented by instantiating an object with 23 fields in it because we could afford be a lot more lax with memory and CPU usage at build time.

Concretely, I can imagine removing warnings (e.g. for rgb values over 255), and also replacing the current extensible records type trickery with phantom types, since they can provide the same compile-time guarantees but get erased at runtime. (So none would become an object with 1 field at runtime, and then someday once the compiler unboxes single-constructor union types, it would become just the string "none" at runtime.) I'm sure there would be other implications I haven't thought of yet.

I ran this overall strategy by Evan and it seemed like the way to go.

Thoughts?

I'm curious to get feedback on this direction. I realize it's a lot to digest, and much has been discussed already, but there are a lot of benefits to be had!

The idea of elm-css being presented as "Install this like any other package, make zero modifications to your build process or asset deployment process, and now you can add type-checked styles in your code, including media queries and pseudo-classes, with styles defined right next to the view code that renders them, and while easily sharing things like datepickers with anyone else." - that idea appeals to me a great deal.

I'd love to know what you all think!

sporto commented 7 years ago

To me the benefit of not having to change my build pipeline to use elm-css is very appealing. Also not having the mirror css folder as proposed before is a big deal. The Html.Styled is very elegant and easy to understand.

So I'm all in favour of this path unless there are big performance issues that are not unknown. I can ask @geelen about his experience in building styled components. But it seems that you have done plenty of research already.

ahstro commented 7 years ago

I strongly agree that not introducing a build step is incredibly valuable and I do like the design of Html.Styled.

I'm a bit saddened that it wouldn't play nicely with something like tesk9/accessible-html, since both it and Html.Styled replace the core Html import, but as long as there exists some possibility to extract the CSS to a .css-file, that wouldn't be too much of a problem.

kuon commented 7 years ago

To summarize my requirements/wishes:

A bit off topic About the last point, I have a strong feeling that elm-css and style-elements should work together to a common goal (and single package). This discussion shows that elm-css is not being a pure css generator anymore. Separating layout and theming feels like the correct approach. Elm has a very opinionated way to generate html, I think it should have a similarly opinionated way to doing styling because HTML without styles is just a pile of text. Ideally, I'd like to see a single solution to manage styling in elm app (a mix of elm-css and style-elements) under elm-lang umbrella.

rtfeldman commented 7 years ago

Thanks for your feedback @kuon!

Some thoughts:

  • Keyframe animations. Media queries, I can live without if I propagate the layout to my views, like style element does.

I think elm-css should have first-class support for keyframe animations and media queries. πŸ‘

  • Ability to define the styling alongside my views. On this, I'll be more nuanced. Ability to define the layout alongside my views, theming can go elsewhere.

The Html.Styled approach supports this. πŸ‘

  • Autoprefixer like feature [...] there are still too many important part of css that require a prefix.

Prefixing can be implemented as a package of helper functions. I don't want to make autoprefixing support an explicit design goal because browsers have deprecated prefixing.

This means that prefixing is necessarily a short-term concern; long-term, it is already being phased out. I don't want to base long-term design decisions on something we already know will not be a long-term need!

It's already possible to do prefixing via a package that exposes helper functions, and I think that's the right approach to making it easier to work with properties that are currently prefixed.

  • I need to be able to bundle a package [...] to be reused in someone's app, I don't want them to have anything else to do than including my package. This is not only an issue with css, but also images... only way now is base64 or svg.

I totally understand why you'd want that, but my high-level goal is for elm-css to be a way to write CSS in Elm, and that's it. Sorry!

I think it makes sense to choose one approach for how that CSS is deployed, but at the end of the day I want the scope of elm-css to be about CSS and CSS alone.

I have a strong feeling that elm-css and style-elements should work together to a common goal (and single package).

I have a different perspective: I think elm-css and style-elements have polar opposite design goals and should always be separate.

Use cases that are mutually exclusive in my mind:

The point of elm-css is to let you write CSS in Elm. The point of style-elements is to build a UI without thinking about CSS.

I don't think it's possible to make a library that's both great at letting you think in CSS while also being great at letting you not think in CSS. πŸ˜„

kuon commented 7 years ago

I understand your points and even if it's not my ideal world, I agree with you. I'd just say that most of my current work targets enterprise customer that will require prefixes for at least 10 years. And today, even if you can target the most bleeding edge browser, you need prefix ( for example http://caniuse.com/#search=user-select )

I have an idea about merging elm-css and style-elements. Why not create a package with some type that hold style. Like StyledHtml msg with helper functions to make it into Html msg. This package would then be common group for elm-css or style-elements. I still have that very strong feeling that a standard interface to work with "styled HTML" is needed.

rtfeldman commented 7 years ago

Both of them share a common underlying type - VirtualDom.Node - so they can already be used together in the same view! πŸ˜„

feluxe commented 7 years ago

Sorry for jumping in, but I really felt the need to share this, because I think it might be relevant for elm-css, Html.Styled, etc.

I think the real beauty of CSS Modules is that it allows you to decouple styling from your module entirely. If you ask me that is huge! When you create a module like the datepicker you won't know what styling is needed for each of the many websites it will appear on. The best way to solve the problem as a module developer would be to offer a default style and an interface for the module consumer to overwrite the defaults with their own CSS.

Here is an example of such interface in React (it's really simple):

The component developer loads a default style object in her component with CSS Modules. The default style object would be used in case the component consumer doesn't replace them with her own CSS Modules object that she passes in via props.classes:

// Shared Component.
import defaultClasses from "./default.css";

const Headline = (props) => {

  const classes = props.classes || defaultClasses;

  return (
    <div className={classes.wrap} >
        <h1>
          {props.title}
        </h1>
    </div>
  );
};

The component consumer can use her own styles to style the component as simple as that:

import Headline from 'headline-package'
import myCss from "./custom.css";

<Headline classes={myCss} title="Hello"/>

All it needs is custom.css to have a wrap class.

With such interface the component consumer can style the third party component any way she likes. Awesome!

Unfortunately the trend in React goes into another direction. The current cool kid on the block is Styled Components. Styled Components follows another approach in which the component developer hardcodes all styles into the component. As far as I can tell, there is no way for the component consumer to overwrite the hardcoded component styles. I think this is really bad. From my experience, you always need to fiddle with the CSS of third party modules/components. I cannot just import a third party module/component and expect all its CSS align well with my page. I need to have access to the classes/ids/elements for customization.

I know React is not Elm, but when it comes to shared modules, I think the situation is similar. The module developer must expose some sort of interface that allows the module consumer to customize styles. I think it would be nice if there was a standardized interface for that. Hope it makes sense :)

kuon commented 7 years ago

I think this is another problem, customization in elm should be handled by passing view functions to the component.

On November 24, 2017 11:43:07 AM GMT+01:00, feluxe notifications@github.com wrote:

Sorry for jumping in, but I really felt the need to share this.

I think the real beauty of CSS Modules is that it allows you to decouple styling from your module entirely. If you ask me that is huge! When you create a module like the datepicker you won't know what styling is needed for each of the many websites it will appear on. The best way to solve the problem as a module developer would be to offer a default style and an interface for the module consumer to overwrite the defaults with their own CSS.

Here is an example of such interface in React:

The component developer loads a default style object in her component with CSS Modules. The default style object would be used in case the component consumer doesn't replace them with her own CSS Modules object that she passes in via props.classes:

// Shared Component.
import defaultClasses from "./default.css";

const Headline = (props) => {

 const classes = props.classes || defaultClasses;

 return (
   <div className={classes.wrap} >
       <h1>
         {props.title}
       </h1>
   </div>
 );
};

The component consumer can use her own styles to style the component as simple as that:

import Headline from 'headline-package'
import myCss from "./custom.css";

<Headline classes={myCss} title="Hello"/>

All it needs is custom.css to have a wrap class.

With such interface the component consumer can style the third party component any way she likes. Awesome!

Unfortunately the trend in React goes into another direction. The current cool kid on the block is Styled Components. Styled Components follows another approach in which the component developer hardcodes all styles into the component. As far as I can tell, there is no way for the component consumer to overwrite the hardcoded component styles. I think this is really bad. From my experience, you always need to fiddle with the CSS of third party modules/components. I cannot just import a third party module/component and expect all its CSS align well with my page. I need to have access to the classes/ids/elements for customization.

I know React is not Elm, but when it comes to shared modules, I think the situation is similar. The module developer must expose some sort of interface that allows the module consumer to customize styles. I think it would be nice if there was a standardized interface for that. Hope it makes sense :)

-- You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/rtfeldman/elm-css/issues/327#issuecomment-346798753

-- Sent from my Android device with K-9 Mail. Please excuse my brevity.

rtfeldman commented 7 years ago

Check it out!

type alias DatepickerStyles =
    { primaryColor : Css.Color
    , accentColor : Css.Color
    , headerStyle : Css.Style
    }

datepicker : DatepickerStyles -> Html Msg

You can totally make a datepicker with customizable styles via elm-css. πŸ˜„