Skip to content

Commit f6cecbf

Browse files
committed
TypeScript: Use Universal Shiki configuartion for Markdown renderer
1 parent 275dae7 commit f6cecbf

File tree

6 files changed

+106
-83
lines changed

6 files changed

+106
-83
lines changed

.changeset/tired-adults-dream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fumadocs-typescript': minor
3+
---
4+
5+
Use Universal Shiki configuartion for Markdown renderer

packages/typescript/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@
6565
},
6666
"peerDependencies": {
6767
"@types/react": "*",
68-
"fumadocs-core": "^15.7.0 || ^16.0.0",
69-
"fumadocs-ui": "^15.7.0 || ^16.0.0",
68+
"fumadocs-core": "^16.5.0",
69+
"fumadocs-ui": "^16.5.0",
7070
"react": "*",
7171
"typescript": "*"
7272
},

packages/typescript/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export * from '@/lib/base';
2-
export { renderMarkdownToHast } from './markdown';
2+
export type { MarkdownRenderer } from './markdown';
33
export { createProject } from './create-project';
44
export * from '@/lib/remark-auto-type-table';
55
export * from './cache/fs-cache';

packages/typescript/src/lib/remark-auto-type-table.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import type { Nodes } from 'hast';
33
import type { Transformer } from 'unified';
44
import type { Expression, ExpressionStatement, ObjectExpression, Program } from 'estree';
55
import { createGenerator, type DocEntry, type Generator } from '@/lib/base';
6-
import { renderMarkdownToHast, renderTypeToHast } from '@/markdown';
6+
import { type MarkdownRenderer, markdownRenderer } from '@/markdown';
77
import { valueToEstree } from 'estree-util-value-to-estree';
88
import { visit } from 'unist-util-visit';
99
import { type BaseTypeTableProps, type GenerateTypeTableOptions } from '@/lib/type-table';
1010
import { toEstree } from 'hast-util-to-estree';
1111
import { type ParameterTag, parseTags } from '@/lib/parse-tags';
12+
import type { ResolvedShikiConfig } from 'fumadocs-core/highlight/config';
1213

