GithubHelp home page GithubHelp logo

ml-in-barcelona / server-reason-react Goto Github PK

View Code? Open in Web Editor NEW
117.0 9.0 7.0 4.31 MB

Server rendering Reason React components with OCaml natively

Home Page: https://ml-in-barcelona.github.io/server-reason-react/local/server-reason-react/index.html

License: MIT License

Shell 0.04% Makefile 0.22% Reason 22.43% OCaml 72.64% C 0.02% HTML 0.04% JavaScript 0.18% Perl 3.60% Raku 0.84%
ocaml reason reason-react

server-reason-react's Introduction

Warning This repo contains a few parts that are considered alpha-stage and not ready for production. The parts that are more stable are used in production at app.ahrefs.com for all users and wordcount.com, but Belt, Js modules have missing APIs and non-implemented functions. This project enables sharing ReasonReact code between native and JavaScript and there's a lot of interesting pieces from this architecture. If you are interested, feel free to contact me in Discord or Twitter.

server-reason-react

Re-implementation of react, react-dom and react-dom/server to run on the server and a few related libraries in Reason/OCaml, to enable Server-side Rendering for reason-react applications.

Explained more details in this blog post sancho.dev/blog/server-side-rendering-react-in-ocaml

server-reason-react's People

Contributors

davesnx avatar jchavarri avatar joprice avatar pedrobslisboa avatar purefunctor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

server-reason-react's Issues

Make compatible with latest reason-react

The router in server-reason-react is named simply Router, whereas the react-react one is ReasonReactRouter. This makes it difficult to use in shared code. To work around it locally, you can define a module called ReasonReactRouter locally in native that includes Router.

Incorrect type for callbackDomRef

The type callbackDomRef in React.Ref is abstract, whereas it should be a function (type callbackDomRef = Dom.element Js.nullable -> unit) to match the interface of reason-react.

use with ocaml 5

What would it take to build the project on ocaml 5? I tried building from nix with an ocaml 5 overlay and ended up with a ppxlib error:

File "packages/intf_of_sign/intf_of_sign.ml", line 85, characters 4-26:
85 |     of_type_desc type_desc
         ^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type
         Ppxlib_ast.Ast.expression = Astlib__Ast_414.Parsetree.expression
       but an expression was expected of type Parsetree.expression

There is a constraint on < ocaml 5, so I assume there's some compatibility issues that need to be resolved first?

fix dom prop mappings

I've found multiple props that are not correct, resulting in html produced by the server suchas htmlFor instead of for. This also shows itself as warnings from hydrate such as Warning: Prop htmlFor did not match. Server: "null" Client: "search".

Js module is unstable

Many submodules of Js (server-reason-react.js) are unimplemented. They alert the user, and raise during runtime.

The Js module is (and probably always will be) not a great idea for SSR, all of this effort is a best-effort to mitigate most usages of it on react components. A universal stdlib is the way to go.

Nevertheless, we can do a lot of work to make the problem disapear.

This list are all the todos or wontfixes for those submodules:

  • Js.Re ๐ŸŸ  #45
  • Js.Exn ๐Ÿ”ด
  • Js.Array/Js.Array2 ๐ŸŸ 
  • Js.String/String2 ๐ŸŸ 
  • Js.Nullable/Js.Undefined ๐ŸŸ 
  • Js.Promise ๐ŸŸข
  • Js.Date ๐Ÿ”ด
  • Js.Dict ๐ŸŸข *
  • Js.Global ๐Ÿ”ด
  • Js.Json ๐Ÿ”ด
  • Js.Math ๐Ÿ”ด
  • Js.Obj ๐Ÿ”ด
  • Js.Float ๐Ÿ”ด
  • Js.Option ๐Ÿ”ด
  • Js.List ๐Ÿ”ด
  • Js.Console ๐ŸŸ 

๐ŸŸ  half-implemented
๐Ÿ”ด all-not-implemented
๐ŸŸข implemented

Congrats on the project

I'm a huge fan of ReasonML, and the main issue that prevented me from continuing with some projects was the lack of native SSR. Rescript evolved into something quite different from ReasonML and OCaml, which was also frustrating for me.

I discovered this repository more than a year ago, and it reignited my motivation. And, to better understand how this repository and feature works, I created this project: https://github.com/pedrobslisboa/universal-portal

I plan to bring all my known issues here so that we can discuss them more thoroughly.

Congrats on the project.
See you.

Warning: Extra attributes from the server: data-reactroot

Hey @davesnx, long time no see.

Captura de Tela 2024-04-09 aฬ€s 18 00 45

