JSX

Would you like some HTML syntax in your ReScript? If not, quickly skip over this section and pretend you didn't see anything!

ReScript supports the JSX syntax, with some slight differences compared to the one in ReactJS. ReScript JSX isn't tied to ReactJS; they translate to normal function calls:

Note for ReScriptReact readers: this isn't what ReScriptReact turns JSX into, in the end. See Usage section for more info.

Capitalized

ReScriptJS Output
<MyComponent name={"ReScript"} />

becomes

ReScriptJS Output
MyComponent.createElement(~name="ReScript", ~children=list{}, ())

Uncapitalized

ReScriptJS Output
<div onClick={handler}> child1 child2 </div>

becomes

ReScriptJS Output
div(~onClick=handler, ~children=list{child1, child2}, ())

Fragment

ReScriptJS Output
<> child1 child2 </>

becomes

ReScriptJS Output
list{child1, child2}

Children

ReScriptJS Output
<MyComponent> child1 child2 </MyComponent>

This is the syntax for passing a list of two items, child1 and child2, to the children position. It transforms to a list containing child1 and child2:

ReScriptJS Output
MyComponent.createElement(~children=list{child1, child2}, ())

Note again that this isn't the transform for ReScriptReact; ReScriptReact turns the final list into an array. But the idea still applies.

So naturally, <MyComponent> myChild </MyComponent> is transformed to MyComponent.createElement(~children=list{myChild}, ()). I.e. whatever you do, the arguments passed to the children position will be wrapped in a list.

Usage

See ReScriptReact Elements & JSX for an example application of JSX, which transforms the above calls into a ReScriptReact-specific call.

Here's a JSX tag that shows most of the features.

ReScriptJS Output
<MyComponent
  booleanAttribute={true}
  stringAttribute="string"
  intAttribute=1
  forcedOptional=?{Some("hello")}
  onClick={handleClick}>
  <div> {React.string("hello")} </div>
</MyComponent>

Departures From JS JSX

  • Attributes and children don't mandate {}, but we show them anyway for ease of learning. Once you format your file, some of them go away and some turn into parentheses.

  • Props spread is supported, but there are some restrictions (see below).

  • Punning!

  • Props and tag names have to follow ReScript's restrictions on identifiers at the exception of hyphens for lowercase tags (see below).

Spread Props

Since 10.1

JSX props spread is supported now, but in a stricter way than in JS.

ReScriptJS Output
<Comp {...props} a="a" />

Multiple spreads are not allowed:

RES
<NotAllowed {...props1} {...props2} />

The spread must be at the first position, followed by other props:

RES
<NotAllowed a="a" {...props} />

Punning

"Punning" refers to the syntax shorthand for when a label and a value are the same. For example, in JavaScript, instead of doing return {name: name}, you can do return {name}.

JSX supports punning. <input checked /> is just a shorthand for <input checked=checked />. The formatter will help you format to the punned syntax whenever possible. This is convenient in the cases where there are lots of props to pass down:

ReScriptJS Output
<MyComponent isLoading text onClick />

Consequently, a JSX component can cram in a few more props before reaching for extra libraries solutions that avoids props passing.

Note that this is a departure from ReactJS JSX, which does not have punning. ReactJS' <input checked /> desugars to <input checked=true />, in order to conform to DOM's idioms and for backward compatibility.

Hyphens in tag names

Since 11.1

JSX now supports lowercase tags with hyphens in their name. This allows to bind to web components.

Note though that props names can't have hyphens, you should use @as to bind to such props in your custom JsxDOM.domProps type (see generic JSX transform).

ReScriptJS Output
<model-viewer src touchActions="pan-y"></model-viewer>

Generic JSX transform: JSX beyond React (experimental)

Since 11.1

While ReScript comes with first class support for JSX in React, it's also possible to have ReScript delegate JSX to other frameworks. You do that by configuring a generic JSX transform.

This is what you need to do to use a generic JSX transform:

  1. Make sure you have a ReScript module that implements the functions and types necessary for the JSX transform.

  2. Configure rescript.json to delegated JSX to that module.

That's it really. We'll expand on each point below.

Configuration

You configure a generic JSX transform by putting any module name in the module config of JSX in rescript.json. This can be any valid module name. Example part from rescript.json:

JSON
"jsx": { "module": "Preact" },

This will now put the Preact module in control of the generated JSX calls. The Preact module can be defined by anyone - locally in your project, or by a package. As long a it's available in the global scope. The JSX transform will delegate any JSX related code to Preact.

What about @react.component for components?

@react.component will still be available, and so is a generic @jsx.component notation. Both work the same way.

Usage Example

Here's a quick usage example (the actual definition of Preact.res comes below):

First, configure rescript.json:

JSON
"jsx": { "module": "Preact" },

Now you can build Preact components:

RESCRIPT
// Name.res @jsx.component // or @react.component if you want let make = (~name) => Preact.string(`Hello ${name}!`)

And you can use them just like normal with JSX:

RESCRIPT
let name = <Name name="Test" />

File level configuration

You can configure what JSX transform is used at the file level via @@jsxConfig, just like before. Like:

RESCRIPT
@@jsxConfig({module_: "Preact"})

This can be convenient if you're mixing different JSX frameworks in the same project.

Implementing a generic JSX transform module

Below is a full list of everything you need in a generic JSX transform module, including code comments to clarify. It's an example implementation of a Preact transform, so when doing this for other frameworks you'd of course adapt what you import from, and so on.

You can easily copy-paste-and-adapt this to your needs if you're creating bindings to a JSX framework. Most often, all you'll need to change is what the @module("") external points to, so the runtime calls point to the correct JS module.

RESCRIPT
// Preact.res /* Below is a number of aliases to the common `Jsx` module */ type element = Jsx.element type component<'props> = Jsx.component<'props> type componentLike<'props, 'return> = Jsx.componentLike<'props, 'return> @module("preact") external jsx: (component<'props>, 'props) => element = "jsx" @module("preact") external jsxKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx" @module("preact") external jsxs: (component<'props>, 'props) => element = "jsxs" @module("preact") external jsxsKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsxs" /* These identity functions and static values below are optional, but lets you move things easily to the `element` type. The only required thing to define though is `array`, which the JSX transform will output. */ external array: array<element> => element = "%identity" @val external null: element = "null" external float: float => element = "%identity" external int: int => element = "%identity" external string: string => element = "%identity" /* These are needed for Fragment (<> </>) support */ type fragmentProps = {children?: element} @module("preact") external jsxFragment: component<fragmentProps> = "Fragment" /* The Elements module is the equivalent to the ReactDOM module in React. This holds things relevant to _lowercase_ JSX elements. */ module Elements = { /* Here you can control what props lowercase JSX elements should have. A base that the React JSX transform uses is provided via JsxDOM.domProps, but you can make this anything. The editor tooling will support autocompletion etc for your specific type. */ type props = JsxDOM.domProps @module("preact") external jsx: (string, props) => Jsx.element = "jsx" @module("preact") external div: (string, props) => Jsx.element = "jsx" @module("preact") external jsxKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsx" @module("preact") external jsxs: (string, props) => Jsx.element = "jsxs" @module("preact") external jsxsKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsxs" external someElement: element => option<element> = "%identity" }

As you can see, most of the things you'll want to implement will be copy paste from the above. But do note that everything needs to be there unless explicitly noted or the transform will fail at compile time.