@theseus.run/jsx-md 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -204
- package/dist/index-8tdwjkh9.js +11 -0
- package/dist/index-8tdwjkh9.js.map +10 -0
- package/dist/index.js +435 -0
- package/dist/index.js.map +13 -0
- package/dist/jsx-dev-runtime.js +13 -0
- package/dist/jsx-dev-runtime.js.map +9 -0
- package/dist/jsx-runtime.js +13 -0
- package/dist/jsx-runtime.js.map +9 -0
- package/package.json +25 -9
- package/src/context.ts +47 -4
- package/src/escape.ts +69 -11
- package/src/index.ts +5 -1
- package/src/jsx-runtime.ts +41 -26
- package/src/primitives.tsx +140 -74
- package/src/render.ts +54 -19
- package/src/_render-registry.ts +0 -16
package/README.md
CHANGED
|
@@ -1,81 +1,14 @@
|
|
|
1
1
|
# `@theseus.run/jsx-md`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JSX runtime that outputs Markdown strings — typed props, Context API, XML intrinsics, zero runtime dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
→ [Why this exists](https://romanonthego.dev/blog/jsx-that-outputs-markdown)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
const instructions = `
|
|
9
|
-
You are a code reviewer.
|
|
7
|
+
## Prior art
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
[dbartholomae/jsx-md](https://github.com/dbartholomae/jsx-md) (2019, inactive) and [eyelly-wu/jsx-to-md](https://github.com/eyelly-wu/jsx-to-md) are built for documentation generation — READMEs, changelogs. They work well for that. `dbartholomae/jsx-md` predates `jsxImportSource` and uses file-level pragma comments; its `render()` returns a Promise.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
${firstPerson
|
|
15
|
-
? '- I always verify output before claiming done.'
|
|
16
|
-
: '- The agent must verify output before claiming done.'}
|
|
17
|
-
`
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
No syntax highlighting for the Markdown inside the string. No types on the structure. A variant is a new file. Refactoring is grep-and-pray.
|
|
21
|
-
|
|
22
|
-
Nested lists make it worse. Markdown requires exact indentation — two spaces per level. Template strings force you to hardcode that spacing:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
const instructions = `
|
|
26
|
-
## Rules
|
|
27
|
-
|
|
28
|
-
- Outer rule
|
|
29
|
-
- Nested rule: two hardcoded spaces
|
|
30
|
-
- Deeper: four hardcoded spaces
|
|
31
|
-
${condition ? ' - Conditional nested item' : ''}
|
|
32
|
-
`
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
A false conditional leaves a blank bullet. The indentation is load-bearing and invisible. `DepthContext` in `@theseus.run/jsx-md` tracks nesting depth automatically — you write `<Ul>` inside `<Li>` and the renderer handles the spaces.
|
|
36
|
-
|
|
37
|
-
This isn't an edge case. [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent) — 39.8k stars, serious harness work — stores instructions the same way:
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
export const PROMETHEUS_HIGH_ACCURACY_MODE = `# PHASE 3: PLAN GENERATION
|
|
41
|
-
## High Accuracy Mode - MANDATORY LOOP
|
|
42
|
-
\`\`\`typescript
|
|
43
|
-
while (true) {
|
|
44
|
-
const result = task(subagent_type="momus", ...)
|
|
45
|
-
if (result.verdict === "OKAY") break
|
|
46
|
-
}
|
|
47
|
-
\`\`\`
|
|
48
|
-
...` // 62 more lines of escaped template string
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The escaped backticks are the tell — not a bad solution, it's the only solution the format offers. No composability, no reuse, no tooling support.
|
|
52
|
-
|
|
53
|
-
The web dev world solved "structured text with conditionals and composable fragments" ten years ago. JSX is a transform spec, not a React dependency — `jsxImportSource` lets you point it at any runtime. `@theseus.run/jsx-md` is a runtime that outputs Markdown:
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
const ReviewerInstructions = ({ harness, firstPerson }: Props) => (
|
|
57
|
-
<>
|
|
58
|
-
<P>You are a code reviewer.</P>
|
|
59
|
-
{harness === 'opencode' && (
|
|
60
|
-
<HtmlComment>use task() for subtasks</HtmlComment>
|
|
61
|
-
)}
|
|
62
|
-
<H2>Traits</H2>
|
|
63
|
-
<Ul>
|
|
64
|
-
<Li>
|
|
65
|
-
{firstPerson
|
|
66
|
-
? 'I always verify output before claiming done.'
|
|
67
|
-
: 'The agent must verify output before claiming done.'}
|
|
68
|
-
</Li>
|
|
69
|
-
</Ul>
|
|
70
|
-
</>
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
render(<ReviewerInstructions harness="opencode" firstPerson={true} />)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
No escaped backticks. Syntax highlighting in your editor. Conditionals are JSX expressions. Variants are props. Shared fragments are components.
|
|
77
|
-
|
|
78
|
-
`render()` returns a plain string — no virtual DOM, no hydration, no React runtime anywhere in the chain.
|
|
11
|
+
`@theseus.run/jsx-md` targets agent instructions assembled at call time: `render()` is synchronous, Context API ships with it, and any lowercase tag is an XML intrinsic.
|
|
79
12
|
|
|
80
13
|
---
|
|
81
14
|
|
|
@@ -83,9 +16,11 @@ No escaped backticks. Syntax highlighting in your editor. Conditionals are JSX e
|
|
|
83
16
|
|
|
84
17
|
```bash
|
|
85
18
|
bun add @theseus.run/jsx-md
|
|
19
|
+
# npm install @theseus.run/jsx-md
|
|
20
|
+
# pnpm add @theseus.run/jsx-md
|
|
86
21
|
```
|
|
87
22
|
|
|
88
|
-
|
|
23
|
+
`tsconfig.json`:
|
|
89
24
|
|
|
90
25
|
```json
|
|
91
26
|
{
|
|
@@ -96,84 +31,71 @@ Then in `tsconfig.json`:
|
|
|
96
31
|
}
|
|
97
32
|
```
|
|
98
33
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
## What you're getting
|
|
102
|
-
|
|
103
|
-
**Zero runtime dependencies.** The package ships TypeScript source with no third-party imports. Nothing gets added to your bundle.
|
|
104
|
-
|
|
105
|
-
**Bun-first.** Ships TypeScript source directly, no compiled output. Works transparently in Bun: install, add two tsconfig lines, write TSX. The trade-off is real: vanilla Node.js without a bundler won't run `.ts` files from `node_modules`. If your stack is Vite, tsup, or esbuild, those handle it. If you're running bare Node, v0.1.0 doesn't have a solution for you yet.
|
|
106
|
-
|
|
107
|
-
**JSX without React.** When you write `<H2>Title</H2>`, the transform calls `jsx(H2, { children: "Title" })`. `H2` is a plain function — takes props, returns a string. No virtual DOM, no reconciler, no fiber, no hooks. `render()` walks the VNode tree synchronously and concatenates. The output is deterministic: same input, same string, every time. You can test it with `expect(render(<MyPrompt />)).toMatchSnapshot()`.
|
|
108
|
-
|
|
109
|
-
**TypeScript-first.** All components and their props are typed. `render()` accepts `VNode`, returns `string`. Wrong usage is a compile error, not a runtime surprise.
|
|
110
|
-
|
|
111
|
-
**String children are verbatim.** `render()` passes raw string values through without escaping. `<P>{"<b>text</b>"}</P>` outputs `<b>text</b>` — no transformation. For LLM prompts this is the right default; Markdown renderers handle the rest.
|
|
112
|
-
|
|
113
|
-
**XML-structured prompts, zero config.** Any lowercase JSX tag renders as an XML block — `<context>`, `<instructions>`, `<example index={1}>`. Anthropic's prompt engineering guide recommends this structure for Claude agents. Attributes are typed and serialized automatically; empty tags self-close. No imports, no registration — it's built into the JSX intrinsics catch-all.
|
|
34
|
+
Ships TypeScript source and compiled ESM output with sourcemaps. Bun resolves the TypeScript source directly. Node.js (≥18) and bundlers (Vite, tsup, esbuild) use the compiled output — no configuration needed, the `exports` map handles it transparently.
|
|
114
35
|
|
|
115
36
|
---
|
|
116
37
|
|
|
117
38
|
## Usage
|
|
118
39
|
|
|
119
40
|
```tsx
|
|
120
|
-
// system-prompt.tsx
|
|
121
41
|
import { render, H2, P, Ul, Li, Bold, Code } from "@theseus.run/jsx-md";
|
|
122
42
|
|
|
123
|
-
const
|
|
43
|
+
const ReviewerPrompt = ({ repo }: { repo: string }) => (
|
|
124
44
|
<>
|
|
125
45
|
<H2>Role</H2>
|
|
126
|
-
<P>
|
|
127
|
-
You are a precise code reviewer. Your job is to find bugs, not suggest
|
|
128
|
-
style changes.
|
|
129
|
-
</P>
|
|
46
|
+
<P>You are a precise code reviewer. Find bugs, not style issues.</P>
|
|
130
47
|
|
|
131
48
|
<H2>Rules</H2>
|
|
132
49
|
<Ul>
|
|
133
|
-
<Li>
|
|
134
|
-
Flag <Bold>P0</Bold> issues immediately — do not bury them.
|
|
135
|
-
</Li>
|
|
136
|
-
<Li>
|
|
137
|
-
Use <Code>inline code</Code> when referencing identifiers.
|
|
138
|
-
</Li>
|
|
50
|
+
<Li>Flag <Bold>P0</Bold> issues first — do not bury them.</Li>
|
|
139
51
|
<Li>One finding per comment. No compound observations.</Li>
|
|
52
|
+
<Li>Use <Code>inline code</Code> when referencing identifiers.</Li>
|
|
140
53
|
</Ul>
|
|
141
|
-
|
|
142
|
-
<H2>Output format</H2>
|
|
143
|
-
<P>
|
|
144
|
-
Respond with a structured list. Each item: severity, location, finding.
|
|
145
|
-
</P>
|
|
146
54
|
</>
|
|
147
55
|
);
|
|
148
56
|
|
|
149
|
-
|
|
57
|
+
const prompt = render(<ReviewerPrompt repo="cockpit" />);
|
|
58
|
+
// "## Role\n\nYou are a precise code reviewer..."
|
|
150
59
|
```
|
|
151
60
|
|
|
152
|
-
|
|
61
|
+
`render()` returns a plain string. No virtual DOM, no React runtime. Same input, same string, every time.
|
|
62
|
+
|
|
63
|
+
---
|
|
153
64
|
|
|
154
|
-
|
|
155
|
-
## Role
|
|
65
|
+
## Context API
|
|
156
66
|
|
|
157
|
-
|
|
67
|
+
Avoids prop-drilling through shared fragment trees. Same shape as React — `createContext`, `useContext`, `Context.Provider` — synchronous, no rules-of-hooks.
|
|
158
68
|
|
|
159
|
-
|
|
69
|
+
```tsx
|
|
70
|
+
import { render, createContext, useContext, Ul, Li, Code } from "@theseus.run/jsx-md";
|
|
160
71
|
|
|
161
|
-
|
|
162
|
-
- Use `inline code` when referencing identifiers.
|
|
163
|
-
- One finding per comment. No compound observations.
|
|
72
|
+
const HarnessCtx = createContext<'opencode' | 'copilot'>('copilot');
|
|
164
73
|
|
|
165
|
-
|
|
74
|
+
const StepsSection = () => {
|
|
75
|
+
const harness = useContext(HarnessCtx);
|
|
76
|
+
return (
|
|
77
|
+
<Ul>
|
|
78
|
+
<Li>Always verify output before claiming done.</Li>
|
|
79
|
+
{harness === 'opencode' && <Li>Use <Code>task()</Code> for multi-step subtasks.</Li>}
|
|
80
|
+
</Ul>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
166
83
|
|
|
167
|
-
|
|
84
|
+
// Wire it once at the root — no prop threading:
|
|
85
|
+
const prompt = render(
|
|
86
|
+
<HarnessCtx.Provider value="opencode">
|
|
87
|
+
<StepsSection />
|
|
88
|
+
</HarnessCtx.Provider>
|
|
89
|
+
);
|
|
168
90
|
```
|
|
169
91
|
|
|
170
|
-
|
|
92
|
+
`useContext` returns the default when called outside a Provider. Providers nest — innermost wins.
|
|
171
93
|
|
|
172
|
-
|
|
94
|
+
---
|
|
173
95
|
|
|
174
|
-
|
|
96
|
+
## XML intrinsics
|
|
175
97
|
|
|
176
|
-
Any lowercase JSX tag
|
|
98
|
+
Any lowercase JSX tag renders as an XML block. No imports, no registration:
|
|
177
99
|
|
|
178
100
|
```tsx
|
|
179
101
|
const ReviewerPrompt = ({ repo, examples }: Props) => (
|
|
@@ -185,13 +107,6 @@ const ReviewerPrompt = ({ repo, examples }: Props) => (
|
|
|
185
107
|
<instructions>
|
|
186
108
|
<H2>Role</H2>
|
|
187
109
|
<P>You are a precise code reviewer. Find bugs, not style issues.</P>
|
|
188
|
-
|
|
189
|
-
<H2>Rules</H2>
|
|
190
|
-
<Ul>
|
|
191
|
-
<Li>Flag <Bold>P0</Bold> issues first — do not bury them.</Li>
|
|
192
|
-
<Li>One finding per comment. No compound observations.</Li>
|
|
193
|
-
<Li>Use <Code>inline code</Code> when referencing identifiers.</Li>
|
|
194
|
-
</Ul>
|
|
195
110
|
</instructions>
|
|
196
111
|
|
|
197
112
|
{examples.length > 0 && (
|
|
@@ -204,120 +119,73 @@ const ReviewerPrompt = ({ repo, examples }: Props) => (
|
|
|
204
119
|
</examples>
|
|
205
120
|
)}
|
|
206
121
|
</>
|
|
207
|
-
)
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Output:
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
<context>
|
|
214
|
-
Repository: cockpit. Language: TypeScript. Package manager: bun.
|
|
215
|
-
</context>
|
|
216
|
-
|
|
217
|
-
<instructions>
|
|
218
|
-
## Role
|
|
219
|
-
|
|
220
|
-
You are a precise code reviewer. Find bugs, not style issues.
|
|
221
|
-
|
|
222
|
-
## Rules
|
|
223
|
-
|
|
224
|
-
- Flag **P0** issues first — do not bury them.
|
|
225
|
-
- One finding per comment. No compound observations.
|
|
226
|
-
- Use `inline code` when referencing identifiers.
|
|
227
|
-
</instructions>
|
|
228
|
-
|
|
229
|
-
<examples>
|
|
230
|
-
<example index="1">
|
|
231
|
-
... your example content ...
|
|
232
|
-
</example>
|
|
233
|
-
</examples>
|
|
122
|
+
);
|
|
234
123
|
```
|
|
235
124
|
|
|
236
|
-
Attributes are typed
|
|
125
|
+
Attributes are typed — `index={1}` serializes to `index="1"`. Boolean `true` renders bare, `false`/`null`/`undefined` are omitted. Empty tags self-close.
|
|
237
126
|
|
|
238
127
|
---
|
|
239
128
|
|
|
240
129
|
## Primitives
|
|
241
130
|
|
|
242
|
-
| Component |
|
|
243
|
-
|
|
131
|
+
| Component | Output |
|
|
132
|
+
|---|---|
|
|
244
133
|
| `H1`–`H6` | `#`–`######` headings |
|
|
245
134
|
| `P` | Paragraph (blank line separated) |
|
|
246
135
|
| `Hr` | `---` horizontal rule |
|
|
247
|
-
| `Codeblock` | Fenced code block
|
|
136
|
+
| `Codeblock` | Fenced code block (`lang` prop optional) |
|
|
248
137
|
| `Blockquote` | `>` blockquote |
|
|
249
138
|
| `Ul` | Unordered list |
|
|
250
|
-
| `Ol` | Ordered list |
|
|
251
|
-
| `Li` | List item (supports
|
|
139
|
+
| `Ol` | Ordered list (auto-numbered, nesting supported) |
|
|
140
|
+
| `Li` | List item (supports nested `Ul` or `Ol` inside `Li`) |
|
|
252
141
|
| `TaskList` | Task list container |
|
|
253
142
|
| `Task` | `- [ ]` / `- [x]` task item (`done` prop) |
|
|
254
143
|
| `Table` | Markdown table |
|
|
255
144
|
| `Tr` | Table row |
|
|
256
|
-
| `Th` | Table header cell |
|
|
145
|
+
| `Th` | Table header cell (`align`: `left`, `center`, `right`) |
|
|
257
146
|
| `Td` | Table data cell |
|
|
258
147
|
| `Bold` | `**bold**` |
|
|
259
148
|
| `Code` | `` `inline code` `` |
|
|
260
149
|
| `Italic` | `*italic*` |
|
|
261
150
|
| `Strikethrough` | `~~strikethrough~~` |
|
|
151
|
+
| `Br` | Hard line break (` \n` — two trailing spaces) |
|
|
152
|
+
| `Sup` | `<sup>content</sup>` superscript |
|
|
153
|
+
| `Sub` | `<sub>content</sub>` subscript |
|
|
154
|
+
| `Kbd` | `<kbd>content</kbd>` keyboard key |
|
|
155
|
+
| `Escape` | Escapes CommonMark metacharacters in children |
|
|
262
156
|
| `Link` | `[text](url)` |
|
|
263
157
|
| `Img` | `` |
|
|
264
|
-
| `Md` | Raw
|
|
265
|
-
| `HtmlComment` | `<!-- comment -->`
|
|
158
|
+
| `Md` | Raw Markdown passthrough — renders verbatim, no transformation |
|
|
159
|
+
| `HtmlComment` | `<!-- comment -->` — invisible to most renderers, useful for LLM-only instructions |
|
|
266
160
|
| `Details` | `<details><summary>` collapsible block |
|
|
267
|
-
| `Callout` | GitHub-style admonition
|
|
161
|
+
| `Callout` | GitHub-style admonition (`type`: `note`, `tip`, `important`, `warning`, `caution`) |
|
|
268
162
|
|
|
269
163
|
---
|
|
270
164
|
|
|
271
|
-
##
|
|
165
|
+
## Utilities
|
|
272
166
|
|
|
273
|
-
|
|
167
|
+
### `escapeMarkdown(s: string): string`
|
|
274
168
|
|
|
275
|
-
|
|
169
|
+
Escapes all CommonMark ASCII punctuation metacharacters with a backslash so user-supplied strings are treated as literal text by any markdown renderer.
|
|
276
170
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Should fix prop-drilling. A prompt tree with conditional sections per harness ends up threading `harness` through every component:
|
|
280
|
-
|
|
281
|
-
```tsx
|
|
282
|
-
// v0.1.0 — prop-drilling
|
|
283
|
-
const Prompt = ({ harness }: Props) => (
|
|
284
|
-
<>
|
|
285
|
-
<Instructions harness={harness} />
|
|
286
|
-
<Rules harness={harness} />
|
|
287
|
-
<Examples harness={harness} />
|
|
288
|
-
</>
|
|
289
|
-
)
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
With the v0.2.0 context API, set it once at the root and read it anywhere:
|
|
171
|
+
```tsx
|
|
172
|
+
import { escapeMarkdown, Escape, P } from "@theseus.run/jsx-md";
|
|
293
173
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const HarnessContext = createContext<'opencode' | 'copilot'>('copilot')
|
|
174
|
+
// Function form
|
|
175
|
+
const safe = escapeMarkdown(untrustedInput); // "**bold**" → "\\*\\*bold\\*\\*"
|
|
297
176
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
</HarnessContext.Provider>
|
|
302
|
-
)
|
|
177
|
+
// Component form — same result, composable in JSX
|
|
178
|
+
render(<P>File: <Escape>{untrustedFilename}</Escape></P>);
|
|
179
|
+
```
|
|
303
180
|
|
|
304
|
-
|
|
305
|
-
const Instructions = () => {
|
|
306
|
-
const harness = useContext(HarnessContext)
|
|
307
|
-
return harness === 'opencode'
|
|
308
|
-
? <P>Use task() for subtasks.</P>
|
|
309
|
-
: null
|
|
310
|
-
}
|
|
311
|
-
```
|
|
181
|
+
Escaped characters: `` \ ` * _ [ ] ( ) # + - . ! | ~ < > ``
|
|
312
182
|
|
|
313
|
-
|
|
183
|
+
`&` is intentionally not escaped — use `escapeHtmlContent` for HTML contexts.
|
|
314
184
|
|
|
315
185
|
---
|
|
316
186
|
|
|
317
187
|
## License
|
|
318
188
|
|
|
319
|
-
MIT — see [LICENSE](../../LICENSE).
|
|
320
|
-
|
|
321
|
-
Built by [Roman Dubinin](https://romanonthego.com).
|
|
189
|
+
MIT — see [LICENSE](../../LICENSE).
|
|
322
190
|
|
|
323
|
-
Part of [Theseus](https://theseus.run).
|
|
191
|
+
Built by [Roman Dubinin](https://romanonthego.dev). Part of [Theseus](https://theseus.run).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/jsx-runtime.ts
|
|
2
|
+
var Fragment = Symbol("Fragment");
|
|
3
|
+
function jsx(type, props) {
|
|
4
|
+
return { type, props };
|
|
5
|
+
}
|
|
6
|
+
var jsxs = jsx;
|
|
7
|
+
|
|
8
|
+
export { Fragment, jsx, jsxs };
|
|
9
|
+
|
|
10
|
+
//# debugId=A10E4D57EB3603AA64756E2164756E21
|
|
11
|
+
//# sourceMappingURL=index-8tdwjkh9.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/jsx-runtime.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Custom JSX-to-VNode runtime for agent markdown generation.\n *\n * Bun's JSX compilation with `jsxImportSource: \"@theseus.run/jsx-md\"` resolves\n * `jsx` and `jsxs` from `@theseus.run/jsx-md/jsx-runtime`. The factory builds\n * a VNode tree — no evaluation at construction time.\n *\n * Call `render(node)` from `./render.ts` to produce the final markdown string.\n * All markdown primitives live in primitives.tsx as named components.\n *\n * Fragment is a Symbol. render.ts imports this Symbol and handles it explicitly,\n * keeping the dependency one-way: render.ts → jsx-runtime.ts (no cycle).\n */\n\n// ---------------------------------------------------------------------------\n// VNode types\n// ---------------------------------------------------------------------------\n\n/**\n * The concrete runtime shape of a JSX element — what the `jsx()` factory always produces.\n *\n * Useful for structural inspection of a VNode tree (e.g. testing whether a node is an\n * element rather than a string, null, or array). The `isVNodeElement` predicate in\n * render.ts narrows to this type.\n *\n * @remarks **Do not use as a component return-type annotation.** TypeScript infers\n * `JSX.Element` (= `VNode`) as the return type of JSX expressions, not `VNodeElement`.\n * Annotating a component as `(): VNodeElement` causes TS2322 because `VNode` (the\n * inferred type) is not assignable to the narrower `VNodeElement`. Use `VNode` instead:\n * ```ts\n * // Wrong — TS2322\n * function MyComp(): VNodeElement { return <P>hi</P>; }\n * // Correct\n * function MyComp(): VNode { return <P>hi</P>; }\n * ```\n */\nexport type VNodeElement = {\n readonly type: Component | string | typeof Fragment;\n readonly props: Record<string, unknown>;\n};\n\nexport type VNode =\n | null\n | undefined\n | boolean\n | string\n | number\n | VNodeElement\n | ReadonlyArray<VNode>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Component<P = any> = (props: P) => VNode;\n\n// ---------------------------------------------------------------------------\n// JSX namespace\n// ---------------------------------------------------------------------------\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n /**\n * JSX.Element is VNode so that components returning any valid VNode\n * (string, VNodeElement, Fragment, null, etc.) satisfy TypeScript's\n * component return-type check. The jsx() factory always produces\n * VNodeElement at runtime; the wider union is only for type-checking.\n */\n export type Element = VNode;\n\n // Catch-all — allows arbitrary lowercase XML tags as intrinsic elements.\n export interface IntrinsicElements {\n [tag: string]: { children?: VNode; [attr: string]: unknown };\n }\n\n export interface ElementChildrenAttribute {\n children: VNode;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fragment symbol\n// ---------------------------------------------------------------------------\n\n/**\n * Fragment — a unique Symbol used as the `type` of JSX fragment VNodes.\n * render.ts detects this Symbol and renders children directly, with no wrapper.\n * Using a Symbol (rather than a function) eliminates the circular dependency\n * that previously required _render-registry.ts.\n */\nexport const Fragment = Symbol('Fragment');\n\n// ---------------------------------------------------------------------------\n// JSX factory\n// ---------------------------------------------------------------------------\n\n/**\n * JSX factory — called by Bun's compiled JSX. Builds VNode tree; no evaluation.\n *\n * `type` is a function component, a string tag name, or the Fragment symbol.\n * String tags are rendered as XML blocks by render.ts: `<tag attrs>\\ncontent\\n</tag>\\n`\n * (or self-closing when the inner content is empty).\n */\nexport function jsx(\n type: Component | string | typeof Fragment,\n props: Record<string, unknown>,\n): VNodeElement {\n return { type, props };\n}\n\n/** jsxs — same as jsx, used when there are multiple children (children is array) */\nexport const jsxs = jsx;\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAuFO,IAAM,WAAW,OAAO,UAAU;AAalC,SAAS,GAAG,CACjB,MACA,OACc;AAAA,EACd,OAAO,EAAE,MAAM,MAAM;AAAA;AAIhB,IAAM,OAAO;",
|
|
8
|
+
"debugId": "A10E4D57EB3603AA64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|