davesnx / styled-ppx

Type-safe styled components for ReScript, Melange and native with type-safe CSS
https://styled-ppx.vercel.app
BSD 2-Clause "Simplified" License
399 stars 31 forks source link

Style HTML tag #467

Closed pedrobslisboa closed 2 months ago

pedrobslisboa commented 2 months ago

Why

As mentioned by @purefunctor it would be nice to have full SSR support on styled-ppx: image

That way the user doesn't need to create a function to handle it and it will work out of the box by the library.

How

Attention, this branch is a extension of this one: https://github.com/davesnx/styled-ppx/pull/468

Screenshot

The screenshot below shows how the emotion handles it well, only creating on client size only client styles the universal styles were not recreated.

Captura de Tela 2024-04-25 às 14 54 14

vercel[bot] commented 2 months ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Ignored Deployment | Name | Status | Preview | Comments | Updated (UTC) | | :--- | :----- | :------ | :------- | :------ | | **styled-ppx** | ⬜️ Ignored ([Inspect](https://vercel.com/david-sanchos-projects-8c534635/styled-ppx/AMnAerLBDGoBYLLomJL2yE4RRsj4)) | [Visit Preview](https://styled-ppx-git-fork-pedr-e2d055-david-sanchos-projects-8c534635.vercel.app) | | Apr 29, 2024 6:41pm |
purefunctor commented 2 months ago

This looks good so far! Though, there's one quirk about the style tag rendering that I misspoke about in the screenshot above. The expected output should actually look like this:

<style data-emotion="css id123-someName" data-s>
  .css-id123-someName { }
</style>

Basically, the IDs used in data-emotion should not have the css- prefix. Also, to keep consistent with emotion's client-side behaviour maybe data-s could go second?

To my knowledge, Stylesheet.get_all already returns names prefixed with css-, so the only way to extract the IDs is to mechanically chop the prefix, which works, but is a little unsafe. Thoughts, @davesnx?

davesnx commented 2 months ago

Probably best to add the prefix where it matters only, and render here without any change.

Do you know why emotion doesn't need the "css-" prefix here?

pedrobslisboa commented 2 months ago

This looks good so far! Though, there's one quirk about the style tag rendering that I misspoke about in the screenshot above. The expected output should actually look like this:

<style data-emotion="css id123-someName" data-s>
  .css-id123-someName { }
</style>

Basically, the IDs used in data-emotion should not have the css- prefix. Also, to keep consistent with emotion's client-side behaviour maybe data-s could go second?

To my knowledge, Stylesheet.get_all already returns names prefixed with css-, so the only way to extract the IDs is to mechanically chop the prefix, which works, but is a little unsafe. Thoughts, @davesnx?

@purefunctor What about the animation- prefix? Does it need to be removed?

purefunctor commented 2 months ago

Do you know why emotion doesn't need the "css-" prefix here?

I'm not entirely sure myself, maybe it's just how emotion encodes it? Ultimately it sees the prefix already as the first word under data-emotion.

What about the animation- prefix? Does it need to be removed?

Not sure as well, it's be good to check but I won't be able to until tomorrow!

pedrobslisboa commented 2 months ago

Probably best to add the prefix where it matters only, and render here without any change.

Do you know why emotion doesn't need the "css-" prefix here?

Were you talking about something like this?

- let pp_keyframes hash keyframes =
+ let pp_keyframes preffix hash keyframes =
   let pp_keyframe (percentage, rules) =
     Printf.sprintf "%i%% { %s }" percentage (render_declarations rules)
   in
   let definition = keyframes |> List.map pp_keyframe |> String.concat " " in
-  Printf.sprintf "@keyframes %s { %s }" hash definition
+  Printf.sprintf "@keyframes %s-%s { %s }" preffix hash definition

(* ... *)

(* `resolved_rule` here means to print valid CSS, selectors are nested
   and properties aren't autoprefixed. This function transforms into correct CSS. *)
- let pp_rules hash rules =
+ let pp_keyframes preffix hash keyframes =
   (* TODO: Refactor with partition or partition_map. List.filter_map is error prone.
      Ss might need to respect the order of definition, and this breaks the order *)
   let list_of_rules = rules |> resolve_selectors in
   let declarations =
     list_of_rules
     |> List.map Autoprefixer.prefix
     |> List.flatten
     |> List.filter_map render_declaration
     |> String.concat " "
-    |> fun all -> Printf.sprintf ".%s { %s }" hash all
+    |> Printf.sprintf "@keyframes %s-%s { %s }" preffix hash definition
   in

(* ... *)

- let render_hash prefix hash styles =
+ let render_hash hash styles =
    let is_label = function D ("label", value) -> Some value | _ -> None in
    match List.find_map is_label styles with
-   | None -> Printf.sprintf "%s-%s" prefix hash
-   | Some label -> Printf.sprintf "%s-%s-%s" prefix hash label
+   | None -> Printf.sprintf "%s" hash
+   | Some label -> Printf.sprintf "%s-%s" hash label

let style (styles : rule list) =
  match styles with
  | [] -> ""
  | _ ->
+    let type_ = "css"
     let hash = Emotion_hash.Hash.default (rules_to_string styles) in
-    let className = render_hash "css" hash styles in
+    let className = render_hash hash styles in
     Stylesheet.push instance (type_, className, Classnames styles);
-    className
+    Printf.sprintf "%s-%s" type_ className

let keyframes (keyframes : (int * rule list) list) =
  match keyframes with
  | [] -> ""
  | _ ->
+  let type_ = "animation"
    let hash = Emotion_hash.Hash.default (keyframes_to_string keyframes) in
    Stylesheet.push instance (type_, hash, Keyframes keyframes);
-   animationName
+  "Printf.sprintf "%s-%s" type_ animationName

let get_style_rules () =
  Stylesheet.get_all instance
  |> List.fold_left
        (fun accumulator (type_, hash, rules) ->
          match rules with
          | Classnames rules ->
            let rules = pp_rules type_ hash rules |> String.trim in
            Printf.sprintf "%s %s" hash rules
          | Keyframes keyframes ->
            let rules = pp_keyframes type_ hash keyframes |> String.trim in
            Printf.sprintf "%s %s" hash rules)
        ""
  |> String.trim
purefunctor commented 2 months ago

Here's an initial fix I came up with, it doesn't account for animations yet: https://github.com/purefunctor/styled-ppx/commit/3032deec06c52be6ab2f6997ddeeda1d40d3f5fd

purefunctor commented 2 months ago

Okay, so with animations, here's what @emotion/server emits on my SSR script. They're stuffed under the css-global namespace with the hashes (as expected):

<style data-emotion="css-global 1acbpvw">
  @-webkit-keyframes animation-1acbpvw {
    0% {
      opacity: 0;
    }

    100% {
      opacity: 1;
    }
  }

  @keyframes animation-1acbpvw {
    0% {
      opacity: 0;
    }

    100% {
      opacity: 1;
    }
  }
</style>

I also discovered that for [%styled.global ...] styles, emotion emits the following, though I'm not entirely sure where it got the hash from.

<style data-emotion="css-global 1dvid4v">
  body {
    background-color: #1F1F1F;
    color: #F5F5F5;
    margin: 0;
    padding: 0;
  }

  #root {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
    -webkit-flex-direction: column;
    -ms-flex-direction: column;
    flex-direction: column;
    box-sizing: border-box;
    padding: 1rem;
    min-height: 100vh;
  }

  #root>main {
    -webkit-box-flex: 1;
    -webkit-flex-grow: 1;
    -ms-flex-positive: 1;
    flex-grow: 1;
  }

  @media screen and (min-width: 768px) {
    #root {
      max-width: 900px;
      margin-left: auto;
      margin-right: auto;
    }
  }
</style>
pedrobslisboa commented 2 months ago

@davesnx I have to issue a mea culpa for not testing it on a project using a reasonml SSR. I'm going to take the @purefunctor details and going to make some changes and test it on a side project. As soon as I have it I'll push to this PR.

pedrobslisboa commented 2 months ago

⚠️ To my future me ⚠️

It would be nice to have a demo of this feature on styleppx