Git Product home page Git Product logo

jsoo-react's Introduction

jsoo-react

Actions Status

Bindings to React for js_of_ocaml, including the JSX ppx.

Status: experimental phase

The library is expected to break backwards compatibility on minor releases.

Adapted from ReasonReact.

Bug reports and contributions are welcome!

Getting started

New project

For new projects, the best way to start is by using the jsoo-react template.

Existing project

  1. Install the jsoo-react package:

    opam install jsoo-react
  2. Add jsoo-react library and ppx to dune file of your executable JavaScript app:

        (executables
        (names index)
        (modes js)
        (libraries jsoo-react.lib)
        (preprocess
        (pps jsoo-react.ppx)))
  3. Provision React.js library

    jsoo-react uses require to import React and ReactDOM. This means that you will likely need to use a bundler such as Webpack or rollup.js.

    Note that at this moment, jsoo-react is compatible with React 16, so be sure to have the appropriate constraints in your package.json.

Contributing

Take a look at our Contributing Guide.

Acknowledgements

Thanks to the authors and maintainers of ReasonReact, in particular @rickyvetter for his work on the v3 of the JSX ppx.

Thanks to the authors and maintainers of Js_of_ocaml, in particular @hhugo who has been answering many many questions in GitHub threads.

Thanks to the Lexifi team for creating and maintaining gen_js_api.

Thanks to @tmattio for creating Spin and the jsoo-react template ๐Ÿ™Œ

And thanks to the team behind React.js! What an amazing library :)

jsoo-react's People

Contributors

davesnx avatar dependabot[bot] avatar glennsl avatar idkjs avatar jchavarri avatar keremc avatar naartjie avatar schinns avatar sim642 avatar zbaylin avatar zindel 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  avatar

jsoo-react's Issues

Recursive components

We need to support recursive components. Right now there's some code inherited from reason-react but it hasn't been tested or used in examples.

Revisit forwardRef

As @benschinn noticed I left an unused var the forwardRef implementation. Needs more thought / testing, via React.useRef, to make sure all things are working correctly.

Compiling without Webpack

Hi @jchavarri!

I've been setting up jsoo-react in my project and thought I'd give some feedback on the setup ๐Ÿ™‚

To give you some context, I'm using ocaml-crunch to generate OCaml modules for my static content. It simplifies the deployment drastically: with this setup, I can just ship an executable and don't have to worry about storing static files.
To serve the React app, I am embedding the compiled Js in an OCaml module and serve it from one of the endpoints of the server.

Now, all of this is pretty much automated with Dune configurations, however, the React app first needs to be processed by Webpack (or another bundler), because the code generated by Js_of_ocaml imports react.

This dependency on Webpack adds a lot of overhead to the setup, and I'm wondering if it would be possible to get rid of the imports in the generated Js code.

The HTML page could potentially get the react library from their CDN:

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

And the compiled Js could just assume that React is available.

I'm not very familiar with Js_of_ocaml, so I have no idea if that's feasible, but if it is, it would have some nice benefits:

  • Simplify the setup by removing the dependency on Webpack.
  • Improve build times, because we wouldn't need Dune to check the node_modules.
  • (Maybe?) this would simplify Server Side Rendering if one-day jsoo-react uses Tyxml

What do you think?

Remove superfluous JS output from ReactDOM.domProps

The domProps "builder" function has a ton of attributes:

https://github.com/jchavarri/jsoo-react/blob/631f9855c727994b12aa928116b9b7068ae15e5c/lib/reactDOM.mli#L65-L75

The resulting JS code generated by jsoo includes zeroes for all of them missing, which is just useless information that increases for every host element rendered (e.g. <div />).

There are some solutions / workarounds that I can think of:

  1. Reduce the amount of attributes (super short term ๐Ÿ˜… ) by wrapping <div /> with a "composite" (non host) component like Div, passing only the props the app will use.

  2. "Lift" the object creation up to the ppx, which has already all the information available. So instead of calling domProps here:

https://github.com/jchavarri/jsoo-react/blob/631f9855c727994b12aa928116b9b7068ae15e5c/ppx/ppx.ml#L528-L533

we would do something like:

