Skip to content

Commit eab60ee

Browse files
authored
feat: builtin minimal JSX runtime (#737)
1 parent d246913 commit eab60ee

12 files changed

+2171
-2
lines changed

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,27 @@ Under the hood, it handles layout calculation, font, typography and more, to gen
5151
Satori only accepts JSX elements that are pure and stateless. You can use a subset of HTML
5252
elements (see section below), or custom React components, but React APIs such as `useState`, `useEffect`, `dangerouslySetInnerHTML` are not supported.
5353

54+
#### Experimental: builtin JSX support
55+
56+
Satori has an experimental JSX runtime that you can use without having to install React. You can enable it on a per-file basis with [`@jsxImportSource` pragmas](https://www.typescriptlang.org/tsconfig/#jsxImportSource). In the future, it will autocomplete only the subset of HTML elements and CSS properties that Satori supports for better type-safety.
57+
58+
```tsx
59+
/** @jsxRuntime automatic */
60+
/** @jsxImportSource satori/jsx */
61+
62+
import satori from 'satori';
63+
import { FC, JSXNode } from 'satori/jsx';
64+
65+
const MyComponent: FC<{ children: JSXNode }> = ({ children }) => (
66+
<div style={{ color: 'black' }}>{children}</div>
67+
)
68+
69+
const svg = await satori(
70+
<MyComponent>hello, world</MyComponent>,
71+
options,
72+
)
73+
```
74+
5475
#### Use without JSX
5576

5677
If you don't have JSX transpiler enabled, you can simply pass [React-elements-like objects](https://reactjs.org/docs/introducing-jsx.html) that have `type`, `props.children` and `props.style` (and other properties too) directly:
@@ -351,8 +372,6 @@ Multiple fonts can be passed to Satori and used in `fontFamily`.
351372
> [!TIP]
352373
> We recommend you define global fonts instead of creating a new object and pass it to satori for better performance, if your fonts do not change. [Read it for more detail](https://github.com/vercel/satori/issues/590)
353374
354-
355-
356375
#### Emojis
357376

358377
To render custom images for specific graphemes, you can use `graphemeImages` option to map the grapheme to an image source:

package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
"dist/**",
1919
"yoga.wasm"
2020
],
21+
"imports": {
22+
"#satori/jsx/jsx-runtime": "./src/jsx/jsx-runtime.ts",
23+
"#satori/jsx/jsx-dev-runtime": "./src/jsx/jsx-runtime.ts"
24+
},
2125
"exports": {
2226
"./package.json": "./package.json",
2327
"./yoga.wasm": "./yoga.wasm",
@@ -34,6 +38,21 @@
3438
"types": "./dist/standalone.d.cts",
3539
"default": "./dist/standalone.cjs"
3640
}
41+
},
42+
"./jsx": {
43+
"types": "./dist/jsx/index.d.ts",
44+
"import": "./dist/jsx/index.js",
45+
"require": "./dist/jsx/index.cjs"
46+
},
47+
"./jsx/jsx-runtime": {
48+
"types": "./dist/jsx/jsx-runtime.d.ts",
49+
"import": "./dist/jsx/jsx-runtime.js",
50+
"require": "./dist/jsx/jsx-runtime.cjs"
51+
},
52+
"./jsx/jsx-dev-runtime": {
53+
"types": "./dist/jsx/jsx-runtime.d.ts",
54+
"import": "./dist/jsx/jsx-runtime.js",
55+
"require": "./dist/jsx/jsx-runtime.cjs"
3756
}
3857
},
3958
"scripts": {

src/jsx/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { jsx } from './jsx-runtime.js'
2+
import type { JSXNode, JSXElement, JSXKey, FC } from './types.js'
3+
4+
export type * from './types.js'
5+
export { Fragment, type JSX } from './jsx-runtime.js'
6+
7+
/**
8+
* Create a `ReactElement`-like object.
9+
*
10+
* @param type - Tag name string or a function component.
11+
* @param props - Optional props to create the element with.
12+
* @param children - Zero or more child nodes.
13+
* @returns A `ReactElement`-like object with properties like `type`, `key`, `props`, and `props.children`.
14+
*/
15+
export function createElement<P extends {}>(
16+
type: string | FC<P>,
17+
props?: P | null,
18+
...children: JSXNode[]
19+
): JSXElement<P> {
20+
if (!props) {
21+
const newProps = children.length ? { children } : {}
22+
return jsx(type, newProps, null) as JSXElement<P>
23+
}
24+
25+
// Destructure key from props.
26+
const { key, ...restProps } = props as {
27+
key?: JSXKey | undefined | null
28+
[x: string]: unknown
29+
}
30+
// Pass children as props.
31+
if (children.length) restProps.children = children
32+
33+
return jsx(type, restProps, key) as JSXElement<P>
34+
}

0 commit comments

Comments
 (0)