Skip to content

Commit 8db921f

Browse files
authored
Generic JSX transform (#806)
* docs for the generic JSX transform * put since 11.1 in a better place * syntax lookup: jsx.component * remove broken link
1 parent c952d21 commit 8db921f

File tree

2 files changed

+167
-4
lines changed

2 files changed

+167
-4
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
id: "jsx-component-decorator"
3+
keywords: ["jsx", "component", "decorator"]
4+
name: "@jsx.component"
5+
summary: "This is the `@jsx.component` decorator."
6+
category: "decorators"
7+
---
8+
9+
**Since 11.1**
10+
11+
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.
12+
13+
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.
14+
15+
16+
### Example
17+
18+
<CodeTab labels={["ReScript", "JS Output"]}>
19+
20+
```res
21+
// Assuming `Preact` is set up and configured as a generic JSX transform.
22+
@jsx.component
23+
let make = (~name) => {
24+
<button> {Preact.string("Hello " ++ name ++ "!")} </button>
25+
}
26+
```
27+
28+
```js
29+
import * as Preact from "preact";
30+
31+
function Playground(props) {
32+
return Preact.jsx("button", {
33+
children: "Hello " + props.name + "!"
34+
});
35+
}
36+
37+
var make = Playground;
38+
```
39+
40+
</CodeTab>
41+
42+
### References
43+
44+
* [Generic JSX transform](/docs/manual/latest/jsx#generic-jsx-transform-jsx-beyond-react-experimental)

pages/docs/manual/latest/jsx.mdx

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,129 @@ Consequently, a JSX component can cram in a few more props before reaching for e
230230

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

233-
## Tip & Tricks
233+
## Generic JSX transform: JSX beyond React (experimental)
234234

235-
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.
235+
**Since 11.1**
236236

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

239-
JSX calls supports the features of [labeled arguments](function.md#labeled-arguments): optional, explicitly passed optional and optional with default.
239+
This is what you need to do to use a generic JSX transform:
240+
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).
241+
2. Configure `rescript.json` to delegated JSX to that module.
242+
243+
That's it really. We'll expand on each point below.
244+
245+
### Configuration
246+
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`:
247+
248+
```json
249+
"jsx": {
250+
"module": "Preact"
251+
},
252+
```
253+
254+
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`.
255+
256+
#### What about `@react.component` for components?
257+
258+
`@react.component` will still be available, and so is a generic `@jsx.component` notation. Both work the same way.
259+
260+
### Usage Example
261+
Here's a quick usage example (the actual definition of `Preact.res` comes below):
262+
263+
First, configure `rescript.json`:
264+
```json
265+
"jsx": {
266+
"module": "Preact"
267+
},
268+
```
269+
270+
Now you can build Preact components:
271+
```rescript
272+
// Name.res
273+
@jsx.component // or @react.component if you want
274+
let make = (~name) => Preact.string(`Hello ${name}!`)
275+
```
276+
277+
And you can use them just like normal with JSX:
278+
```rescript
279+
let name = <Name name="Test" />
280+
```
281+
282+
#### File level configuration
283+
You can configure what JSX transform is used at the file level via `@@jsxConfig`, just like before. Like:
284+
```rescript
285+
@@jsxConfig({module_: "Preact"})
286+
```
287+
288+
This can be convenient if you're mixing different JSX frameworks in the same project.
289+
290+
### Implementing a generic JSX transform module
291+
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.
292+
293+
> 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.
294+
295+
```rescript
296+
// Preact.res
297+
/* Below is a number of aliases to the common `Jsx` module */
298+
type element = Jsx.element
299+
300+
type component<'props> = Jsx.component<'props>
301+
302+
type componentLike<'props, 'return> = Jsx.componentLike<'props, 'return>
303+
304+
@module("preact")
305+
external jsx: (component<'props>, 'props) => element = "jsx"
306+
307+
@module("preact")
308+
external jsxKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx"
309+
310+
@module("preact")
311+
external jsxs: (component<'props>, 'props) => element = "jsxs"
312+
313+
@module("preact")
314+
external jsxsKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsxs"
315+
316+
/* These identity functions and static values below are optional, but lets
317+
you move things easily to the `element` type. The only required thing to
318+
define though is `array`, which the JSX transform will output. */
319+
external array: array<element> => element = "%identity"
320+
@val external null: element = "null"
321+
322+
external float: float => element = "%identity"
323+
external int: int => element = "%identity"
324+
external string: string => element = "%identity"
325+
326+
/* These are needed for Fragment (<> </>) support */
327+
type fragmentProps = {children?: element}
328+
329+
@module("preact") external jsxFragment: component<fragmentProps> = "Fragment"
330+
331+
/* The Elements module is the equivalent to the ReactDOM module in React. This holds things relevant to _lowercase_ JSX elements. */
332+
module Elements = {
333+
/* Here you can control what props lowercase JSX elements should have.
334+
A base that the React JSX transform uses is provided via JsxDOM.domProps,
335+
but you can make this anything. The editor tooling will support
336+
autocompletion etc for your specific type. */
337+
type props = JsxDOM.domProps
338+
339+
@module("preact")
340+
external jsx: (string, props) => Jsx.element = "jsx"
341+
342+
@module("preact")
343+
external div: (string, props) => Jsx.element = "jsx"
344+
345+
@module("preact")
346+
external jsxKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsx"
347+
348+
@module("preact")
349+
external jsxs: (string, props) => Jsx.element = "jsxs"
350+
351+
@module("preact")
352+
external jsxsKeyed: (string, props, ~key: string=?, @ignore unit) => Jsx.element = "jsxs"
353+
354+
external someElement: element => option<element> = "%identity"
355+
}
356+
```
357+
358+
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.

0 commit comments

Comments
 (0)