match nonEmptyProps with
| (Labelled "onClick", expr) :: _ -> (* actually, mapping over the `nonEmptyProps` list, not matching it :) *)
    let mapped_expr = mapper.expr mapper expr in
    [%expr
      let empty = Ojs.empty_obj () in
      Ojs.set empty "onClick"
        (Ojs.fun_to_js 1 (fun inner ->
              [%e mapped_expr] (ReactEvent.Mouse.t_of_js inner))) ;
      Obj.magic empty]
  1. Explore using tyxml, which compiles the attributes down to a list, a representation that doesn't have this issue.

Add module Context

https://github.com/jchavarri/rroo/blob/7f5e9626742c9b2d8c602fb92c26544c7174d36e/lib/React.re#L43-L55

This is the way it works currently in ReasonReact:

๐Ÿ‘ˆ OpenRaw ReasonReact
type context = Foo | Bar;

let context = React.createContext(Foo);

module ContextProvider = {
  let make = context->React.Context.provider;

  [@bs.obj]
  external makeProps:
    (
      ~value: context,
      ~children: React.element,
      ~key: string=?,
      unit,
    ) =>
    {
      .
      "value": context,
      "children": React.element,
    } =
    "";
};

module Container = {
  [@react.component]
  let make = (~children) => {
    <ContextProvider value=Foo>
      children
    </ContextProvider>;
  };
};

module Component = {
  [@react.component]
  let make = (~children) => {
    let ctx = React.useContext(context);
    <div>
      // using value here...
    </div>;
  };
};

which can be simplified in:

๐Ÿ‘ˆ Open Abstracted ReasonReact
// ReactContext.re

module type Config = {
  type context;
  let defaultValue: context;
};

module Make = (Config: Config) => {
  let x = React.createContext(Config.defaultValue);

  module Provider = {
    let make = x->React.Context.provider;

    [@bs.obj]
    external makeProps:
      (
        ~value: Config.context,
        ~children: React.element,
        ~key: string=?,
        unit
      ) =>
      {
        .
        "value": Config.context,
        "children": React.element,
      } =
      "";
  };
};

Usage:

module Context = {
  type t = ...;

  include ReactContext.Make({
    type context = t;
    let defaultValue = ...;
  });
};

// Provider
<Context.Provider value={...}> children </Context.Provider>

// Consumer
let ctx = React.useContext(Context.x);

We could start providing an API similar to the first one, at least in a first stage, and without the need to deal with JS object {value, children} of the props or bs.obj and decide after that based on what's possible.

Improve bundle size by using arrays instead of objects for uppercase element props

Right now, we use a JavaScript object to convert from labelled arguments to a single value, so that the component implementation can receive them from ReactJS:

jsoo-react/ppx/ppx.ml

Lines 563 to 569 in d7290d3

[%expr
obj
[%e
Exp.array ~loc
(List.map
(fun (label, _, _, _) -> label_to_tuple label)
named_arg_list_with_key_and_ref )]]

However, this approach leads to generated code like:

function make$2(className, onClick, disabled, children, key, param) {
  var
  _a_ = caml_call2(Stdlib_Option[7], caml_jsstring_of_string, key),
  _b_ =
  {
    "key": caml_call1(Js_of_ocaml_Js[6][9], _a_),
    "children": children,
    "disabled": disabled,
    "onClick": onClick,
    "className": className
  };
  return caml_call2(React[12], make$1, _b_)
}

This leads to larger bundle size as minifiers are not able to remove the string constants in the props object.

There might be a work around that involves passing an array instead of an object. The only constraint is that this array has to be written and read in a consistent order, but because ppx has full knowledge about this, it should be feasible.

Fix external%component to not make assumptions about the JS component type

Reported by @glennsl in #106 (comment).

Current code generated by ppx for external%component adds a make function with arity 1 that just makes a function call to the JS component and pass props. But JS components can be objects too (forwardRef, class components and such).

Ppx should not make any assumptions about JS components.

We might work around this by making that the ppx output code something like this:

-let make
-  (Props :
-    < name: Js.js_string Js.t Js_of_ocaml.Js.readonly_prop   > 
-      Js_of_ocaml.Js.t)
-  =
-  (((Js_of_ocaml.Js.Unsafe.js_expr
-        "require(\"my-react-library\").MyReactComponent") Props)
-  [@warning "-20"]) in
fun ~name ->
-  fun ?key -> fun () -> React.createElement make (make_props ?key ~name ())
+  let t ?key () =
+    React.createElement
+      (Js_of_ocaml.Js.Unsafe.js_expr "require(\"my-react-library\").MyReactComponent" )
+      (make_props ?key ~name ())