1314
function objectBuilder() {
1415
const out: ObjectExpression = {
@@ -46,21 +47,19 @@ function objectBuilder() {
4647

4748
async function buildTypeProp(
4849
entries: DocEntry[],
49-
{
50-
renderMarkdown = renderMarkdownToHast,
51-
renderType = renderTypeToHast,
52-
}: RemarkAutoTypeTableOptions,
50+
renderer: MarkdownRenderer,
5351
): Promise<ObjectExpression> {
5452
async function onItem(entry: DocEntry) {
5553
const node = objectBuilder();
5654
const tags = parseTags(entry.tags);
57-
node.addJsxProperty('type', await renderType(entry.simplifiedType));
58-
node.addJsxProperty('typeDescription', await renderType(entry.type));
55+
node.addJsxProperty('type', await renderer.renderTypeToHast(entry.simplifiedType));
56+
node.addJsxProperty('typeDescription', await renderer.renderTypeToHast(entry.type));
5957
node.addExpressionNode('required', valueToEstree(entry.required));
6058

61-
if (tags.default) node.addJsxProperty('default', await renderType(tags.default));
59+
if (tags.default) node.addJsxProperty('default', await renderer.renderTypeToHast(tags.default));
6260

63-
if (tags.returns) node.addJsxProperty('returns', await renderMarkdown(tags.returns));
61+
if (tags.returns)
62+
node.addJsxProperty('returns', await renderer.renderMarkdownToHast(tags.returns));
6463

6564
if (tags.params) {
6665
node.addExpressionNode('parameters', {
@@ -70,7 +69,7 @@ async function buildTypeProp(
7069
}
7170

7271
if (entry.description) {
73-
node.addJsxProperty('description', await renderMarkdown(entry.description));
72+
node.addJsxProperty('description', await renderer.renderMarkdownToHast(entry.description));
7473
}
7574

7675
return node.build();
@@ -80,7 +79,7 @@ async function buildTypeProp(
8079
const node = objectBuilder();
8180
node.addExpressionNode('name', valueToEstree(param.name));
8281
if (param.description)
83-
node.addJsxProperty('description', await renderMarkdown(param.description));
82+
node.addJsxProperty('description', await renderer.renderMarkdownToHast(param.description));
8483

8584
return node.build();
8685
}
@@ -111,8 +110,12 @@ export interface RemarkAutoTypeTableOptions {
111110
*/
112111
outputName?: string;
113112

114-
renderMarkdown?: typeof renderMarkdownToHast;
115-
renderType?: typeof renderTypeToHast;
113+
/**
114+
* config for Shiki when using default `renderMarkdown` & `renderType`.
115+
*/
116+
shiki?: ResolvedShikiConfig;
117+
renderMarkdown?: MarkdownRenderer['renderMarkdownToHast'];
118+
renderType?: MarkdownRenderer['renderTypeToHast'];
116119

117120
/**
118121
* Customise type table generation
@@ -145,7 +148,19 @@ export function remarkAutoTypeTable(
145148
options: generateOptions = {},
146149
remarkStringify = true,
147150
generator = createGenerator(),
151+
renderMarkdown,
152+
renderType,
153+
shiki,
148154
} = config;
155+
let renderer: MarkdownRenderer;
156+
157+
if (renderMarkdown && renderType) {
158+
renderer = { renderMarkdownToHast: renderMarkdown, renderTypeToHast: renderType };
159+
} else {
160+
renderer = markdownRenderer(shiki);
161+
if (renderMarkdown) renderer.renderMarkdownToHast = renderMarkdown;
162+
if (renderType) renderer.renderTypeToHast = renderType;
163+
}
149164

150165
return async (tree, file) => {
151166
const queue: Promise<void>[] = [];
@@ -178,7 +193,7 @@ export function remarkAutoTypeTable(
178193
body: [
179194
{
180195
type: 'ExpressionStatement',
181-
expression: await buildTypeProp(doc.entries, config),
196+
expression: await buildTypeProp(doc.entries, renderer),
182197
},
183198
],
184199
} satisfies Program,
Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,56 @@
11
import type { ElementContent, Nodes } from 'hast';
22
import { remark } from 'remark';
33
import { remarkGfm } from 'fumadocs-core/mdx-plugins/remark-gfm';
4-
import { rehypeCode, type RehypeCodeOptions } from 'fumadocs-core/mdx-plugins/rehype-code';
4+
import { createRehypeCode } from 'fumadocs-core/mdx-plugins/rehype-code.min';
55
import remarkRehype from 'remark-rehype';
6-
import { getHighlighter } from 'fumadocs-core/highlight';
6+
import { highlightHast } from 'fumadocs-core/highlight/core';
7+
import { withJSEngine } from 'fumadocs-core/highlight/full/config';
78

8-
const shikiOptions = {
9-
lazy: true,
10-
langs: ['ts', 'tsx'],
11-
12-
// disable default transformers & meta parser
13-
transformers: [],
14-
parseMetaString: undefined,
15-
16-
themes: {
17-
light: 'github-light',
18-
dark: 'github-dark',
19-
},
20-
} satisfies RehypeCodeOptions;
21-
22-
const processor = remark().use(remarkGfm).use(remarkRehype).use(rehypeCode, shikiOptions);
23-
24-
export async function renderTypeToHast(type: string): Promise<Nodes> {
25-
const highlighter = await getHighlighter('js', {
26-
langs: ['ts'],
27-
themes: Object.values(shikiOptions.themes),
28-
});
29-
30-
const nodes = highlighter.codeToHast(type, {
31-
lang: 'ts',
32-
structure: 'inline',
33-
themes: shikiOptions.themes,
34-
defaultColor: false,
35-
});
9+
export interface MarkdownRenderer {
10+
renderTypeToHast: (type: string) => Nodes | Promise<Nodes>;
11+
renderMarkdownToHast: (md: string) => Nodes | Promise<Nodes>;
12+
}
3613

14+
export function markdownRenderer(shiki = withJSEngine): MarkdownRenderer {
15+
const processor = remark()
16+
.use(remarkGfm)
17+
.use(remarkRehype)
18+
.use(createRehypeCode(shiki), {
19+
lazy: true,
20+
langs: ['ts', 'tsx'],
21+
// disable default transformers & meta parser
22+
transformers: [],
23+
parseMetaString: undefined,
24+
});
3725
return {
38-
type: 'element',
39-
tagName: 'span',
40-
properties: {
41-
class: 'shiki',
42-
},
43-
children: [
44-
{
26+
async renderTypeToHast(type) {
27+
const nodes = await highlightHast(type, {
28+
config: shiki,
29+
lang: 'ts',
30+
structure: 'inline',
31+
defaultColor: false,
32+
});
33+
34+
return {
4535
type: 'element',
46-
tagName: 'code',
47-
properties: {},
48-
children: nodes.children as ElementContent[],
49-
},
50-
],
51-
};
52-
}
53-
54-
export async function renderMarkdownToHast(md: string): Promise<Nodes> {
55-
md = md.replace(/{@link (?<link>[^}]*)}/g, '$1'); // replace jsdoc links
36+
tagName: 'span',
37+
properties: {
38+
class: 'shiki',
39+
},
40+
children: [
41+
{
42+
type: 'element',
43+
tagName: 'code',
44+
properties: {},
45+
children: nodes.children as ElementContent[],
46+
},
47+
],
48+
};
49+
},
50+
renderMarkdownToHast(md) {
51+
md = md.replace(/{@link (?<link>[^}]*)}/g, '$1'); // replace jsdoc links
5652

57-
return processor.run(processor.parse(md));
53+
return processor.run(processor.parse(md));
54+
},
55+
};
5856
}

packages/typescript/src/ui/auto-type-table.tsx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,42 @@ import { type ParameterNode, type TypeNode, TypeTable } from 'fumadocs-ui/compon
22
import { type Jsx, toJsxRuntime } from 'hast-util-to-jsx-runtime';
33
import * as runtime from 'react/jsx-runtime';
44
import defaultMdxComponents from 'fumadocs-ui/mdx';
5-
import { renderMarkdownToHast, renderTypeToHast } from '@/markdown';
65
import 'server-only';
76
import type { ReactNode } from 'react';
87
import { type BaseTypeTableProps, type GenerateTypeTableOptions } from '@/lib/type-table';
98
import { type Generator } from '@/lib/base';
109
import type { Nodes } from 'hast';
1110
import { parseTags } from '@/lib/parse-tags';
11+
import type { ResolvedShikiConfig } from 'fumadocs-core/highlight/config';
12+
import { markdownRenderer } from '@/markdown';
1213

13-
export type AutoTypeTableProps = BaseTypeTableProps;
14+
interface JSXMarkdownRenderer {
15+
renderMarkdown: (md: string) => Promise<ReactNode>;
16+
renderType: (type: string) => Promise<ReactNode>;
17+
}
18+
19+
export interface AutoTypeTableProps extends BaseTypeTableProps, Partial<JSXMarkdownRenderer> {
20+
generator: Generator;
21+
22+
/** Shiki configuration when using default `renderMarkdown` & `renderType` */
23+
shiki?: ResolvedShikiConfig;
24+
options?: GenerateTypeTableOptions;
25+
}
1426

1527
export async function AutoTypeTable({
1628
generator,
1729
options = {},
18-
renderType = renderTypeDefault,
19-
renderMarkdown = renderMarkdownDefault,
30+
renderType,
31+
renderMarkdown,
32+
shiki,
2033
...props
21-
}: AutoTypeTableProps & {
22-
generator: Generator;
34+
}: AutoTypeTableProps) {
35+
if (!renderType || !renderMarkdown) {
36+
const renderer = markdownRenderer(shiki);
37+
renderType ??= async (v) => toJsx(await renderer.renderTypeToHast(v));
38+
renderMarkdown ??= async (v) => toJsx(await renderer.renderMarkdownToHast(v));
39+
}
2340

24-
renderMarkdown?: typeof renderMarkdownDefault;
25-
renderType?: typeof renderTypeDefault;
26-
options?: GenerateTypeTableOptions;
27-
}) {
2841
const output = await generator.generateTypeTable(props, options);
2942

3043
return output.map(async (item) => {
@@ -66,11 +79,3 @@ function toJsx(hast: Nodes) {
6679
components: { ...defaultMdxComponents, img: undefined },
6780
});
6881
}
69-
70-
async function renderTypeDefault(type: string): Promise<ReactNode> {
71-
return toJsx(await renderTypeToHast(type));
72-
}
73-
74-
async function renderMarkdownDefault(md: string): Promise<ReactNode> {
75-
return toJsx(await renderMarkdownToHast(md));
76-
}

0 commit comments

Comments
 (0)