Skip to content

[RFC] Generic JSX transform #6408

Closed
Closed
@zth

Description

@zth

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions