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

CSS Extraction at build-time #433

Open
3 of 10 tasks
davesnx opened this issue Mar 7, 2024 · 1 comment
Open
3 of 10 tasks

CSS Extraction at build-time #433

davesnx opened this issue Mar 7, 2024 · 1 comment

Comments

@davesnx
Copy link
Owner

davesnx commented Mar 7, 2024

We can remove the runtime completely and go for static styles. Generate CSS files in the _build folder (for Melange/native) and in-source in ReScript.

  • We will remove the runtime completely allowing much faster implementation
  • Output real CSS (can be extracted to a new file, cached)
    • Need to know how to generate files on the same folder (both in rescript and melange)
  • Dynamic components can be translated to CSS variables
  • We can have development type-safety if we keep the parsing/type-checking + inline styles at run-time.
    • Not sure how thought, maybe with unboxed?
  • We already have a css-in-js logic implemented in native

Design

styled.div

module C = [%styled.div "height: 100%"]
(* pushes into the Stylesheet ".div12312 {height: 100%}" *)
(* module C = <div className="div12312" /> *)

module C = [%styled.div (~what) => "width: $(what); height: 100%"]
(* pushes static properties ".div12312 {height: 100%}" *)
(* generates dynamic properties with CSS vars ".div12345 {width: (--var-what)}" *)
(* module C = <div className="div12312 div12345" style={ReactDOM.Style.make("--var-what": what)} /> *)

cx

let cx = [%cx "height: 100%"]
(* pushes properties ".hash {height: 100%}" *)
(* let cx = ".hash" *)

Ideal plan

Some JavaScript implementations

@davesnx davesnx changed the title CSS Extraction in styled-ppx CSS Extraction at build-time Mar 7, 2024
@davesnx
Copy link
Owner Author

davesnx commented Jul 4, 2024

The problem remaining to solve is the static interpolation on cx

let small = `px 10 in
let classname = [%cx {| margin: $(small); |}];

How can we access small, and turn it into a CSS variable?

Possible solutions

Solution A

Change cx into let%cx and expose everything inside the scope block:

let%cx classname = {
  let lola = `px 10 in
  {| font-size: $(lola) |}
};
let classname = {
  let _ = Css.global_var "lola" (`px 10); in
  "r4nd0mCl4sSN4M3"
};
:root {
  --lola: 10px;
}

.r4nd0mCl4sSN4M3 {
  font-size: var(--lola);
}

Problems

  • Complext AST transformations are always tricky
  • Changing the types is a bad idea
  • What to do with StyleVars approach?
    • What about name conflicts?
    • What about let bindings?
module StyleVars = struct
  let lola = `px 10
end

let%cx classname = {
  let lola = StyleVars.lola
  {| font-size: $(lola) |}
};

Solution B

Create a new css extension css_var that pushes into a global :root

let%css_var small = `px 10 in
(* Will be transformed into a side-effect + the value:
  let small = Css.publish_global "-small" `px 10; `px 10 in *)
let classname = [%cx {| margin: var(--small); |}]
:root {
  --small: 10px;
}

.lola {
  margin: var(--small);
}

Problems

  • What about let%css small = getSize ()? Probably enforcing to be a primitive (ensure it's not a runtime thingy)
  • What about Module.value access?
  • CSS Variables are global, what about collisions?

Solution C

cx has runtime and is an abstract type, also a new prop "cx" (<div cx) to transform from abstract to className/style

module Css = struct
  type cx = { static: string; dynamic: unit => ReactDOM.Style.t }

  let merge cxa cxb =
    { static: cxa.static ++ " " ++ cxb.static; dynamic: () => ReactDOM.Style.make(~unsafe=cxa.dynamic() ++ cxb.dynamic(), ()) }
end

let small = `px 10 in
let classname: CSS.cx = [%cx {| margin: $(small); |} { small }]
(* .lola {
  margin: var(--lola-small);
} *)
let classname: CSS.cx = { static: "lola", dynamic: Css.variable("small", small) };

Problems

  • What about dynamic with more than one variable?
  • Merging is an issue
  • Order is an issue
  • A lot of code to implement
let foo: string = [%cx {| margin: $(small); |}];
let bar: string = [%cx {| margin: $(small); |}];
let className = foo ++ " " ++ bar
let className = Css.merge foo bar

Solution D

Similar to B, but with a CSS-like approach with :root {}. Create a [%root] extension which expose everything to the global CSS root. Needs to work with structure_items with value bindings, but also with modules.

Example 1

[%root
  let lola = `px 10
]
:root {
  --lola: 10px;
}
let classname = [%cx {| font-size: $(lola) |}];
.lola {
  font-size: var(--lola);
}

Example 2

[%root
  module StyleVars = struct
    let lola = `px 10
  end
]
:root {
  --lola: 10px;
}
let classname = [%cx {| font-size: $(StyleVars.lola) |}];
.lola {
  font-size: var(--lola);
}

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

1 participant