So we just remove any of the assumptions and let React handle the component itself, directly.

Unintuitive error message from PPX: prop '' is not a valid prop for 'foo'

Passing a non-labeled argument to an element constructor/"function" (what do we call these?) results in a rather unintuitive error message.

Minimal repro

[@@@react.dom]

let () = area 51

Actual outcome

File "src/main.ml", line 3, characters 9-16:
3 | let () = area 51
             ^^^^^^^
Error: prop '' isn't a valid prop for a 'area'

Expected outcome

In this case I was really expecting it to call a function I had defined myself, but realizing the actual problem I would expect an error more along the lines of "Unexpected unlabeled argument. Props must be passed to elements as labeled arguments." Perhaps also prefixed with something like "react.dom PPX error: " to identify the source of the error.

And just to add some nitpicking: "a" should become "an" when followed by a vowel (or a syllable that sounds like a vowel), so "...a 'area'" should really be "...an 'area'". But that would be really complicated to implement and probably overkill for this, so maybe just go with "...isn't a valid prop for 'area'"?

Improve type-safety of string based props

After the merge of #66, the code has full control over the types of makeProps. Many props are defined as string when in reality (the HTML spec) doesn't allow any string and defines an enum or some sort of combination. It might happen to some props that are defined as int or bool as well.

The benefit from moving away from string-based API are:

  • Safety, within a string there could be a typo without noticing it.
  • Learnability. Users might be able to learn about HTML and their APIs.
  • Correctness, generate valid HTML does have a good impact on on-page SEO.

There have been a few efforts on rescript-react to push that direction but didn't land yet (a few comments by @dodomorandi in rescript-lang/syntax#235) and I'm not sure if there's a positive intent to do so.

We have the possibility to do it.

Other references:


The plan for this is to check TyXML and https://html.spec.whatwg.org/#global-attributes and manually ensure that most of our attributes make sense.

Further investigations are on my plate, where I might try to change the html.ml list of attributes into per-element basis and ensure that the generated HTML from React components is valid.

Force unit on component definitions

The make function needs unit at the end, but currently ppx allows to pass functions without it. It should prob be compulsory to avoid confusion, and if it's missing return some error.

Related: #106 (comment).

Running example

FYI, I had to change name of library in dune to jsoo_react_ppx to overcome this error:

โžœ  example esy
info esy 0.5.8 (using esy.json)
info building jsoo-react-ppx-esy@link-dev:../ppx
error: build failed with exit code: 1
  build log:
    # esy-build-package: building: jsoo-react-ppx-esy@link-dev:../ppx
    # esy-build-package: pwd: /Users/prisc_000/Downloads/jsoo-react-master/ppx
    # esy-build-package: running: 'refmterr' 'dune' 'build'
    File "dune", line 2, characters 7-21:
    2 |  (name jsoo-react-ppx)
               ^^^^^^^^^^^^^^
    Error: Invalid library name.
    Hint: library names must be non-empty and composed only of the following
    characters: 'A'..'Z', 'a'..'z', '_' or '0'..'9'
    error: command failed: 'refmterr' 'dune' 'build' (exited with 1)
    esy-build-package: exiting with errors above...

  building jsoo-react-ppx-esy@link-dev:../ppx
esy: exiting due to errors above

Thank you for doing this amazing work. I wish I was skilled enough to help you. Until then, I will run and report. Peace to you.

ReactEvent

Right now ReactEvent is just copy-pasta from ReasonReact ๐Ÿ˜„ ๐Ÿ

New updater functions being returned by useState on every render

The generated code for useState is:

function useState(x278) {
  var
  ___ =
    caml_js_wrap_callback_strict
      (1, function (param) { return caml_call1(x278, 0) }),
  x279 = Ojs[18].React.useState(___);
  function _$_(x280) {
    var
    _aa_ =
      caml_js_wrap_callback_strict
        (1, function (x281) { return caml_call1(x280, x281) });
    caml_call2(Ojs[16], x279, 1)(_aa_);
    return 0
  }
  return [0, caml_call2(Ojs[16], x279, 0), _$_]
}

