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

Add @browser_only #185

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions demo/server/Error.re
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
let handler =
Dream.error_template((error, info, suggested) => {
Dream.error_template((_error, info, suggested) => {
let status = Dream.status(suggested);
let code = Dream.status_to_int(status);
let _code = Dream.status_to_int(status);
let reason = Dream.status_to_string(status);
Dream.html(
ReactDOM.renderToStaticMarkup(
Expand Down
1 change: 0 additions & 1 deletion demo/server/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
(name server)
(enabled_if
(= %{profile} "dev"))
(flags :standard -w -26-27) ; browser_only removes code form the server, making this warning necessary
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:3 🎉

(libraries
dream
demo_shared_native
Expand Down
2 changes: 1 addition & 1 deletion demo/server/server.re
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let renderToStreamHandler = _ =>
let%lwt () = Dream.write(response_stream, data);
Dream.flush(response_stream);
};
let%lwt (stream, _abort) =
let%lwt (_stream, _abort) =
ReactDOM.renderToStream(~pipe, <Document> <Comments /> </Document>);
Lwt.return();
},
Expand Down
2 changes: 1 addition & 1 deletion demo/universal/native/lib/App.re
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ let make = () => {
<Stack gap=8 justify=`start>
<Title />
<Hr />
<Counter initial=22 />
<Counter initial=22 onClick={_ => print_endline("Clicked")} />
</Stack>
</Layout>;
};
27 changes: 16 additions & 11 deletions demo/universal/native/lib/Counter.re
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
[@warning "-27"];
let make = (~initial, ~onClick as [@browser_only] onClick=?, ()) => {
let (count, [@browser_only] setCount) = RR.useStateValue(initial);

let make = (~initial) => {
let (state, setCount) = RR.useStateValue(initial);
[@browser_only]
let onClick = e => {
setCount(count + 1);
Js.log2("Printing count", count);

let onClick = _event => {
setCount(state + 1);
switch (onClick) {
| Some(onClick) => onClick(e)
| None => ()
};
};

<div className={Theme.text(Theme.Color.white)}>
Expand All @@ -20,26 +25,26 @@ let make = (~initial) => {
{React.string("Counter")}
</p>
<button
onClick
className="font-mono border-2 py-1 px-2 rounded-lg bg-yellow-950 border-yellow-700 text-yellow-200">
{React.string(Int.to_string(state))}
className="font-mono border-2 py-1 px-2 rounded-lg bg-yellow-950 border-yellow-700 text-yellow-200"
onClick={[@browser_only] e => onClick(e)}>
{React.string(Int.to_string(count))}
</button>
</div>
</Spacer>
</div>;
};

[@react.component]
let make = (~initial: int) =>
let make = (~initial, ~onClick as [@browser_only] onClick=?) =>
switch%platform (Runtime.platform) {
| Server =>
React.Client_component({
import_module: "Counter",
import_name: "",
props: [("initial", React.Json(`Int(initial)))],
client: make(~initial),
client: make(~initial, ~onClick=_ => (), ()),
})
| Client => make(~initial)
| Client => make(~initial, ~onClick?, ())
};

let default = make;
65 changes: 42 additions & 23 deletions doc/browser_only.mld
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ For example, if you're using Webapi to query the DOM or using LocalStorage. This

{1 Usage}

The ppx expose a [browser_only] attribute that can be used to discard a function or a value, and [switch%platform] to conditionally execute code based on the platform.
The ppx expose a [[@browser_only]] attribute that can be used to discard functions, values, arguments, etc, and [[switch%platform]] to conditionally execute code based on the platform.

Add [server-reason-react.browser_ppx] into to your pps field under a dune stanzas (melange.emit, libraries or executable) in your dune files.
Add [[server-reason-react.browser_ppx]] into to your pps field under a dune stanzas (melange.emit, libraries or executable) in your dune files.

You would need to add it on both "server" and "client" dune files. Adding the [-js] flag [server-reason-react.browser_ppx -js] for the client and without for the server:
{[
{@txt[
; server exectuable
(executable
(name server)
Expand All @@ -26,46 +26,65 @@ You would need to add it on both "server" and "client" dune files. Adding the [-
]}

{2 browser_only}

A code with [[@browser_only]] attribute will keep the function untouched for the client build, but for the server build, will be transformed into to a dead code. If you try to run it on the server, it will fail as it does not exist on the server, and the compiler will crash.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add another page of documentation for [@browser_only], just to link to %browser_only and not lose the content. Since we have a bunch of code relying on it


{[
let%browser_only countDomNodes = (id) => {
let elements = Webapi.Element.querySelector("#" ++ id);
let arr_elements = Webapi.Element.toArray(elements);
Array.length(arr_elements);
};
[@browser_only]
let sayHello = () => "Hello";

sayHello();
]}

Error:
{@txt[
20 | sayHello();
^^^^^^^^
Error: Unbound value sayHello
]}

The method tagged by [browser_only] will keep the function untouched for the client build, but for the server build, will be transformed to a function that raises the exception [Runtime.Impossible_in_ssr].
The [[@browser_only]] attribute can be used in any kind of code, including let bindings, function, module, args, etc. Checkout the {{:https://github.com/ml-in-barcelona/server-reason-react/blob/main/packages/browser-ppx/tests/at_browser_only.t}browser_only tests} for more examples.

Checkout the following example:
{[
[@browser_only]
let getName = (person) => person.name;

let getLastName = ([@browser_only] person) => {
[@browser_only]
let lastName = _ => person.last_name;

"Doe";
};

If this function runs on the server, it will raise an exception, and the server will crash. In order to prevent this, you can use [try] to catch the exception and provide a default behaviour/value.
let hello_world = "Hello world";
]}

Following with the example from above:
The final result of the code above, on server, will be:
{[
let%browser_only countDomNodes = (id) => {
let elements = Webapi.Element.querySelector("#" ++ id);
let arr_elements = Webapi.Element.toArray(elements);
Array.length(arr_elements);
}

let main = id =>
try(countDomNodes(id)) {
| _ => 0
};
();

let getLastName = (_) => {
"Doe";
};

let hello_world = "Hello world";
]}

{2 switch%platform}

[switch%platform] allows to conditionally execute code based on the platform. There are some cases where you need to run a specific code only on the server or only on the client.

{[
switch%platform (Runtime.platform) {
switch%platform () {
| Server => print_endline("This prints to the terminal")
| Client => Js.log("This prints to the console")
};
]}

{2 @platform attribute}

The [@platform] attribute allows to specify code blocks that should only be included in the JavaScript or native build, respectively. This is useful when you have code that is specific to one platform and should not be included in the other.
The [[@platform]] attribute allows to specify code blocks that should only be included in the JavaScript or native build, respectively. This is useful when you have code that is specific to one platform and should not be included in the other.

For example, you can define two modules, but only one of them should be kept in the final build based on the platform.
{[
Expand Down
2 changes: 1 addition & 1 deletion dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
; Dev dependencies
(ocamlformat
(and
(= 0.26.2)
(= 0.27.0)
:with-test)) ; We use ocamlformat on the tests
(ocaml-lsp-server :with-dev-setup)

Expand Down
Loading