@kubb/renderer-jsx 5.0.0-beta.3 → 5.0.0-beta.30

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.
@@ -0,0 +1,38 @@
1
+ import { stringify } from 'yaml'
2
+ import type { Key, KubbReactElement } from '../types.ts'
3
+
4
+ type Props = {
5
+ key?: Key
6
+ /**
7
+ * Plain object serialised as YAML between `---` fences.
8
+ *
9
+ * @example
10
+ * `data: { title: 'Pets', layout: 'doc' }`
11
+ */
12
+ data: Record<string, unknown>
13
+ }
14
+
15
+ /**
16
+ * Emits a YAML frontmatter envelope at the top of a generated markdown file.
17
+ *
18
+ * Renders a `<File.Source>` block containing `---\n<yaml>\n---`. Place it as
19
+ * the first child of `<File>` so it appears at the top of the output. Pair with
20
+ * `parserMd` to write `.md` files that downstream tooling (VitePress, MDX,
21
+ * static-site generators) treats as frontmatter.
22
+ *
23
+ * @example Page frontmatter at the top of a generated markdown file
24
+ * ```tsx
25
+ * <File baseName="pets.md" path="src/pets.md">
26
+ * <Frontmatter data={{ title: 'Pets', layout: 'doc' }} />
27
+ * <File.Source>
28
+ * {'# Pets\n\nList of pets.'}
29
+ * </File.Source>
30
+ * </File>
31
+ * ```
32
+ */
33
+ export function Frontmatter({ data }: Props): KubbReactElement {
34
+ const envelope = `---\n${stringify(data).trimEnd()}\n---`
35
+ return <kubb-source name="frontmatter">{envelope}</kubb-source>
36
+ }
37
+
38
+ Frontmatter.displayName = 'Frontmatter'
@@ -14,28 +14,28 @@ type Props = {
14
14
  * Requires `export` to also be `true`.
15
15
  * @default false
16
16
  */
17
- default?: boolean
17
+ default?: boolean | null
18
18
  /**
19
19
  * Parameter list written verbatim between the function's parentheses.
20
20
  *
21
21
  * @example
22
22
  * `params: 'petId: string, options?: RequestOptions'`
23
23
  */
24
- params?: string
24
+ params?: string | null
25
25
  /**
26
26
  * Emit the `export` keyword before the function declaration.
27
27
  * - `true` generates `export function name(…) { … }`
28
28
  * - `false` generates `function name(…) { … }`
29
29
  * @default false
30
30
  */
31
- export?: boolean
31
+ export?: boolean | null
32
32
  /**
33
33
  * Emit the `async` keyword, making this an async function.
34
34
  * The return type is automatically wrapped in `Promise<returnType>` when both
35
35
  * `async` and `returnType` are set.
36
36
  * @default false
37
37
  */
38
- async?: boolean
38
+ async?: boolean | null
39
39
  /**
40
40
  * TypeScript generic type parameters written verbatim between `<` and `>`.
41
41
  * Pass an array to emit multiple parameters separated by commas.
@@ -46,7 +46,7 @@ type Props = {
46
46
  * @example Multiple generics
47
47
  * `generics: ['TData', 'TError = unknown']`
48
48
  */
49
- generics?: string | string[]
49
+ generics?: string | Array<string> | null
50
50
  /**
51
51
  * TypeScript return type annotation written verbatim after `:`.
52
52
  * When `async` is `true`, the value is automatically wrapped in `Promise<…>`.
@@ -54,12 +54,12 @@ type Props = {
54
54
  * @example
55
55
  * `returnType: 'Pet'`
56
56
  */
57
- returnType?: string
57
+ returnType?: string | null
58
58
  /**
59
59
  * JSDoc block to prepend to the function declaration.
60
60
  * Each entry in `comments` becomes one line inside the emitted `/** … *\/` block.
61
61
  */
62
- JSDoc?: JSDoc
62
+ JSDoc?: JSDoc | null
63
63
  /**
64
64
  * Child nodes rendered as the body of the function.
65
65
  */
@@ -110,7 +110,7 @@ type ArrowFunctionProps = Props & {
110
110
  * - `false` generates `const name = (…) => { … }`
111
111
  * @default false
112
112
  */
113
- singleLine?: boolean
113
+ singleLine?: boolean | null
114
114
  }
115
115
 
116
116
  /**
@@ -0,0 +1,34 @@
1
+ import type { Key, KubbReactElement } from '../types.ts'
2
+
3
+ type Level = 1 | 2 | 3 | 4 | 5 | 6
4
+
5
+ type Props = {
6
+ key?: Key
7
+ /**
8
+ * Heading depth, `1` through `6`. Matches the number of `#` characters
9
+ * prefixed to the heading text.
10
+ */
11
+ level: Level
12
+ /**
13
+ * Heading text. Inline markdown (links, emphasis) is passed through verbatim.
14
+ */
15
+ children: string
16
+ }
17
+
18
+ /**
19
+ * Renders an ATX-style markdown heading.
20
+ *
21
+ * Emits a `<File.Source>` block containing `${'#'.repeat(level)} ${children}`.
22
+ * Use inside a `<File>` rendered by `parserMd`.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <Heading level={2}>Installation</Heading>
27
+ * // ## Installation
28
+ * ```
29
+ */
30
+ export function Heading({ level, children }: Props): KubbReactElement {
31
+ return <kubb-source name="heading">{`${'#'.repeat(level)} ${children}`}</kubb-source>
32
+ }
33
+
34
+ Heading.displayName = 'Heading'
@@ -0,0 +1,40 @@
1
+ import type { Key, KubbReactElement } from '../types.ts'
2
+
3
+ type Props = {
4
+ key?: Key
5
+ /**
6
+ * When `true`, emits a numbered list (`1. …`). When `false` or omitted,
7
+ * emits a bullet list (`- …`).
8
+ *
9
+ * @default false
10
+ */
11
+ ordered?: boolean | null
12
+ /**
13
+ * One entry per line. Inline markdown is passed through verbatim.
14
+ */
15
+ items: ReadonlyArray<string>
16
+ }
17
+
18
+ /**
19
+ * Renders a markdown list.
20
+ *
21
+ * Emits a `<File.Source>` block containing one entry per line, prefixed with
22
+ * `1.` / `2.` … when `ordered`, or `-` otherwise.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <List items={['Add the parser', 'Render the page']} />
27
+ * // - Add the parser
28
+ * // - Render the page
29
+ *
30
+ * <List ordered items={['First', 'Second']} />
31
+ * // 1. First
32
+ * // 2. Second
33
+ * ```
34
+ */
35
+ export function List({ ordered, items }: Props): KubbReactElement {
36
+ const body = items.map((item, index) => `${ordered ? `${index + 1}.` : '-'} ${item}`).join('\n')
37
+ return <kubb-source name="list">{body}</kubb-source>
38
+ }
39
+
40
+ List.displayName = 'List'
@@ -0,0 +1,28 @@
1
+ import type { Key, KubbReactElement } from '../types.ts'
2
+
3
+ type Props = {
4
+ key?: Key
5
+ /**
6
+ * Paragraph text. Inline markdown (links, emphasis, code spans) is passed
7
+ * through verbatim.
8
+ */
9
+ children: string
10
+ }
11
+
12
+ /**
13
+ * Renders a markdown paragraph.
14
+ *
15
+ * Emits a `<File.Source>` block containing the text as-is. Paragraphs are
16
+ * separated from surrounding blocks by blank lines via the parser's source
17
+ * joining.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <Paragraph>{'A pet object with `id` and `name` fields.'}</Paragraph>
22
+ * ```
23
+ */
24
+ export function Paragraph({ children }: Props): KubbReactElement {
25
+ return <kubb-source name="paragraph">{children}</kubb-source>
26
+ }
27
+
28
+ Paragraph.displayName = 'Paragraph'
@@ -16,12 +16,12 @@ type TypeProps = {
16
16
  * - `false` generates `type Name = …`
17
17
  * @default false
18
18
  */
19
- export?: boolean
19
+ export?: boolean | null
20
20
  /**
21
21
  * JSDoc block to prepend to the type alias declaration.
22
22
  * Each entry in `comments` becomes one line inside the emitted `/** … *\/` block.
23
23
  */
24
- JSDoc?: JSDoc
24
+ JSDoc?: JSDoc | null
25
25
  /**
26
26
  * Child nodes rendered as the type expression on the right-hand side of the alias.
27
27
  */
package/src/constants.ts CHANGED
@@ -5,20 +5,30 @@ import type { ElementNames } from './types.ts'
5
5
  */
6
6
  export const TEXT_NODE_NAME = '#text' as const
7
7
 
8
+ export const KUBB_FILE = 'kubb-file' as const
9
+ export const KUBB_SOURCE = 'kubb-source' as const
10
+ export const KUBB_EXPORT = 'kubb-export' as const
11
+ export const KUBB_IMPORT = 'kubb-import' as const
12
+ export const KUBB_FUNCTION = 'kubb-function' as const
13
+ export const KUBB_ARROW_FUNCTION = 'kubb-arrow-function' as const
14
+ export const KUBB_CONST = 'kubb-const' as const
15
+ export const KUBB_TYPE = 'kubb-type' as const
16
+ export const KUBB_JSX = 'kubb-jsx' as const
17
+
8
18
  /**
9
19
  * Set of all element names recognized by the Kubb renderer.
10
20
  * Used to distinguish Kubb-owned elements from unrecognized or text nodes during tree traversal.
11
21
  */
12
22
  export const nodeNames = new Set<ElementNames>([
13
- 'kubb-export',
14
- 'kubb-file',
15
- 'kubb-source',
16
- 'kubb-import',
17
- 'kubb-function',
18
- 'kubb-arrow-function',
19
- 'kubb-const',
20
- 'kubb-type',
21
- 'kubb-jsx',
23
+ KUBB_EXPORT,
24
+ KUBB_FILE,
25
+ KUBB_SOURCE,
26
+ KUBB_IMPORT,
27
+ KUBB_FUNCTION,
28
+ KUBB_ARROW_FUNCTION,
29
+ KUBB_CONST,
30
+ KUBB_TYPE,
31
+ KUBB_JSX,
22
32
  'kubb-text',
23
33
  'kubb-root',
24
34
  'kubb-app',
@@ -1,93 +1,112 @@
1
1
  import type { FileNode } from '@kubb/ast'
2
2
  import { Runtime } from './Runtime.tsx'
3
+ import { SyncRuntime } from './SyncRuntime.tsx'
3
4
  import type { KubbReactElement } from './types.ts'
4
5
 
5
- type Options = {
6
- /**
7
- * Print each render result to the console for debugging.
8
- * Useful when diagnosing output differences between renders.
9
- * @default false
10
- */
11
- debug?: boolean
12
- }
13
-
14
- /**
15
- * The renderer instance returned by {@link createRenderer}.
16
- */
17
- type Renderer = {
18
- /**
19
- * Render a JSX element tree and collect the resulting {@link FileNode} entries.
20
- * Resolves once all synchronous render work (including React's flush) is done.
21
- */
22
- render(Element: KubbReactElement): Promise<void>
23
- /**
24
- * Tear down the renderer and release all React resources.
25
- * Pass an `Error` to signal an abnormal shutdown.
26
- */
27
- unmount(error?: Error | number | null): void
28
- /**
29
- * The {@link FileNode} entries collected from the most recent `render` call.
30
- */
31
- files: Array<FileNode>
32
- }
33
-
34
6
  /**
35
- * Create a Kubb JSX renderer.
7
+ * Renderer factory that turns the JSX produced by a generator into
8
+ * `FileNode`s using React's reconciler under the hood. Pass as the `renderer`
9
+ * property on `defineGenerator`. Kubb core stays generic, with no hard
10
+ * dependency on `@kubb/renderer-jsx`.
36
11
  *
37
- * The renderer converts a React JSX element tree built from the components in this
38
- * package into an array of {@link FileNode} entries representing the generated files.
12
+ * Use this when generators rely on React features (hooks, suspense, context).
13
+ * For pure-function components, see {@link jsxRendererSync} for ~2-4× faster
14
+ * rendering.
39
15
  *
40
- * @example Basic usage
41
- * ```ts
42
- * import { createRenderer, File } from '@kubb/renderer-jsx'
16
+ * @example Wire up a JSX generator
17
+ * ```tsx
18
+ * import { defineGenerator } from '@kubb/core'
19
+ * import { jsxRenderer } from '@kubb/renderer-jsx'
43
20
  *
44
- * const renderer = createRenderer()
45
- * await renderer.render(
46
- * <File baseName="pet.ts" path="src/models/pet.ts">
47
- * <File.Source name="Pet" isExportable isIndexable>
48
- * {`export type Pet = { id: number; name: string }`}
49
- * </File.Source>
50
- * </File>
51
- * )
52
- * console.log(renderer.files) // [FileNode]
53
- * renderer.unmount()
21
+ * export const myGenerator = defineGenerator<PluginTs>({
22
+ * name: 'types',
23
+ * renderer: jsxRenderer,
24
+ * schema(node, ctx) {
25
+ * return (
26
+ * <File baseName="output.ts" path={`${ctx.root}/output.ts`}>
27
+ * <Type node={node} resolver={ctx.resolver} />
28
+ * </File>
29
+ * )
30
+ * },
31
+ * })
54
32
  * ```
55
33
  */
56
- export function createRenderer(options: Options = {}): Renderer {
57
- const runtime = new Runtime(options)
58
-
34
+ export const jsxRenderer = () => {
35
+ const runtime = new Runtime()
59
36
  return {
60
- async render(Element) {
61
- await runtime.render(Element)
37
+ async render(element: KubbReactElement) {
38
+ await runtime.render(element)
62
39
  },
63
40
  get files() {
64
41
  return runtime.nodes
65
42
  },
66
- unmount(error) {
43
+ dispose() {
44
+ runtime.unmount()
45
+ },
46
+ unmount(error?: Error | number | null) {
67
47
  runtime.unmount(error)
68
48
  },
49
+ [Symbol.dispose]() {
50
+ this.dispose()
51
+ },
69
52
  }
70
53
  }
71
54
 
72
55
  /**
73
- * A renderer factory for generators that produce JSX output.
56
+ * Lightweight renderer that walks the JSX tree in a single recursive pass —
57
+ * no React reconciler, no scheduler. Drop-in replacement for
58
+ * {@link jsxRenderer} at roughly 2–4× the throughput.
74
59
  *
75
- * Pass this as the `renderer` property of a `defineGenerator` call so that
76
- * core can render the JSX element tree returned by your generator methods
77
- * without a hard dependency on `@kubb/renderer-jsx`.
60
+ * Constraints: every component must be a pure function. Hooks, suspense, and
61
+ * class components are not supported.
78
62
  *
79
- * @example
80
- * ```ts
81
- * import { jsxRenderer } from '@kubb/renderer-jsx'
63
+ * Use this for generators that produce large amounts of output and do not need
64
+ * React's runtime features. It also exposes `stream()` for incremental file
65
+ * emission.
66
+ *
67
+ * @example Drop-in faster renderer
68
+ * ```tsx
82
69
  * import { defineGenerator } from '@kubb/core'
70
+ * import { jsxRendererSync } from '@kubb/renderer-jsx'
83
71
  *
84
72
  * export const myGenerator = defineGenerator<PluginTs>({
85
- * name: 'my-generator',
86
- * renderer: jsxRenderer,
87
- * schema(node, options) {
88
- * return <File baseName="output.ts" path="src/output.ts">...</File>
73
+ * name: 'types',
74
+ * renderer: jsxRendererSync,
75
+ * schema(node, ctx) {
76
+ * return (
77
+ * <File baseName="output.ts" path={`${ctx.root}/output.ts`}>
78
+ * <Type node={node} resolver={ctx.resolver} />
79
+ * </File>
80
+ * )
89
81
  * },
90
82
  * })
91
83
  * ```
84
+ *
85
+ * @example Stream files as they are produced
86
+ * ```tsx
87
+ * const renderer = jsxRendererSync()
88
+ * for (const file of renderer.stream(element)) {
89
+ * await writeFile(file.path, file.sources[0])
90
+ * }
91
+ * ```
92
92
  */
93
- export const jsxRenderer: () => Renderer = () => createRenderer()
93
+ export const jsxRendererSync = () => {
94
+ const runtime = new SyncRuntime()
95
+
96
+ return {
97
+ async render(element: KubbReactElement): Promise<void> {
98
+ runtime.render(element)
99
+ },
100
+ get files() {
101
+ return runtime.nodes
102
+ },
103
+ stream(element: KubbReactElement): Generator<FileNode> {
104
+ return runtime.stream(element)
105
+ },
106
+ dispose() {},
107
+ unmount(_error?: Error | number | null) {},
108
+ [Symbol.dispose]() {
109
+ this.dispose()
110
+ },
111
+ }
112
+ }
package/src/dom.ts CHANGED
@@ -6,14 +6,12 @@ import type { DOMElement, DOMNode, DOMNodeAttribute, TextNode } from './types.ts
6
6
  * The element has no attributes, no children, and no parent.
7
7
  */
8
8
  export const createNode = (nodeName: string): DOMElement => {
9
- const node: DOMElement = {
9
+ return {
10
10
  nodeName: nodeName as DOMElement['nodeName'],
11
- attributes: new Map(),
11
+ attributes: Object.create(null) as Record<string, DOMNodeAttribute>,
12
12
  childNodes: [],
13
- parentNode: undefined,
13
+ parentNode: null,
14
14
  }
15
-
16
- return node
17
15
  }
18
16
 
19
17
  /**
@@ -50,7 +48,6 @@ export const insertBeforeNode = (node: DOMElement, newChildNode: DOMNode, before
50
48
  const index = node.childNodes.indexOf(beforeChildNode)
51
49
  if (index >= 0) {
52
50
  node.childNodes.splice(index, 0, newChildNode)
53
-
54
51
  return
55
52
  }
56
53
 
@@ -62,7 +59,7 @@ export const insertBeforeNode = (node: DOMElement, newChildNode: DOMNode, before
62
59
  * Does nothing if `removeNode` is not a direct child of `node`.
63
60
  */
64
61
  export const removeChildNode = (node: DOMElement, removeNode: DOMNode): void => {
65
- removeNode.parentNode = undefined
62
+ removeNode.parentNode = null
66
63
 
67
64
  const index = node.childNodes.indexOf(removeNode)
68
65
  if (index >= 0) {
@@ -74,32 +71,23 @@ export const removeChildNode = (node: DOMElement, removeNode: DOMNode): void =>
74
71
  * Set an attribute on `node`, storing it in the node's `attributes` map.
75
72
  */
76
73
  export const setAttribute = (node: DOMElement, key: string, value: DOMNodeAttribute): void => {
77
- node.attributes.set(key, value)
74
+ node.attributes[key] = value
78
75
  }
79
76
 
80
77
  /**
81
78
  * Create a new {@link TextNode} with the given text value.
82
79
  */
83
80
  export const createTextNode = (text: string): TextNode => {
84
- const node: TextNode = {
81
+ return {
85
82
  nodeName: TEXT_NODE_NAME,
86
83
  nodeValue: text,
87
- parentNode: undefined,
84
+ parentNode: null,
88
85
  }
89
-
90
- setTextNodeValue(node, text)
91
-
92
- return node
93
86
  }
94
87
 
95
88
  /**
96
89
  * Update the `nodeValue` of an existing {@link TextNode}.
97
- * Non-string values are coerced to strings via `String(text)`.
98
90
  */
99
91
  export const setTextNodeValue = (node: TextNode, text: string): void => {
100
- if (typeof text !== 'string') {
101
- text = String(text)
102
- }
103
-
104
92
  node.nodeValue = text
105
93
  }
package/src/index.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  export { createContext, inject, provide, unprovide } from '@internals/utils'
2
+ export { Callout } from './components/Callout.tsx'
3
+ export { CodeBlock } from './components/CodeBlock.tsx'
2
4
  export { Const } from './components/Const.tsx'
3
5
  export { File } from './components/File.tsx'
6
+ export { Frontmatter } from './components/Frontmatter.tsx'
4
7
  export { Function } from './components/Function.tsx'
8
+ export { Heading } from './components/Heading.tsx'
5
9
  export { Jsx } from './components/Jsx.tsx'
10
+ export { List } from './components/List.tsx'
11
+ export { Paragraph } from './components/Paragraph.tsx'
6
12
  export { Root } from './components/Root.tsx'
7
13
  export { Type } from './components/Type.tsx'
8
- export { createRenderer, jsxRenderer } from './createRenderer.tsx'
14
+ export { jsxRenderer, jsxRendererSync } from './createRenderer.tsx'
package/src/types.ts CHANGED
@@ -30,14 +30,15 @@ export type ElementNames =
30
30
  | 'kubb-app'
31
31
 
32
32
  type Node = {
33
- parentNode: DOMElement | undefined
33
+ parentNode: DOMElement | null
34
34
  internal_static?: boolean
35
35
  }
36
36
 
37
37
  /**
38
38
  * Allowed attribute value types for DOM elements.
39
+ * `null` signals intentionally empty — the prop was explicitly cleared.
39
40
  */
40
- export type DOMNodeAttribute = boolean | string | number | Record<string, unknown> | Array<unknown>
41
+ export type DOMNodeAttribute = boolean | string | number | null | Record<string, unknown> | Array<unknown>
41
42
 
42
43
  type TextName = '#text'
43
44
 
@@ -71,11 +72,11 @@ export type DOMElement = {
71
72
  /**
72
73
  * Key/value attributes passed as JSX props to this element.
73
74
  */
74
- attributes: Map<string, DOMNodeAttribute>
75
+ attributes: Record<string, DOMNodeAttribute>
75
76
  /**
76
77
  * Ordered list of child nodes attached to this element.
77
78
  */
78
- childNodes: DOMNode[]
79
+ childNodes: Array<DOMNode>
79
80
  internal_transform?: OutputTransformer
80
81
 
81
82
  // Internal properties
@@ -119,12 +120,12 @@ export type KubbTextProps = {
119
120
  * Represents a generated file.
120
121
  */
121
122
  export type KubbFileProps = {
122
- id?: string
123
+ id?: string | null
123
124
  children?: KubbReactNode
124
125
  baseName: string
125
126
  path: string
126
- override?: boolean
127
- meta?: FileNode['meta']
127
+ override?: boolean | null
128
+ meta?: FileNode['meta'] | null
128
129
  }
129
130
 
130
131
  /**