This is problematic because every time a new function _$_ is created, which breaks the assumptions of React, where if you do

let data, setData = React.useState (fun () -> 2)

setData should remain constant over time, but I am not sure it's possible as it'd require removing caml_js_wrap_callback_strict. I'm not sure why wrapping is needed for functions that only take 1 param ๐Ÿค”

Bundle size analysis

Would be nice to do some analysis of resulting bundle size in a real world application, considering basic case (1 page) and how it evolves as more pages are added.

React shows warning when using `maybe` + None

When using a prop with maybe and passing None (e.g. maybe className None), React.js will emit a warning:

Warning: Invalid attribute name: ``
    in div

This happens in ml syntax:

React.Dom.render (div [|maybe className None|] []) (Html.element c) ) ;

And Reason syntax:

React.Dom.render(<div className=?None />, Dom_html.element(c))

I tried using any other string like _ or _unused and in those cases React does not complain ๐Ÿค” Another alternative would be to conditionally push to the array.

Should jsoo-react vendor react.js?

Right now, jsoo-react calls require to bring react.js libraries:

let react : react = Js_of_ocaml.Js.Unsafe.js_expr {|require("react")|}
let reactDom : reactDom = Js_of_ocaml.Js.Unsafe.js_expr {|require("react-dom")|}

This means that users of the library are forced to use some JavaScript bundler like Webpack to resolve these requires with the actual library code.

But we could instead vendor a version of React.js and connect OCaml bindings with the JavaScript library objects internally, without exposing all this connection details to end users. This is what some OCaml projects have been doing, like virtual_dom, that uses browserify to bundle the JavaScript library.

Upsides of vendoring:

  • No need to use Webpack or any bundler on client
  • More controlled environment (less chances of things going wrong as less parts required)
  • Simpler setup, templates, etc
  • Faster iteration loops

Downsides:

  • Vendoring would require to put React.js entry points (library objects) in some field under global jsoo_global_object with all the downsides that relying on globals bring.
  • It also means that there is less flexibility for users to decide which version of React.js they want to use. E.g. right now jsoo-react supports v16, but some user could start using v17 for some reason just by updating their package.json.

I guess it all comes down to how "close" to JavaScript ecosystem a project is: for projects leveraging a lot of JS related tooling, using Webpack or other bundler is not a pain (actually helps). But for projects leaning more on OCaml side, with very few JavaScript dependencies, adding bundler is actually overkill.

Any other things? What do you think?

recommended workflow for hmr/fast refresh

Is there a setup that allows for hmr or fast refresh? I enabled 'hot' in my webpack config, but I've run into a few issues: When I change a file in vim (running coc.nvim with ocaml-lsp), the js file is not generated. Explicitly running dune build gets it to generate, but then I see the error below:

[WDS] Hot Module Replacement enabled.
client:52 [WDS] Live Reloading enabled.
client:55 [WDS] App updated. Recompiling...
client:150 [WDS] Errors while compiling. Reload prevented.
Module build failed: Error: ENOENT: no such file or directory, open '../_build/default/src/react-test/main.bc.js'

Bindings to external JS components

One missing part is binding to JavaScript components. Right now there are some examples in core.mli (e.g. to Fragment) using gen_js_api, but they are written manually, and use some magic conversions due to gen_js_api inability to parse some types like < children: element Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t.

It'd be nice to have some solution that decorates externals (or some other type of node) to bind to these JS components, but some questions quickly arise:

  • is binding to global values the only available solution? This means users have to somehow expose the bound component library in jsoo_global_object, like it's done in example.
  • If not, should we support others?
  • is it ok to include these identity conversions in generated code?

OCaml syntax: support for custom elements (i.e. web components)

To interact with web components there needs to be some way to instantiate custom elements, and to have those elements take custom attributes.

Three options I can see that I believe would work today:

  1. Write calls to React.createDOMElementVariadic manually
  2. Write wrapper components in Reason (I would prefer not to depend on Reason just for that though.)
  3. Write wrapper components in JS, and associated bindings.

Are there any other options? It would be nice to have a bit more convenience.

Somewhat related to #93, as having a way to pass custom attributes to any element would also solve that.

Add description to all HTML/SVG attributes with ocaml.doc

