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
becomes
Uncapitalized
becomes
Fragment
becomes
Children
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
:
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.
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.
Multiple spreads are not allowed:
<NotAllowed {...props1} {...props2} />
The spread must be at the first position, followed by other props:
<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:
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).
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:
Make sure you have a ReScript module that implements the functions and types necessary for the JSX transform.
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:
RESCRIPTlet 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.