OCaml
This guide assumes that you’ll use opam and dune and have a server or native process running.
Install
opam install styled-ppx
Packages available
styled-ppx
is the ppx to transform[%styled.div ""]
and[%cx ""]
styled-ppx.native
is the library with the CSS bindings, and the implementation ofemotion.sh
on the server: capable of storing CSS, hashing it, generating a unique classnames, and autoprefixing
Usage
Add styled-ppx
under dune’s preprocess pps for any library or executable; and add styled-ppx.native
as a library. Regardless of being a library or executable:
(library
(name ...)
(libraries
+ styled-ppx.native
server-reason-react)
(preprocess
(pps
+ styled-ppx
server-reason-react-ppx)))
(executable
(libraries
+ styled-ppx.native
server-reason-react)
(preprocess
(pps
+ styled-ppx
server-reason-react-ppx)))
Note: server-reason-react
and server-reason-react-ppx
are optional, and only needed if you use styled components ([%styled.div {||}]
).
API
CSS.get_stylesheet
returns a string with all stylesCSS.style_tag
returns a<style />
React element, with all styles. This is designed to be used with server-reason-react
Example
let className = [%cx {|
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center
|}];
let stylesheet: string = CSS.get_stylesheet();
print_endline(className);
/* .css-1xuw4bg */
print_endline(stylesheet);
/*
.css-1xuw4bg {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
} */
Example with server-reason-react
/* This is a server-reason-react module with those styles encoded as a unique className */
module Link = [%styled.a (~color=CSS.hex("4299E1")) => {|
font-size: 1.875rem;
line-height: 1.5;
text-decoration: none;
margin: 0px;
padding: 10px 0px;
color: $(color);
|}];
/* This is a unique className pointing to those styles */
let layout = [%cx {|
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center
|}];
/* Later in a component */
let app =
<div className=layout>
<Link
color={CSS.hex("333333")}
href="https://sancho.dev"
rel="noopener noreferrer">
{React.string("sancho.dev")}
</Link>
</div>;
To generate the CSS on the server, you would need to use <CSS.style_tag />
. This component will render a <style>
tag with all the generated styles.
Given a Page
component to simulate a real-world scenario:
module Page = {
[@react.component]
let make = () => {
<html>
<head>
<CSS.style_tag />
</head>
</html>
}
}
A note on missing classNames
If you’re using dynamic CSS values, such as dynamic-components or interpolation based on runtime values, you would need to evaluate your app before extracting all the CSS. Let me explain it with a simple example:
module App = {
[@react.component]
let make = (~value) => {
let className = switch (value) {
| Some(value) => [%cx "margin: $(value)"]
| None => [%cx "margin: 0"]
};
<div className />
}
};
This component (App) needs to be rendered before the CSS.style_tag
otherwise the [%cx]
calls won’t be added to the stylesheet, since they aren’t being executed yet.
CSS.style_tag
returns a <style />
with all styles injected, and you can think of CSS’s stylesheet as a global registry for styles. If we place CSS.style_tag
in the <head>
and your App
in the <body>
, when we are rendering the head (happens before) the global registry won’t have the dynamic classNames until App is rendered, which happens later, when React renders the <body />
.
To solve this, we recommend: render the React application first as a string, and then inject the result as dangerouslySetInnerHTML
in the <body />
. This way we ensure that the execution of the App
happens before collecting all classNames.
/* `App` here is the entry component of your React application with the interactivity from React, while the `Document` component is running on the server and is static. */
module Document = {
[@react.component]
let make = () => {
let app = ReactDOM.renderToString(<App />);
<html>
<head>
<CSS.style_tag />
</head>
<body>
<div id="root" dangerouslySetInnerHTML={"__html": app} />
</body>
</html>
}
};
/* Let's assume we are using dream (from https://github.com/aantron/dream) */
let some_server_side_handler = _request => {
/* Here we render the entire HTML */
Dream.html(ReactDOM.renderToString(<Document />));
};
Advanced
If you don’t want to render the stylesheet directly, you can use CSS.get_stylesheet
to obtain the stylesheet
as a string
. In this case, the hydration with the client won’t be supported.
To make sure hydration works, you would need the following <style/>
tag:
React.createElement("style",
[
Bool("data-s", true),
String("data-emotion", "css " ++ CSS.get_string_style_hashes()),
DangerouslyInnerHtml(CSS.get_stylesheet())
], []
)