Each prop might contain a small description of itself, that we could provide to the user. Which would be great to have as documentation on your editor with the hover (with ocaml.doc).

Plus would be a really complete feature if we have a documentation page for it.

Improve error messaging on `InvalidProp`

Errors look like this

โ†’ ~/C/g/r/jsoo-react on Type-safe-html-tags โœ— make

File "test.ml", lines 109-111, characters 2-10:
109 | ..((div ~cols:1 ~href:(("https://example.com")
110 |       [@reason.raw_literal "https://example.com"]) ~children:[foo] ())
111 |   [@JSX ])
Error: prop 'cols' isn't a valid prop for a 'div'

TypeScript version, from the current type-checked from TS

Type '{ children: Element[]; classId: string; }' is not assignable to type 'DetailedHTMLProps<ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement>'.
  Property 'classId' does not exist on type 'DetailedHTMLProps<ObjectHTMLAttributes<HTMLObjectElement>, HTMLObjectElement>'. Did you mean 'classID'?

We could aim to have a similar output as our type-checker but I believe is rather useless, the output on the labelled argument being miss-spelt or unknown looks like https://rescript-lang.org/try?code=DwIwrgLhD2B2AEAbaiCGBeAzAPngbwFsBnAcwFpsAlAU1QGMIA6IiAJwEtYSBfeYAenBQ42AFBA

Maybe we could aim for something more simple:

File "test.ml", lines 109-111, characters 2-10:
109 | ..((div ~cols:1 ~href:(("https://example.com")
110 |       [@reason.raw_literal "https://example.com"]) ~children:[foo] ())
111 |   [@JSX ])
Error: 'div' contains an invalid prop: 'cols'.
It only accepts 'Global attributes' defined in https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes

and including a fancy Levenshtein distance with the rest of the props from the element: Maybe you mean xxxx.

Error when running example

I successfully installed both npm and opam dependencies. Joo is generating js files. I am getting an error when I open bin/index.html.

Screen Shot 2019-05-28 at 10 44 44 AM

This is where the error is in App.bc.js:

Screen Shot 2019-05-28 at 10 55 20 AM

Wrong component type signature when using type annotation

[@@@react.dom]

let%component foo ~(bar : int option) =
  div ~children:[React.string (string_of_int (Option.value ~default:0 bar))] ()

The inferred type is bar:int -> ?key:string -> unit -> React.element, but it should be bar:int option -> ?key:string -> unit -> React.element. Removing the type annotation fixes it.

Make React.Dom.Style.make more bundle size friendly

Right now, every call to React.Dom.Style.make is compiled to a bunch of zeros on unused optional arguments.

This leads quickly to larger bundle sizes, e.g. for 7 usages of this function in some real world project, a prod bundle increases by ~17KB.

We should provide an API where the style properties are passed in a list, so unused ones don't lead into bundle costs.

e.g. like @davesnx does in jsoo-css:

  let box =
    React.Dom.Style.make([|
      color("333"),
      backgroundColor("C0FFEE"),
      padding("45px"),
      borderRadius("8px"),
      fontSize("22px"),
      fontWeight("bold"),
    |]);

I don't think we need type safety on each prop type, for a first version.

Go to definition doesn't work inside let%component

When playing around with @glennsl 's code being developed in #119, I noticed it is not possible to go to function definitions of either elements (div, a), or props "makers" (className, id).

I avoided this when we were using @@@react.dom preprocessing, but with the changes in PR linked above it becomes easier to track down the problem: the ppx should not intervene in any inner parts of the component, just wrap the implementation with what is needed (createElement, JS props make function, etc) and leave any locations in the implementation code as is.

Note the issue could be either that:

  • the locations of these ast nodes are erased by the preprocessor
  • or rather, that some generated ast nodes also reuse the original locations, so merlin gets confused

I tend to think the problem might be the latter ๐Ÿค” but this needs investigation.

Add linting on js files

There are some basic mistakes that can happen all the time in .js files. Adding eslint (or typescript?) would help mitigate that.

Use dune option ppx_runtime_libraries

There is a dune stanza ppx_runtime_libraries to include the runtime lib automatically.

Per dune docs:

(ppx_runtime_libraries (<library-names>)) is for when the library is a ppx rewriter or a [@@deriving ...] plugin and has runtime dependencies. You need to specify these runtime dependencies here

