Skip to content
This repository has been archived by the owner on Jun 15, 2023. It is now read-only.

JSX V4 part 1 (to be continued). #547

Merged
merged 35 commits into from
Jun 25, 2022
Merged

Conversation

mununki
Copy link
Member

@mununki mununki commented Jun 15, 2022

This PR rewrites the JSX PPX which is aligned to #521.
Here's the Spec at the time of writing this. But please check if the spec has changed as part of this PR.

A further plans

  • forwardRef interface
  • Engage the feedback from the forum
  • Polish the loc properly
  • Test (e.g. newtype, type constraint cases)
  • Explore the surface for the new JSX transform withreact/jsx-runtime
    • Check the jsx binding in rescript-react. PR Jsx adaptions rescript-react#1
    • Add jsx.Fragment binding into the above jsx binding
    • Align to the core team regarding the possibility of support for jsx and createElement both or not.
  • Expose ref only for React.forwardRef
  • RescriptReactErrorBoundary impl and intf are mismatching in current version of rescript-react impl intf

Copy link
Contributor

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small comment on style: this looks a little strange:

let makePropsTypeParamsSig namedTypeList =
  namedTypeList
  |> List.filter_map (fun (isOptional, label, _, interiorType) ->
         if label = "key" || label = "ref" then None
         else if isOptional then Some (extractOptionalCoreType interiorType)
         else Some interiorType)

as it removes an @optional from the type. But that annotation was added by the ppx itself.

It might indicate that the logic for doing this should probably be further back in the caller, not in this function. So that the @optional attribute is never added in the first place. Otherwise, it could be easy to introduce bugs via refactoring without realizing it.

Copy link
Contributor

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this is pre-existing, but now that the code is formatted it really jumps to the eye.

Wrapping things in let jsxMapper () = indents everything, and groups a bunch of functions that don't really need to be grouped. How about just remove it and bring everything to the top level.

@cristianoc
Copy link
Contributor

Also this is pre-existing, but now that the code is formatted it really jumps to the eye.

Wrapping things in let jsxMapper () = indents everything, and groups a bunch of functions that don't really need to be grouped. How about just remove it and bring everything to the top level.

At that point, this global state:

let nestedModules = ref []

should be created in rewrite_implementation and rewrite_signature and passed around to the functions that need it.

@mununki
Copy link
Member Author

mununki commented Jun 17, 2022

A small comment on style: this looks a little strange:

let makePropsTypeParamsSig namedTypeList =
  namedTypeList
  |> List.filter_map (fun (isOptional, label, _, interiorType) ->
         if label = "key" || label = "ref" then None
         else if isOptional then Some (extractOptionalCoreType interiorType)
         else Some interiorType)

as it removes an @optional from the type. But that annotation was added by the ppx itself.

It might indicate that the logic for doing this should probably be further back in the caller, not in this function. So that the @optional attribute is never added in the first place. Otherwise, it could be easy to introduce bugs via refactoring without realizing it.

Absolutely, I totally forgot this part of generating sig, this strange code was written before you introduced the @optional syntax. I'll fix it.

@mununki
Copy link
Member Author

mununki commented Jun 17, 2022

Also this is pre-existing, but now that the code is formatted it really jumps to the eye.

Wrapping things in let jsxMapper () = indents everything, and groups a bunch of functions that don't really need to be grouped. How about just remove it and bring everything to the top level.

It sounds good to me!

@mununki mununki closed this Jun 17, 2022
@mununki mununki reopened this Jun 17, 2022
cli/reactjs_jsx_ppx_v3.ml Outdated Show resolved Hide resolved
@cristianoc cristianoc mentioned this pull request Jun 17, 2022
4 tasks
cli/reactjs_jsx_ppx_v3.ml Outdated Show resolved Hide resolved
@cristianoc
Copy link
Contributor

