[RFC] ReScript React

The reason-react library is already closely tied to the ReScript ecosystem as the syntax for JSX and for creating new components ships with the rest of ReScript. To better reflect this close relationship we are planning that the 1.0 release of reason-react will be under a new name: rescript-react.

Aside from the name change, there are a couple explicit goals in this breaking change: full support for aria and data attributes, better performance when working with lowercase JSX elements (like <div />), support for exporting the props type of a component, and cleaning up many modules that have been deprecated since the last breaking change.

After some initial discussions it seems to make sense to break this into a couple steps:

rescript-react v0.10
This is a smaller change that mostly handles the actual name change. It will remove all deprecated/legacy modules and will rename all uses of ReasonReact. In order to upgrade, users will need to:

  1. Remove all uses of modules listed under the legacy folder in reason-react. This includes ReasonReact, ReasonReactCompat, ReactDOMRe, and more (you can see the modules in the /legacy folder of reason-react today).
  2. Remove reason-react from your project and add rescript-react
    a. ReasonReactRouter → RescriptReactRouter
    b. ReasonReactErrorBoundary → RescriptReactErrorBoundary

rescript-react v1.0
A larger change more centered on the ppx - adding support for React.jsx/s, full support of aria spec and data- attributes in JSX, moving a lot of checks for valid DOM nodes from the type checker into the ppx to improve compilation speed, cleanup of key and ref handling, and a new feature to export the type of your props from a module with a component in it. In order to make this upgrade, you would need to:

  1. Bump jsx version in bsconfig from 3 to 4
    a. Make sure you have react and react-dom >=17 as a npm dependency
  2. This could break existing code in a couple ways that need fixing:
    a. You have a component made with @react.component and then you call it without JSX and while using a key in props. JSX 4 removes the key from every props list because key isn’t really a prop - it’s an external mechanism React uses to track relationships between elements. In this case you’ll need to move to the JSX call for that component and it should work fine.
    b. Some aria props have been moved from an open string to a variant of all possible cases. If this is the case you might have an aria value like “mixed” which needs to be migrated to #mixed
  3. You might have different methods for attaching aria or data attributes to React elements. These can be deprecated in favor of attaching them directly in the jsx: <div ariaHidden=true \"data-secret"="true" />

Does this approach make sense? Any thing that’s missing and needs to be addressed?

28 Likes

Hi @rickyvetter could you site an example of above quote please

Can we allow kebab case dom elements (used for web compnents, <foo-bar />, <amp-img>)?
Ofcourse people usually do not use web components directly in their react apps but there are cases when working with third party libraries. My own use case is using amp components with nextjs.

Sure. Take a look at this code with comments:

module Button = {
  // this code did implicitly add a key to the type of props. it wouldn't with these changes
  @react.component
  let make = (~count: int) => {
    let times = switch count {
    | 1 => "once"
    | 2 => "twice"
    | n => Belt.Int.toString(n) ++ " times"
    }
    let msg = "Click me " ++ times

    <button> {msg->React.string} </button>
  }
}

// this code would break because key isn't passed in props anymore.
let buttonEl = React.createElement(
  Button.make,
  Button.makeProps(~key="foo", ~count=1, ()),
)

// this code would be fine
let buttonElJSX = <Button key="foo" count=1 />
2 Likes

So I think I would need more information to be able to speak fully to this. ReScript+ReasonReact today supports this with the existing escape syntax: <\"amp-img" src="wow" />. So I guess there’s a couple questions:

  1. Is that escaping sufficient or do you think it should be first-classed into the syntax?
  2. Right now this is treated exactly like a DOM element and allows the same type of props. Would you imagine this working more like existing externals where you define props?

Sounds perfect! This proposal works for us. Looking forward to v1!

1 Like

I have two primary questions.

  1. How will this affect ‘breaking’ the ecosystem. Is it possible to provide a compat layer so that larger projects don’t need to wait for all dependencies to move to rescript-react (essentially forcing every library to fork/update before people can upgrade)? Alternatively can a compat layer be provided the other way so that projects with the old Reason React version can upgrade dependencies before making the switch themselves? Either would help everyone move along.

  2. From reading your proposal it looks like the ReScript JSX will become more tightly coupled to React. Personally, I’d love to see the JSX be less tightly coupled. I love React and am happy to use it but I hope that ReScript outlives whatever the preferential framework currently is. We can see in the JavaScript community that the build tools are making JSX more generic so that it becomes available to more frameworks (e.g. the introduction of the new Babel JSX transform).