I've noticed that since I updated the server-reason-react to 0.1.0 and using react 18+ I'm having this issue, I believe react 18 removed the data-reactroot attribute since I cannot find it on code: https://github.com/search?q=repo%3Afacebook%2Freact+data-reactroot&type=code

Maybe it needs to be removed from the source code?

let react_root_attr_name = "data-reactroot"

I'm solving it by replacing an empty value.

      Str.global_replace(
        Str.regexp("data-reactroot=\"\""),
        "",
        Str.global_replace(
          Str.regexp("<div id=\"root\"></div>"),
          "<div id=\"root\">" ++ html ++ "</div>",
          indexFileString,
        )
        |> UniversalPortal_Server.appendUniversalPortals(_, portals^),
      )
      |> Dream.html;

You can check that on this nextjs 13 with react 18 there is no data-reactroot.

https://stackblitz.com/edit/nextjs-d9sshp?file=package.json

Add browser_only to all React.useEffect/useLayoutEffect automatically

All useEffect look either:

React.useEffect0(
    [%browser_only
      () => {
		/* Stuff */
      }
    ],
  );

or

let%browser_only effecting = () => {};

React.useEffect0(effecting);

Looks less to type but comes with some weird cases (only React.useEffect0/1/.. works, if let useEffect = React.useEffect; and later used useEffect it won't add the browser_only for you.

Put all ppxes under server-reason-react-ppx

Currently we have a bunch of ppxes:

  • server-reason-react-ppx (to transform JSX and domProps)
  • pipe_first (enable pipe first in native)
  • browser (the ppx to code-split from the client)
  • regex (a re ppx that bings to pcre)
  • double_hash_ppx (that's a silly one but enables ## syntax on objects)

I'm thinking to embed all ppxes inside server-reason-react-ppx to limit the surface area and to not users face the issue over and over. The downside seems like regex isn't stable enough so relying on it feels risky, but even that pipe_first and browser might be a valid additions

Missing dependency

Trying to run it locally I face this error:
image

Doesn't it needs to be set on pin-depends? Does it belong to Opam repo?

pin-depends: [
  ["quickjs.0.1.1" "git+https://github.com/ml-in-barcelona/quickjs.ml#0.1.1"]
]

mismatching type for Context

The Context module is missing a type 'a t as the reason-react context module has, preventing assigning an explicit type that works across the two libs in shared code. I have a fix here, along with a test that compiles a melange and native version of the same file, just to trigger compilation. This would require adding a reason-react test dependency. This branch also has a temporary nix flake, just for my local development purposes, which can be ignored.

https://github.com/ml-in-barcelona/server-reason-react/compare/melange-v2...joprice:server-reason-react:crossTest?expand=1#diff-cadade5b8a347e2aa9719675d7bb80b188df08ecd725c63a5e7c2fe60783ade7

Cannot run this project in MacOS with Nix

Reproduces

  • clone this repository
  • run command nix develop -c $SHELL
  • run command dune build
  • Error
server-reason-react ๎‚  main via ๐Ÿซ v5.1.1+dev0-2023-09-14  โ„๏ธ  impure
โฏ dune build
File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Location, and its interface was not compiled with -opaque
File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Stdppx, and its interface was not compiled with -opaque
File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Compmisc, and its interface was not compiled with -opaque

File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Location, and its interface was not compiled with -opaque
File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Alcotest_engine__Test, and its interface was not compiled with -opaque

File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Alcotest_engine__Core, and its interface was not compiled with -opaque

File "_none_", line 1:
Error (warning 58 [no-cmx-file]): no cmx file was found in path for module Alcotest_engine__Config, and its interface was not compiled with -opaque

Thank you

browser_only for types

Let's go with an example:

type response = Fetch.Response.t;

While fetch comes from melange-fetch it's a melange package, so all usages on the native site would crash at server runtime.

With the browser_only annotation type%browser_only response = Fetch.Response.t will make sure response is only handled inside browser_only functions.

Remove all bindings from webapi and stup them

Currently server-reason-react.webapi is almost a copy of https://github.com/melange-community/melange-webapi. The library is changed to be compiled, but not run. If you ever run it, there's going to be crazy linker issues (due to the fact you are binding to JavaScript when it doesn't exist)

Truncated error:

"_$84$95$a6$be$00$00$00C$00$00$00$12$00$00$009$00$00$004$b0$90$a0$a0$93$91$2fselectionchangeA$a0$a0AA$a0$a0$93$92$24trueA$a0$a0AA$40E$c53removeEventListener$40A$40", referenced from:
      _camlWebapi__Dom__GlobalEventHandlers.1 in webapi.a(webapi__Dom__GlobalEventHandlers.o)
  "_$84$95$a6$be$00$00$00D$00$00$00$12$00$00$00$3a$00$00$005$b0$90$a0$a0$93$910compositionstartA$a0$a0AA$a0$a0$93$92$24trueA$a0$a0AA$40E$c53removeEventListener$40A$40", referenced from:
      _camlWebapi__Dom__EventTarget.1 in webapi.a(webapi__Dom__EventTarget.o)
  "_$84$95$a6$be$00$00$00E$00$00$00$12$00$00$00$3a$00$00$005$b0$90$a0$a0$93$911compositionupdateA$a0$a0AA$a0$a0$93$92$24trueA$a0$a0AA$40E$c53removeEventListener$40A$40", referenced from:
      _camlWebapi__Dom__EventTarget.1 in webapi.a(webapi__Dom__EventTarget.o)
  "_$84$95$a6$be$00$00$00F$00$00$00$12$00$00$00$3a$00$00$005$b0$90$a0$a0$93$912animationiterationA$a0$a0AA$a0$a0$93$92$24trueA$a0$a0AA$40E$c53removeEventListener$40A$40", referenced from:
      _camlWebapi__Dom__EventTarget.1 in webapi.a(webapi__Dom__EventTarget.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Solution

Would be nice to implement a non breaking for all of the methods while keeping the interface untouched.

Implement debugger in melange_native.ppx

It would be nice to be able to insert a [%mel.debugger] in shared code and have it erased in native code. I'm not that familiar with what ppx operations are possible. Maybe this could be done globally, but it could also be a new extension. Here's a simple attempt at it that works so far:

let debug =
    let extractor = Ast_pattern.(__') in
    let handler ~ctxt:_ { loc } =
      (* TODO: check that no args were provided (PStr with empty structure array?) and error out *)
      match !mode with Js -> [%expr [%mel.debugger]] | Native -> [%expr ()]
    in
    Context_free.Rule.extension
      (Extension.V3.declare "debug" Extension.Context.expression extractor
         handler)

browser_only: functions with type annotations fail to compile

The browser ppx does not handle the Pexp_constraint variant when traversing expressions, so adding a type annotation to a binding that uses browser_only causes a compilation error. I encountered this when one browser_only binding with a type annotation was calling another:

This expression is marked to only run on the browser where JavaScript can run. You can only use it inside a let%browser_only function.
module ResultSyntax = {
    type t('a, 'b) = Js.Promise.t(result('a, 'b));

    let%browser_only lift = f => f |> map(Result.ok);
    let%browser_only liftResult = v => return(v);
    let%browser_only map = (f, p) => bind(result => Result.map(f, result) |> return, p);
    let%browser_only return = v => return(Ok(v));
    let%browser_only bind = (f, p) =>
      bind(
        result => {
          result |> Result.fold(~ok=value => f(value), ~error=error => resolve(Error(error)))
        },
        p,
      );

    // these functions fail to compile. Removing the type annotation allows them to succeed
    let%browser_only (let+): (t('a, 'b), 'a => 'c) => t('c, 'b) = (p, f) => map(f, p);
    let%browser_only ( let* ): (t('a, 'error1), 'a => t('c, 'error2)) => t('c, 'error2) =
      (p, f) => bind(f, p);
    let%browser_only discard: Js.Promise.t(unit) => unit = value => ignore(value);
    let%browser_only void = p => map(_ => (), p);
  };

Error: Version 3.8 of the dune language is not supported.

Greetings,

Been away a while. Do I need to change the version of dune or the compiler here to get this to run?

 mando@mando ๎‚ฐ ~/git/server-reason-react ๎‚ฐ ๎‚  main ๎‚ฐ make build                                                                                                                                 ๎‚ฒ โœ” ๎‚ฒ 2249 ๎‚ฒ 09:53:52 
File "dune-project", line 1, characters 11-14:
1 | (lang dune 3.8)
               ^^^
Error: Version 3.8 of the dune language is not supported.
Supported versions of this extension in version 3.8 of the dune language:
- 1.0 to 1.12
- 2.0 to 2.9
- 3.0 to 3.6
make: *** [build] Error 1

Trying to jump back in. Thanks.

missing useDeferredValue hook

The useDeferredValue hook is not defined, preventing usage in shared code. This can probably just be the identity function.

Error when using with styled-ppx

When trying to use server-rr and styled-ppx in the same project, I get this error:

File "_none_", line 1:                                         
Error: Files /home/me/code/monorepo/_opam/lib/styled-ppx/lib/ppx.cmxa
       and /home/me/code/monorepo/_opam/lib/server-reason-react/ppx/ppx.cmxa
       both define a module named Ppx

I was unsure where to open the issue, prob worth renaming the module on both repos, as it's such a common name.

missing drag event handlers

When trying to use the onDragEnter attribute, I get the error

Fatal error: exception Failure("Attribute not implemented, open an issue")

Make ReasonReactRouter.useUrl return ~serverUrl or throw

Hi, this is a really awesome library! I'm having fun re-learning OCaml and ReasonML with it.

Based on the documentation for ReasonReactRouter.useUrl:

/** hook for watching url changes.
 * serverUrl is used for ssr. it allows you to specify the url without relying on browser apis existing/working as expected
 */

This is reflected in the implementation where if the serverUrl is passed, it just returns that--otherwise, it calls into JS in order to obtain the URL (which is not possible in a server context).

In server-reason-react, useUrl returns an empty url and completely ignores serverUrl. If used in a shared component, routing silently fails and the [] gets matched every time:

module App = {
  [@react.component]
  let make = (~serverUrl: option(ReasonReactRouter.url)=?) => {
    let url = ReasonReactRouter.useUrl(~serverUrl?, ());
    switch (url.path) {
    | [] => <div> {React.string("Index!")} </div>
    | [name] => <Counter name />
    | _ => <div> {React.string("Not Found!")} </div>
    };
  };
};

The workaround I came up with is:

module App = {
  [@react.component]
  let make = (~serverUrl: option(ReasonReactRouter.url)=?) => {
    let url =
      switch%platform (Runtime.platform) {
      | Client => ReasonReactRouter.useUrl(~serverUrl?, ())
      | Server =>
        switch (serverUrl) {
        | Some(serverUrl) => serverUrl
        | None => raise(Invalid_argument("Server component needs URL!"))
        }
      };
  
    switch (url.path) {
    | [] => <div> {React.string("Index!")} </div>
    | [name] => <Counter name />
    | _ => <div> {React.string("Not Found!")} </div>
    };
  };
};

Alternatively, having separate implementations as suggested by the code-structure docs is another reasonable solution.

What do you think of defining useUrl as the following?

let useUrl ?(serverUrl : ReasonReactRouter.url option) (_ : unit) =
  match serverUrl with
  | Some serverUrl -> serverUrl
  | None -> raise (Invalid_argument "Must provide serverUrl")

I could also write the PR for it--would be cool as a first contribution to the ecosystem ๐Ÿ˜„

Js.Re / [%re ...] problems

Js.Re.t or [%re] carries a few issues:

  • Not all regexes are supported (compared to the new RegExp in JavaScript)
  • Your project requires pcre to be installed in the system

server-reason-react doesn't generate makeProps (so interface files aren't correct)

makeProps is an interface by reason-react-ppx. It needs to exist since react needs to call the createElement with createElement(Component, { ... }) so to generate the { ... } we need to convert the arrow function labelled arguments to JavaScript objects (via mel.obj)

This doesn't exist on server-reason-react-ppx since we transform the let make function to be just a function with labelled arguments.

This has a few limits from server-reason-react:

  • Users can't use makeProps directly on the server
  • Interfaces won't match if you share .rei files

Just opening the issue to see if there are methods to fix the general problem, but I'm not very optimistic about it.

suppress more warnings in browser_only ppx

Some warnings are currently suppressed, but in some usages, they still fire, due to either a extra missing warning, the incorrect type for the expression, or not a wide enough location (such as taking the full function definition into account including unused arguments). This prevents enabling strict warnings or requires annotating code to suppress them.

I put up a pr to fix some of them: #55.

There is another case that could perhaps be improved, where a hook is defined, but only read from the js side. Perhaps in this case, let%browser_only could leave the expression as is and simply add an unused warning suppression instead of failing with browser only works on expressions or function definitions.

Ref module missing from reactDOM

The Ref module in reason-react is missing from reactDOM.ml. Adding the line module Ref = React.Ref to the ml and mli files is enough to get it working.

conflicting module names

On the melange-v2 branch, reactDom and react both provide a DomProps module:

File "_none_", line 1:
Error: Files /nix/store/xy03b4ijl7w1fg213swvjxwx8qrd74c5-ocaml5.1.0-rc3-server-reason-react-n-a/lib/ocaml/5.1.0-rc3/site-lib/server-reason-react/reactDom/reactDOM.cmxa
       and /nix/store/xy03b4ijl7w1fg213swvjxwx8qrd74c5-ocaml5.1.0-rc3-server-reason-react-n-a/lib/ocaml/5.1.0-rc3/site-lib/server-reason-react/react/react.cmxa
       both define a module named DomProps

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.