This would allow to get rid of (libraries jsoo-react.lib) (can update readme instructions as well)

OCaml syntax proposal: unlabeled children

Spoke briefly about this with @jchavarri yesterday.

Currently, both dom elements (lower-case components) and custom (upper-case) components require children to be passed through an argument labeled ~children, and to have a terminating unit argument since there's no other positional arguments. From a consumer point of view this seems unnecessary when children as an unlabeled argument could also serve the role as terminator.

Pros:

  • Less noise
  • Plays better with formatters

Cons:

  • Some extra transformation required to migrate from Reason
  • Reason's JSX will have to be transformed slightly differently

Example

Currently, with labeled children:

let%component make () =
  React.Fragment.make
    ~children: [
      a ~href:"https://github.com/jihchi/jsoo-react-realworld-example-app" ~target:"_blank" 
        ~children: [ 
          i ~className:"ion-social-github" ~children:[] (); 
          React.string "Fork on GitHub";
        ]
        ();
      footer
        ~children: [
          div ~className:"container"
            ~children: [
              Link.make ~onClick:(Link.location Link.home) ~className:"logo-font"
                ~children:[ React.string "conduit" ]
                ();
              span ~className:"attribution"
                ~children: [
                  React.string "An interactive learning project from ";
                  a ~href:"https://thinkster.io" ~children:[ React.string "Thinkster" ] ();
                  React.string ". Code &amp; design licensed under MIT.";
                ]
                ();
            ]
            ();
        ]
        ();
    ]
    ()

could become

let%component make () =
  React.Fragment.make [
    a ~href:"https://github.com/jihchi/jsoo-react-realworld-example-app" ~target:"_blank" [
      i ~className:"ion-social-github" [];
      React.string "Fork on GitHub";
    ];
    footer [
      div ~className:"container" [
        Link.make ~onClick:(Link.location Link.home) ~className:"logo-font" [
          React.string "conduit"
        ];
        span ~className:"attribution" [
          React.string "An interactive learning project from ";
          a ~href:"https://thinkster.io" [ React.string "Thinkster" ];
          React.string ". Code &amp; design licensed under MIT.";
        ];
      ];
    ];
  ]

or alternatively, if you prefer (same syntax, just different formatting)

let%component make () =
  React.Fragment.make
    [ a ~href:"https://github.com/jihchi/jsoo-react-realworld-example-app" ~target:"_blank"
        [ i ~className:"ion-social-github" []
        ; React.string "Fork on GitHub"
        ]
    ; footer 
        [ div ~className:"container" 
            [ Link.make ~onClick:(Link.location Link.home) ~className:"logo-font" 
                [ React.string "conduit" ]
            ; span ~className:"attribution" 
                [ React.string "An interactive learning project from "
                ; a ~href:"https://thinkster.io" [ React.string "Thinkster" ]
                ; React.string ". Code &amp; design licensed under MIT."
                ] 
            ]
        ]
    ]

Example fails to compile

The example project fails to compile on my machine, the message I get is:

