-
Notifications
You must be signed in to change notification settings - Fork 463
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
React fragment's children type change and JSX4 adaptation #6238
Conversation
@cknitt Please refer to the test output, |
In my test project, the build seems to work well, and it also appears to work correctly at runtime. |
Are any of these changes something genType should be aware of? |
Oops, I couldn't think about the genType. Let me check. This is going to be a good chance for me to learn the genType implementation. |
Makefile
Outdated
@@ -26,7 +26,7 @@ node_modules/.bin/semver: | |||
test: lib | |||
node scripts/ciTest.js -all | |||
|
|||
test-syntax: | |||
test-syntax: build |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will cause CI to run the dune build + copyExe.js twice.
I should probably clean this up somehow at some point, but for this PR I'd prefer to keep the Makefile as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, noted. Reverted 9af6b6a
My current (large) project now builds again with this change, and I see no changes in the compiler output! 🎉 Thanks a lot! My project is in uncurried mode though, it won't build without, so I haven't tested that yet. |
@mununki Unfortunately our ReScript 11 project only builds with uncurried and JSX 4, and our other projects are not ReScript 11 ready yet. So it's hard for me to test JSX 3 and curried mode. Do you have a project/setup where you could try this? |
Wow, it seems you've already migrated your project to uncurried already! nice! |
With JSX4 classic, when using
|
But only without the changes I made in |
Ah that's clear because there were no children there previously: @module("react") external fragment: 'a = "Fragment" So everything looks good, provided @rescript/react is upgraded, too. |
Oh no, in classic mode it seems this gives |
Yes, it is intended. Now the ppx is looking for the children for fragment, that's why. |
Can you share the example code? |
<>
<div />
<div />
</> If we have key warning from this case, it is not related to fragment. |
Yes, it occurs on a simple example like this:
The divs should not need a key in this case and did not need one before, but with your changes they now do because React.createElement is created differently. I found this stackoverflow entry that also describes this: https://stackoverflow.com/questions/64889193/react-key-on-array-of-children-versus-multiple-hard-coded-children |
Ah, you're correct. It should not require the key for |
@cknitt Fixed missing key warning for JSX4 classic fragment, could you test it again? |
@cristianoc The type of React.fragment has changed, but since the type of React.fragment's application remains the same as React.element, I couldn't find anything that would affect genType. |
Perfect. Thank you for checking. |
@mununki Classic mode now works without any runtime warnings 🎉. However, there is still the following change in the generated code for Previously: return React.createElement(React.Fragment, undefined, ... /* the children */); Now return React.createElement(React.Fragment, {}, ... /* the children */); (Not sure if that is an issue.) |
The second one is correct as referring to the doc https://react.dev/reference/react/createElement#:~:text=props%3A%20The%20props,element.key.
I think previous output is not correct. 🤷 |
Now that I have the opportunity to look again, it seems that there are some things that need to be fixed in the conversion results for JSX4 classic. For example: let _ = <><div /></>
// generated
let _ = React.createElement(
React.fragment,
{children: ReactDOM.createDOMElementVariadic("div", [])},
)
// should be
let _ = React.createElement(
React.fragment,
{}
ReactDOM.createDOMElementVariadic("div", []),
) let _ = <Z><div /></Z>
// generated
let _ = React.createElement(Z.make, {children: ReactDOM.createDOMElementVariadic("div", [])})
// should be
let _ = React.createElement(Z.make, {}, ReactDOM.createDOMElementVariadic("div", [])) let _ = <div />
// generated
let _ = ReactDOM.createDOMElementVariadic("div", [])
// should be
let _ = ReactDOM.createDOMElementVariadic("div", {}) |
@cknitt Although there have been no issues at runtime so far, the expression is strictly different from the signature of React.createElement, so I've tried making some modifications regarding the parts I've summarized in my comment above. What do you think? EDIT: this is only change for JSX4 classic. The automatic is just fine. |
The issue lies in the second case. To summarize the issue a bit, // (JSX4 classic)
// module Z = {
// @react.component
// let make = (~children:React.element) => children // children prop is not optional
// }
// application
let _ = <Z> <div /> </Z>
// generated
let _ = React.createElementVariadic(Z.make, {children: React.null}, [<div />])
^^^^^^^^^^^^^^^^^^^^^
// should be
let _ = React.createElementVariadic(Z.make, {}, [<div />]) If the {children: React.element} corresponding to the prop record is not passed in the second argument position, a build error occurs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some high-level comments to what otherwise seems to be a pretty complete PR.
jscomp/syntax/src/reactjs_jsx_v4.ml
Outdated
| Exact children -> ( | ||
match config.React_jsx_common.mode with | ||
| "automatic" -> [(labelled "children", children)] | ||
| _ -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it's obvious by the discussion, but this is the aspect I'd like to dig into a little bit.
Can you explain again why there are 2 apparently different treatments for automatic vs non-automatic?
Is is possible to explain the need for a difference at a conceptual level, or is this just due to some implementation details? I wonder especially if this reflects a difference in the way React already treats the different modes, or if it is specific to ReScript.
Also why is that a string? Not clear we catch all the cases here, especially if more cases will be added later in addition to the current 3 cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder especially if this reflects a difference in the way React already treats the different modes
That's right, React handles the representation of classic and automatic differently with different runtimes. This is easy to see in the Babel playground.
(I couldn't find the share function in the Babel playground, so here's a screenshot.)
Also why is that a string? Not clear we catch all the cases here, especially if more cases will be added later in addition to the current 3 cases.
It doesn't have to be a string, because we take a string as an argument to -bs-jsx-mode, but it could be parsed as a variant. As for the exception handling in the parsing, I think we could use the default value and not have to use the option type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suddenly, I'm wondering if ReScript should only support the automatic React runtime, since the classic runtime is for React v16 and earlier. I withdraw my idea. We need to preserve backward compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the apparent extra complexity is due to React's choice (separate question is whether that is a good choice, but not relevant for this), everything looks good here, as far as I can see.
Go ahead if there are no pending issues.
@mununki I'll retest the last commit against my project with JSX4 classic. Could you add to the changelog as a breaking change that |
Tested again and looks good to me. |
Thank you so much for the test. I'll test JSX3 and JSX4 classic a bit more in my projects |
Sorry, my team has been using |
I found an issue when testing JSX4 classic: my latest implementation of passing children as the 3rd argument has one problem. I can't pass children of type I'll revert a couple of recent commits that tried to send children as a 3rd argument and clean up the PR. Sorry for the confusion. |
Side comment: I was once again surprised by the complexity of the React interface and implementation. I found the automatic runtime to be an improvement on that. |
28475ca
to
5e9536a
Compare
5e9536a
to
12d4c08
Compare
I've reverted, cleaned up, and rebased my latest attempt. |
No build or runtime issue found in JSX4 classic and automatic on my projects |
Perhaps no need to run make artifacts and commit the artifacts.txt file? |
This reverts commit 649a0f7.
Fixes #6234
This PR modifies the JSX4 implementation to accommodate the change of React fragment's
'children
type toReact.element
rescript-lang/rescript-react#94. The main changes are as follows:JSX4 classic
React*
instead ofReactDOM*
React.createElementVariadic
AddingReact.array
for children of fragmentJSX4 automatic:
React.array
for children of fragmentSide change:make test-syntax
wouldn't build the compiler, so I've changed it to build compiler first and test-syntax.