2 Likes

The ReScript JSX itself is pretty generic already. We are talking mostly about the extra transformation step that is the react-ppx (v3, soon v4). This one will be compiling to jsx calls, as outlined by the newest React blog post, so this is actually more decoupled than the current v3 version (which compiles to React.createElement)

I personally prefer to have this without escaping syntax, but we should see what are the consequences of having this.

Web components usually have custom props, you can take a look at amp components.

Okay cool! Thanks for the clarification. It’s difficult for me to see where the JSX as syntax support ends and the React specific tasks of react-ppx begin.

1 Like

Makes sense. I think the escaping/not escaping question is out of scope for this conversation as it’s at the syntax layer. I’m also not sure it’s even relevant as the best way to handle this might involve converting to camelCase naturally.

Support for web components with custom props is definitely not existing in the work I’ve done so far but I think adding support for them should be straightforward. The basic idea would be to do something like:

module AmpImg = {
  @react.component({wc: "amp-img"})
  external make: (~src: string) => React.element = "";
};

To be clear, these externals could be written today. Just a bit messy for the author as a lot of the code is a bit creative with types.

1 Like

This is a large part of why the proposal is to do this in two steps. The v0.10 change is basically only two things: deletion of things that have been long deprecated with compat modes and migration strategies already existing, global renaming of strings. This should be a pretty painless upgrade.

The v1.0 change is newer but it all happens at the ppx layer which versions independently from the code itself. v0.10 will fully support ppx 3 and 4, and v1.0 will only support ppx 4. So your compat mode is to stay in v0.10 while iterating.

@ryyppy has already touched on the coupling specifically but I want to mention that things are already very flexible. If you want a transform you can disable the React and swap for your own.

1 Like

The example you have shown looks good to me. It’s simple and concise.
The fact is that it’s like writing bindings for react components, I like it.

Just to set expectations - I am not committing to add this to the initial ppx v4, but it’s on my radar and hopefully won’t cause much issue. Additionally it’s completely an additive change so doesn’t require a ppx version bump and can be added later.

3 Likes
  • Would it be possible for the React-PPX to ship as a separate independent ppx instead of shipping it with the compiler? (it can just be part of the rescript-react package)

  • Would it be possible to configure on what sources (directories would be great I think) what ppx's run? This would allow an incremental update, and it would also allow to mix React JSX with another JSX that is not React.

The last would also solve some issues with migration to graphql-ppx from 0.x to 1.0 in large code bases.

1 Like

The ppx is shipped with the compiler because a large percentage of people use it and it improves performance significantly (there is no overhead of creating a new process per file since it’s built in). It’s possible to ship separately but I’m not sure there is much value here directly. Is this ask only related to your second question? Or do you have other reasons?

Good question, but a little outside the scope of these ppx changes. This kind of behavior is already built into the compiler at the file level with this kind of annotation at the top of your file. The intention is for this annotation to be exceptional so it certainly isn’t the most ergonomic. I think that if you want/need this behavior more broadly then you should open a new topic or post an issue on the compiler.

@config({flags: [|"-ppx", "path/to/custom/ppx"|]});

With regards to this proposed change, I don’t expect the set of changes that need to be done in lock-step with upgrade of JSX versions to be large enough to require both to co-exist. Inside of Facebook, this change took ~2 hours of work including lock-step changes, reviewing JS source changes, and automated testing.

It would be good to get info about non-React JSX you’re planning to mix with React JSX. This is pretty rare in JS and I would expect to be rare in ReScript too. It’s possible we could build support for this directly into the JSX, but without some examples it’s hard to say.

1 Like

Do you plan to release v0.10 as reason-react or as rescript-react. I.e., will it affect projects that have something like "^0.8.0" in their package.json?

No, rescript-react will be published as a separate package, and reason-react will not receive any changes, so reason-react projects will not break. We will also keep the old docs for those who still need it.

3 Likes

I hope we upgrade soon anyway, it’s just that we have a lot of ReactDomRe usages that we haven’t quite figured out how to replace. And anyway, wanted to make sure it won’t break suddenly for us on yarn install. Thank you!