Skip to content

Generic JSX transform #806

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

Merged
merged 4 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions misc_docs/syntax/decorator_jsx_component.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
id: "jsx-component-decorator"
keywords: ["jsx", "component", "decorator"]
name: "@jsx.component"
summary: "This is the `@jsx.component` decorator."
category: "decorators"
---

**Since 11.1**

The `@jsx.component` decorator is used to annotate functions that are [JSX](/docs/react/latest/elements-and-jsx) components. This is a _generic_ version that's equivalent to the existing `@react.component` for React.

You will need this decorator whenever you want to use JSX component in JSX expressions, with a [generic JSX transform](/docs/manual/latest/jsx#generic-jsx-transform-jsx-beyond-react-experimental) configured.


### Example

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// Assuming `Preact` is set up and configured as a generic JSX transform.
@jsx.component
let make = (~name) => {
<button> {Preact.string("Hello " ++ name ++ "!")} </button>
}
```

```js
import * as Preact from "preact";

function Playground(props) {
return Preact.jsx("button", {
children: "Hello " + props.name + "!"
});
}

var make = Playground;
```

</CodeTab>

### References

* [Generic JSX transform](/docs/manual/latest/jsx#generic-jsx-transform-jsx-beyond-react-experimental)
127 changes: 123 additions & 4 deletions pages/docs/manual/latest/jsx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,129 @@ Consequently, a JSX component can cram in a few more props before reaching for e

**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.

## Tip & Tricks
## Generic JSX transform: JSX beyond React (experimental)

For library authors wanting to take advantage of the JSX: the `@JSX` attribute is a hook for potential ppx macros to spot a function wanting to format as JSX. Once you spot the function, you can turn it into any other expression.
**Since 11.1**

This way, everyone gets to benefit the JSX syntax without needing to opt into a specific library using it, e.g. ReScriptReact.
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_.

JSX calls supports the features of [labeled arguments](function.md#labeled-arguments): optional, explicitly passed optional and optional with default.
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](#implementing-a-generic-jsx-transform-module).
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.