michael@michael-ThinkPad-X1-Carbon-6th:~/Documents/tmp/jsoo-react/example$ esy
info esy 0.6.2 (using esy.json)
.... fetching @opam/js_of_ocaml@github:ocsigen/js_of_ocaml:js_of_ocaml.opam#0bf1                                                                                .... fetching @opam/js_of_ocaml-ppx@github:ocsigen/js_of_ocaml:js_of_ocaml-ppx.o                                                                                .... fetching @opam/js_of_ocaml-lwt@github:ocsigen/js_of_ocaml:js_of_ocaml-lwt.o                                                                                info fetching: done                                                            
info installing: done                                                                                 
info building @opam/dune@opam:1.11.3@9894df55
info building @opam/menhir@opam:20190626@bbeb8953
info building @opam/uucp@opam:12.0.0@b7d4c3df
info building @opam/uucp@opam:12.0.0@b7d4c3df: done
info building @opam/uuseg@opam:12.0.0@bf82c4c7
info building @opam/uuseg@opam:12.0.0@bf82c4c7: done
info building @opam/dune@opam:1.11.3@9894df55: done
info building @opam/cppo@opam:1.6.6@f4f83858
info building @opam/ppx_derivers@opam:1.2.1@ecf0aa45
info building @opam/result@opam:1.4@dc720aef
info building @opam/easy-format@opam:1.3.2@0484b3c4
info building @opam/dune-configurator@opam:1.0.0@4873acd8
info building @opam/mmap@opam:1.1.0@b85334ff
info building @opam/sexplib0@opam:v0.12.0@e432406d
info building @opam/dune-configurator@opam:1.0.0@4873acd8: done
info building @opam/re@opam:1.9.0@d4d5e13d
info building @opam/ppx_derivers@opam:1.2.1@ecf0aa45: done
info building @opam/jbuilder@opam:transition@58bdfe0a
info building @opam/jbuilder@opam:transition@58bdfe0a: done
info building @opam/ocaml-compiler-libs@opam:v0.12.1@5c34eb0d
info building @opam/mmap@opam:1.1.0@b85334ff: done
info building @opam/result@opam:1.4@dc720aef: done
info building @opam/ocaml-migrate-parsetree@opam:1.4.0@0c4ec62d
info building @opam/fpath@opam:0.7.2@45477b93
info building @opam/easy-format@opam:1.3.2@0484b3c4: done
info building @opam/biniou@opam:1.2.1@d7570399
info building @opam/menhir@opam:20190626@bbeb8953: done
info building @opam/atd@opam:2.0.0@087614b7
info building @opam/sexplib0@opam:v0.12.0@e432406d: done
info building @opam/base@opam:v0.12.2@d687150c
info building @opam/cppo@opam:1.6.6@f4f83858: done
info building @opam/ocplib-endian@opam:1.0@aa720242
info building @opam/biniou@opam:1.2.1@d7570399: done
info building @opam/merlin-extend@opam:0.5@a5dd7d4b
info building @opam/ocaml-compiler-libs@opam:v0.12.1@5c34eb0d: done
info building @opam/yojson@opam:1.7.0@7056d985
info building @opam/fpath@opam:0.7.2@45477b93: done
info building @opam/re@opam:1.9.0@d4d5e13d: done
info building @opam/tyxml@opam:4.3.0@c1da25f1
info building @opam/merlin-extend@opam:0.5@a5dd7d4b: done
info building @opam/atd@opam:2.0.0@087614b7: done
info building @opam/yojson@opam:1.7.0@7056d985: done
info building @opam/atdgen-runtime@opam:2.0.0@8a75c3bb
info building @opam/js_of_ocaml-compiler@opam:3.4.0@d2f7c406
info building @opam/atdgen-runtime@opam:2.0.0@8a75c3bb: done
info building @opam/atdgen@opam:2.0.0@5d912e07
info building @opam/atdgen@opam:2.0.0@5d912e07: done
info building @opam/tyxml@opam:4.3.0@c1da25f1: done
info building @opam/odoc@opam:1.4.2@187ed639
info building @opam/ocplib-endian@opam:1.0@aa720242: done
info building @opam/lwt@opam:4.3.0@865b709c
info building @opam/ocaml-migrate-parsetree@opam:1.4.0@0c4ec62d: done
info building @esy-ocaml/[email protected]@d41d8cd9
info building @opam/ppx_blob@opam:0.4.0@88e42e0d
info building @opam/ppx_tools_versioned@opam:5.2.3@4994ec80
info building @opam/base@opam:v0.12.2@d687150c: done
info building @opam/stdio@opam:v0.12.0@04b3b004
info building @opam/js_of_ocaml-compiler@opam:3.4.0@d2f7c406: done
info building @opam/stdio@opam:v0.12.0@04b3b004: done
info building @opam/ppxlib@opam:0.9.0@bfabe269
info building @opam/ppx_blob@opam:0.4.0@88e42e0d: done
info building @opam/odoc@opam:1.4.2@187ed639: done
info building @opam/lwt@opam:4.3.0@865b709c: done
info building @opam/ppx_tools_versioned@opam:5.2.3@4994ec80: done
info building @opam/js_of_ocaml@github:ocsigen/js_of_ocaml:js_of_ocaml.opam#0bf16c4@d41d8cd9
info building @opam/bisect_ppx@opam:1.4.1@e75b441f
info building @opam/bisect_ppx@opam:1.4.1@e75b441f: done
info building @opam/ocamlformat@opam:0.11.0@ec74e6e8
info building @esy-ocaml/[email protected]@d41d8cd9: done
error: build failed with exit code: 1
  build log:
    # esy-build-package: building: @opam/js_of_ocaml@github:ocsigen/js_of_ocaml:js_of_ocaml.opam#0bf16c4
    # esy-build-package: pwd: /home/michael/.esy/3/b/opam__s__js__of__ocaml-3a7c9c5f
    # esy-build-package: running: 'dune' 'build' '-p' 'js_of_ocaml' '-j' '4'
           ocaml (internal) (exit 2)
    /home/michael/.esy/3__________________________________________________________________/i/ocaml-4.8.1000-e74c5c0d/bin/ocaml -I +compiler-libs /home/michael/.esy/3/b/opam__s__js__of__ocaml-3a7c9c5f/_build/.dune/default/dune.ml
    fatal: not a git repository (or any of the parent directories): .git
    Exception:
    Failure
     "Command failed: git log -n1 --pretty=format:%h\nExit code: 128\nOutput:\n".
    error: command failed: 'dune' 'build' '-p' 'js_of_ocaml' '-j' '4' (exited with 1)
    esy-build-package: exiting with errors above...