Probably best to rebase on master (see #565) sooner rather than later.
To avoid piling up merge conflicts.

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

What is the core team's plan for ppx v4 to support the new jsx transform? I found some feedbacks in forum that they want it, but there is a review not to support the new jsx transform. rescript-lang/rescript-react#1 (comment)
I think it is an easy task to make the ppx v4 support the new jsx transform, but I have questions about it.

  1. Does the ppx v4 need to support the new jsx transform? or support them both?
  2. If the ppx v4 needs to support them both with user's preference, what is the interface to get a configuration value from the compiler?

@cristianoc
Copy link
Contributor

What is the core team's plan for ppx v4 to support the new jsx transform? I found some feedbacks in forum that they want it, but there is a review not to support the new jsx transform. rescript-lang/rescript-react#1 (comment) I think it is an easy task to make the ppx v4 support the new jsx transform, but I have questions about it.

  1. Does the ppx v4 need to support the new jsx transform? or support them both?
  2. If the ppx v4 needs to support them both with user's preference, what is the interface to get a configuration value from the compiler?

One constraint is that the initial release will require no changes to rescript-react.
Does this constraint affect the ability to support the new jsx transform?

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

One constraint is that the initial release will require no changes to rescript-react.
Does this constraint affect the ability to support the new jsx transform?

The APIs for the new jsx transform are bound already here, but those are having attribute @deprecated. I think ppx v4 can use it without changes in rescript-react, but users will encounter countless warnings I guess.

@cristianoc
Copy link
Contributor

One constraint is that the initial release will require no changes to rescript-react.
Does this constraint affect the ability to support the new jsx transform?

The APIs for the new jsx transform are bound already https://github.com/rescript-lang/rescript-react/blob/ef325291733c8d42a53d87e8317371cbb6381011/src/React.res#L28, but those are having attribute @deprecated. I think ppx v4 can use it without changes in rescript-react, but users will encounter countless warnings I guess.

The PPX can issue code that silences deprecation warnings. Just like it can do for unused variables.
Another question is whether using the new jsx transform is a breaking change for existing projects?

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

Another question is whether using the new jsx transform is a breaking change for existing projects?

Sure, the first case that comes to mind is the project which has the dependencies under React v17. But I can't claim other cases for now. Maybe we can ask it in the forum.

@cristianoc
Copy link
Contributor

cristianoc commented Jun 19, 2022

Definitely something for the forum. Really need to know whom this will break. As that would make it necessary to have it configurable.
If configurable, probably in bsconfig.json.

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

If configurable, probably in bsconfig.json.

Yes, it is workable as one of configurations in bsconfig.json.

"reason": {
  "react-jsx": 4,
  "react-runtime": "automatic"  // or "classic" like the Babel configuration
}

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

I'm trying to figure out how to access the bsconfig value, but I think I'm not on right track. Can you give me a clue?
AFAIK, all syntax modules are copied to /jscomp/napkin in compiler for building, then napkin.cmx is linked to bsc.exe. But, the parser for the bsconfig.json and config values are accessed in rescript.exe only. Is it possible for ppx module to access the config value? I guess it is only possible backward. I mean bsc.exe is executed with args, so that the additional cli arg is needed to be executed by rescript.exe.

@cristianoc
Copy link
Contributor

I think that's right. bsc needs to be as fast as possible so it gets all the options passed to

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

It makes sense that the separate binary seems an architectural choice for performance.

@mununki
Copy link
Member Author

mununki commented Jun 19, 2022

I figured out and tested adding a new cli arg between rescript.exe and bsc.exe successfully. Now I can get a configuration value from bsconfig.json to syntax. I have a question and ask your thought about user interface and API between compiler and syntax.

bsconfig.json

"reason": {
  "react-jsx": 3 // or 4
  "react-runtime": "classic" // or "automatic"
}

cli args

-bs-jsx [3 or 4]
-bs-react-runtime [classic or automatic]

In this regard, If we put the ppx v3 and v4 both, then we can make users migrate their projects gracefully. What do you think?

@cristianoc
Copy link
Contributor

I figured out and tested adding a new cli arg between rescript.exe and bsc.exe successfully. Now I can get a configuration value from bsconfig.json to syntax. I have a question and ask your thought about user interface and API between compiler and syntax.

bsconfig.json

"reason": {
  "react-jsx": 3 // or 4
  "react-runtime": "classic" // or "automatic"
}

cli args

-bs-jsx [3 or 4]
-bs-react-runtime [classic or automatic]

In this regard, If we put the ppx v3 and v4 both, then we can make users migrate their projects gracefully. What do you think?

Suggestion: "reason" called "react" instead.
Then just "jsx" and "runtime".
There will be the schema to update when this is final, but there's time for that.

This could be a good time to begin thinking about migration. Having a version flag is useful, though not enough, to support migration. The flag is what's going to be used at the end of a gradual migration. What's left is supporting intermediate migration steps when one only migrates e.g. one component, or one dependent project.

@cristianoc
Copy link
Contributor

Something to check, once the ppx version flag is in place: what happens with dependencies? I guess they will be recompiled with whatever setting is used in the current project. But one needs to verify that this is true.

@cristianoc
Copy link
Contributor

How would you like to proceed.
By separating into v3 and v4 it's possible to review and merge this in soon, and keep on refining in a follow-on PR.

@mununki
Copy link
Member Author

mununki commented Jun 25, 2022

Sure, why not. I prefer keeping the open time of PR short either. Maybe, we can ship the ppx v4 as an experimental feature with V10 release, it allows us to get feedback and chances to refine the v4 and rescript-react.
As a side comment, I'd like to ask the core team for any chance to update the rescript-react for the official release ppx v4. I think there are missing bindings for the new jsx transform and it is outdated such asReasonReact.fragment.

@cristianoc
Copy link
Contributor

How about putting bindings for the new jsx transform directly inside the compiler? Under the Js.* namespace.
As from what I understand is not bound to react necessarily.

Default: none" );
( "-jsx-runtime",
Arg.String (fun txt -> jsxRuntime := txt),
"Specify the jsx runtime for React, classic or automatic. Default: \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option only affects V4 right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it affects V4 only.

@@ -2604,6 +2605,28 @@ and parseJsxProp p =
if optional then Asttypes.Optional name else Asttypes.Labelled name
in
Some (label, attrExpr))
(* {...props} *)
| Lbrace -> (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when this is parsed while V3 is active?
Should this be gated on version or is it OK like this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be OK with V3. V9 will give us a parsing error, but V10 will parse the JSX application as below, but it will be a type error anyway. Because the definition doesn't have a spreadProps as a prop. IMO, it doesn't make anything worse.

// lowercase
ReactDOMRe.createDOMElementVariadic(
  "div",
  ~props=ReactDOMRe.domProps(~spreadProps=foo, ()),
  [],
)

// uppercase
React.createElement(Foo.make, Foo.makeProps(~spreadProps=foo, ())),

Copy link
Contributor

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments.
Just checking that nothing is broken in V3 support at the moment.
Not really reviewing V4 fully.

@cristianoc
Copy link
Contributor

cristianoc commented Jun 25, 2022

I think what's out there, modulo a couple of pending questions, can be merged. And work on V4, and review on it, can continue separately.
So that one does not need to keep on rebasing this PR when master changes.
One obvious thing to review is the use of locations in V4, and what happens with editor integration. But it's OK to do that later.

@mununki
Copy link
Member Author

mununki commented Jun 25, 2022

How about putting bindings for the new jsx transform directly inside the compiler? Under the Js.* namespace. As from what I understand is not bound to react necessarily.

I can put the new jsx binding into Js.*, but can it make an unnecessary dependency of the compiler with react?

@cristianoc
Copy link
Contributor

How about putting bindings for the new jsx transform directly inside the compiler? Under the Js.* namespace. As from what I understand is not bound to react necessarily.

I can put the new jsx binding into Js.*, but can it make an unnecessary dependency of the compiler with react?
There's an entire ppx in the compiler. A couple of bindings don't seem like a big deal.
But, would like to see what they look like / how they are used to know better.

@mununki
Copy link
Member Author

mununki commented Jun 25, 2022

How about putting bindings for the new jsx transform directly inside the compiler? Under the Js.* namespace. As from what I understand is not bound to react necessarily.

I can put the new jsx binding into Js.*, but can it make an unnecessary dependency of the compiler with react?

How about adding some missing binding into rescript-react, and make additional type such as type domProps2, type props2 for V4 in ReactDOM.res with @optional attribute. I think it will not break anything.

@cristianoc
Copy link
Contributor

I think this can be merged right now, then continued.

@cristianoc cristianoc merged commit 5dedb5b into rescript-lang:master Jun 25, 2022
@cristianoc cristianoc changed the title JSX V4 JSX V4 part 1 (to be continued). Jun 25, 2022
@mununki mununki deleted the react-ppx-v4 branch June 25, 2022 08:23
@mununki
Copy link
Member Author

mununki commented Jun 25, 2022

Thanks for merging, I'm gonna make the new PR part 2.

@mununki mununki mentioned this pull request Jun 29, 2022
13 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants