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:
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).
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:
Bump jsx version in bsconfig from 3 to 4
a. Make sure you have react and react-dom >=17 as a npm dependency
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
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?
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.
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 />
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:
Is that escaping sufficient or do you think it should be first-classed into the syntax?
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?
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.
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).
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)
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.
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:
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.
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.
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.
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.
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.
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.
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!