I am using the following setup:

$ esy --version
0.6.2
$ ocamlc --version
4.08.0
$ dune --version
2.4.0

The problem seems to be that the directory is not a git repository despite having a .git folder. (See also https://github.com/jordwalke/rehp/blob/master/dune).

Now, I'm not sure if this a bug here or something that should be filed against esy or js_of_ocaml.
But I thought that even if it is not a jsoo-react issue, you could at least point me in the right direction.

Investigate unmounting effects not running

Thanks to @benschinn we have support for useEffect and useLayoutEffect ๐ŸŽ‰

One thing that remains to investigate are the unmount effects, which don't see to be running at the moment.

This can be seen in this line

https://github.com/jchavarri/rroo/blob/cfd84885338f055c6e1285f586de2f51043bb8b8/bin/UseEffect.re#L20-L22

that does not appear in the console.

In order to "bridge" the returned value with the effect we would have to, from React.js file:

  • read the returned value from the callback
  • check if it's None (0) or Some (array)
  • if it's Some, take the callback "from inside", convert to jsoo and return it as part as the React.useEffect function.

Remove filtering of undefined props passed to upper case components (except `key`)

There's no need to filter optional props on uppercase components when calling createElement, they can (and should) be passed as is. This will allow to get rid of array->list->array conversion and filtering on each element creation call.

The only exception is key property, which is handled by React.

Questions:

  • What about ref?

Transform `key` prop before passing over to JS

Right now, key is being passed as option from the ppx, but this is problematic because None is 0, so React complains of many children having the same key.

It needs to be converted with Js.Opt.option before passing it over.

Allow data-* attributes

Currently, there's no way to pass data-* into an element, while React allows it. There's currently a workaround where avoiding JSX transformations you can pass an object with the key being data-watever.

This makes it a little hard to create components that might rely on external from your react code, such as Analytics, error reporting and integration with older frontend systems.

The idea is to have a special case where data_what or dataWhat (or literally whatever we want, didn't think deeply about any edge case) gets transformed into data-what as an attribute on the DOM element.

Inexistent locations on error when component returns list of elements

let%component make () = [ React.null ]

Will trigger this error:

Error: This expression has type
         <  > Js_of_ocaml.Js.t -> React__Core.element list
       but an expression was expected of type
         <  > Js_of_ocaml.Js.t React.component =
           <  > Js_of_ocaml.Js.t -> React__Core.element
       Type React__Core.element list is not compatible with type
         React__Core.element

but with no locations.

external%component props from optional arguments are wrapped in option

There seems to be no special handling of optional arguments for external%component, which means that optional arguments are passed in as option values.

Given

external%component my_component ?num_leprechauns:int -> React.element = "..."

my_component () will cause 0 to be passed as num_leprechauns.

my_component ~num_leprechauns:42 () will cause [0, 42] to be passed as num_leprechauns.

It might be sufficient to convert the option value to a Js.optdef value, but I think the proper solution is to omit it from the props object if the argument is not passed. I'll try doing the simpler thing for now though.

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.