Skip to content
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

[RFC] Generic JSX transform #6408

Closed
zth opened this issue Sep 19, 2023 · 12 comments
Closed

[RFC] Generic JSX transform #6408

zth opened this issue Sep 19, 2023 · 12 comments

Comments

@zth
Copy link
Collaborator

zth commented Sep 19, 2023

I'm proposing a generic JSX transform where you bring your own JSX assets (functions to call, types for JSX nodes, etc), that ReScript can delegate to. This will both enable slicker integration with other FE frameworks than React that also use JSX, and enable innovation since JSX is just syntax sugar for function calls.

JSX is an XML-style syntax that's great for templating hierarchical things like HTML, while preserving the full strength of ReScript in your templating. With a generic JSX transform, one could leverage the fact that JSX is part of the ReScript language to build your own things leveraging ReScript's great JSX support. I believe this could unlock some innovation.

Background

We currently have a great React JSX transform in ReScript, including the @react.component transform for component support. This works well for React. It's possible to use the React JSX transform with other frameworks today by carefully overriding the React, ReactDOM and Jsx* modules. However, this is cumbersome, error prone, and is a hack.

Prior art

TypeScript let's you configure various things related to JSX: https://www.typescriptlang.org/tsconfig#jsxFactory

Proposal

I propose a simple way of swapping out the React JSX transform to your own custom one, and additionally a dedicated @jsx.component transform.

Configure a custom JSX module in bsconfig.json

{ "jsx": { "customModule": "MyJsxModule" } }

MyJsxModule is then what the JSX PPX delegates to for all JSX related functionality.

@jsx.component

A generic component transform that gives the same ergonomic experience as @react.component, but with none of the React specific quirks. We'll need to figure out which of the quirks belong in the generic PPX.

Complete example

Here's a sketch of a complete example of how this could look. Note: Hyperons is a lightweight lib for rendering JSX to a string of HTML.

bsconfig.json

{ "jsx": { "customModule": "Hyperons" } }
// Hyperons.res
type element

type domProps = {
  ...JsxDOM.domProps,
  @as("my-custom-dom-attribute")  myCustomDomAttribute?: [#on | #off]
}

type component<'props>

type fragmentProps = {children?: element}

@module("hyperons/src") external jsxFragment: component<fragmentProps> = "Fragment"

@module("hyperons/src")
external jsx: (component<'props>, 'props) =>element = "h"

@module("hyperons/src")
external jsxs: (string, domProps) =>element = "h"

external someElement:element => option<element> = "%identity"

@val external null: element = "null"

external float: float => element = "%identity"
external int: int => element = "%identity"
external string: string => element = "%identity"
external array: array<element> => element = "%identity"

@module("hyperons/src")
external render: element =>string = "render"
// SomeComponent.res
@jsx.component
let make = (~status) => {
  <div myCustomDomAttribute=status />
}
// App.res
let app = <SomeComponent status=#on />
let html = Hyperons.render(app)

Ending thoughts

It'll be important to ensure that all of this works seamlessly, including things like autocomplete via the editor integration for both user defined components and lowecase "HTML" dom nodes.

Interested in hearing any feedback really.

@tsnobip
Copy link
Contributor

tsnobip commented Sep 19, 2023

I also think this could unlock quite a lot of innovation for rescript, I'm all for it.

Could you detail in this example what part belongs to the JSX PPX transform? Is it the jsx and jsxs functions?

@mununki
Copy link
Member

mununki commented Sep 19, 2023

This seems quite feasible. Is there any particular reason to use jsx.customModule instead of using jsx.module="MyCustomModule" in the current compiler config?

@zth
Copy link
Collaborator Author

zth commented Sep 19, 2023

@mununki that would work well too. My thinking was mostly that React is special cased there, but that probably doesn't matter. Putting it directly in module works 👍

@cometkim
Copy link
Contributor

Maybe we can get even further than just custom prop types in this RFC.

There is JSX.IntrinsicElements type in the TypeScript.

Which allows to user overriding not only props, but also elements. So custom renderers like react-three-fiber can provide type definitions for their own host element (e.g. <mesh>, <ambientLight>)

There is a few points to change for this:

  • At least we should ensure that it is not being built in a way that relies on type/var names imply some host detail like "domProps"
  • There should be a definition that provides element-props type mapping.
    // might look like...
    
    type threeJsProps = {...}
    type meshProps = {...threeJsProps, ...}
    
    type intrinsicElements = {
      mesh: meshProps,
    }

@joshderochervlk-simplisafe

Out of all the feature requests and proposals for improvement this would have the largest impact on the ability to adopt ReScript. Being able to use it for things other than React would be huge!

@zth
Copy link
Collaborator Author

zth commented Sep 27, 2023

Currently occupied with other things, but maybe me and @mununki could pair on this and get an implementation going that we can test.

@zth
Copy link
Collaborator Author

zth commented Sep 27, 2023

Maybe we can get even further than just custom prop types in this RFC.

There is JSX.IntrinsicElements type in the TypeScript.

Which allows to user overriding not only props, but also elements. So custom renderers like react-three-fiber can provide type definitions for their own host element (e.g. <mesh>, <ambientLight>)

There is a few points to change for this:

  • At least we should ensure that it is not being built in a way that relies on type/var names imply some host detail like "domProps"
  • There should be a definition that provides element-props type mapping.
    // might look like...
    
    type threeJsProps = {...}
    type meshProps = {...threeJsProps, ...}
    
    type intrinsicElements = {
      mesh: meshProps,
    }

We definitively need a solution for this, it's great feedback. I just wonder if this should perhaps be a "step 2" thing in this proposal.

@zth
Copy link
Collaborator Author

zth commented Oct 5, 2023

@mununki I had a fairly thorough look at the existing JSX code and I think adding a generic one looks pretty straight forward. But, we need to make some assumptions. Here's a list of things I thought about when reading the code.

  • Call the transform @jsx.component instead of @react.component when doing the generic transform
  • We can disregard ref, forwardRef and other React specific things. We can also ignore classic vs automatic since that's a React thing
  • JSX v4 only for generic transforms, disregard v3
  • React utilizes both React.res, ReactDOM.res, and Jsx.res, right? I think a generic transform should point to a single module only (like VueJsx.res for example) that's responsible for defining and pulling in whatever's needed for JSX, including pullings things from Jsx.res if needed
  • It's important to preserve the possibility of changing JSX config on the fly with @@jsx

@mununki what do you think?

@mununki
Copy link
Member

mununki commented Oct 6, 2023

Totally agree with your assumption. I think preserve mode should definitely be implemented.

@zth
Copy link
Collaborator Author

zth commented Jan 14, 2024

Started working on a generic transform here: #6565

Please leave any feedback you might have.

@haehgeuwhveaw
Copy link

I have used this one.
https://github.com/kitajs/html

@zth
Copy link
Collaborator Author

zth commented Jan 15, 2024

I have used this one. https://github.com/kitajs/html

Cool! Perfect example of something that would be very easy to set up with the generic transform.

@zth zth closed this as completed Apr 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants