From 5d0dd11d385c806dd73fc1be879fea26b67653f3 Mon Sep 17 00:00:00 2001 From: Fuma Nama Date: Thu, 27 Feb 2025 19:00:46 +0800 Subject: [PATCH] TypeScript: Support overriding `renderMarkdown` function --- .changeset/every-parks-stand.md | 5 ++ packages/typescript/package.json | 6 +- packages/typescript/src/markdown.ts | 84 +++++++------------ .../typescript/src/ui/auto-type-table.tsx | 11 ++- packages/typescript/tsup.config.ts | 2 +- pnpm-lock.yaml | 25 +++--- 6 files changed, 56 insertions(+), 77 deletions(-) create mode 100644 .changeset/every-parks-stand.md diff --git a/.changeset/every-parks-stand.md b/.changeset/every-parks-stand.md new file mode 100644 index 000000000..ff14b1fbb --- /dev/null +++ b/.changeset/every-parks-stand.md @@ -0,0 +1,5 @@ +--- +'fumadocs-typescript': patch +--- + +Support overriding `renderMarkdown` function diff --git a/packages/typescript/package.json b/packages/typescript/package.json index 0e475c9af..c703d4cd1 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -37,9 +37,8 @@ "dependencies": { "fast-glob": "^3.3.3", "hast-util-to-jsx-runtime": "^2.3.5", - "mdast-util-from-markdown": "^2.0.2", - "mdast-util-gfm": "^3.1.0", - "mdast-util-to-hast": "^13.2.0", + "remark": "^15.0.1", + "remark-rehype": "^11.1.1", "shiki": "^3.1.0", "ts-morph": "^25.0.1" }, @@ -51,6 +50,7 @@ "@types/react": "19.0.10", "@types/react-dom": "19.0.4", "eslint-config-custom": "workspace:*", + "fumadocs-core": "workspace:*", "fumadocs-ui": "workspace:*", "tsconfig": "workspace:*", "typescript": "^5.7.3" diff --git a/packages/typescript/src/markdown.ts b/packages/typescript/src/markdown.ts index b2faf0e1a..4de86e536 100644 --- a/packages/typescript/src/markdown.ts +++ b/packages/typescript/src/markdown.ts @@ -1,64 +1,36 @@ -import { fromMarkdown } from 'mdast-util-from-markdown'; -import { gfmFromMarkdown } from 'mdast-util-gfm'; -import { toHast } from 'mdast-util-to-hast'; -import { getSingletonHighlighter, type LanguageRegistration } from 'shiki'; import type { Nodes } from 'hast'; -import { type Code, type RootContent } from 'mdast'; +import { remark } from 'remark'; +import { + remarkGfm, + rehypeCode, + type RehypeCodeOptions, +} from 'fumadocs-core/mdx-plugins'; +import remarkRehype from 'remark-rehype'; -export async function renderMarkdownToHast(md: string): Promise { - const mdast = fromMarkdown( - md.replace(/{@link (?[^}]*)}/g, '$1'), // replace jsdoc links - { mdastExtensions: [gfmFromMarkdown()] }, - ); - - const highlighter = await getSingletonHighlighter({ - themes: ['vitesse-light', 'vitesse-dark'], - }); +const processor = remark() + .use(remarkGfm) + .use(remarkRehype) + .use(rehypeCode, { + lazy: true, - async function preload(contents: RootContent[]): Promise { - await Promise.all( - contents.map(async (c) => { - if ('children' in c) await preload(c.children); + themes: { + light: 'github-light', + dark: 'github-dark', + }, + } satisfies RehypeCodeOptions) + // @ts-expect-error -- safe + .use(() => { + return (tree, file: { data: Record }) => { + file.data.tree = tree; - if (c.type === 'code' && c.lang) { - await highlighter.loadLanguage( - c.lang as unknown as LanguageRegistration, - ); - } - }), - ); - } + return ''; + }; + }); - await preload(mdast.children); +export async function renderMarkdownToHast(md: string): Promise { + md = md.replace(/{@link (?[^}]*)}/g, '$1'); // replace jsdoc links - return toHast(mdast, { - handlers: { - // @ts-expect-error hast with mdx - code(_, node: Code) { - const lang = node.lang ?? 'plaintext'; + const out = await processor.process(md); - return highlighter.codeToHast(node.value, { - lang, - themes: { - light: 'vitesse-light', - dark: 'vitesse-dark', - }, - defaultColor: false, - transformers: [ - { - name: 'rehype-code:pre-process', - line(hast) { - if (hast.children.length > 0) return; - // Keep the empty lines when using grid layout - hast.children.push({ - type: 'text', - value: ' ', - }); - }, - }, - ], - }).children; - }, - }, - }); + return out.data.tree as Nodes; } diff --git a/packages/typescript/src/ui/auto-type-table.tsx b/packages/typescript/src/ui/auto-type-table.tsx index 2a5136094..9ada64514 100644 --- a/packages/typescript/src/ui/auto-type-table.tsx +++ b/packages/typescript/src/ui/auto-type-table.tsx @@ -1,5 +1,5 @@ /// -import fs from 'node:fs/promises'; +import * as fs from 'node:fs/promises'; import { TypeTable } from 'fumadocs-ui/components/type-table'; import { type Jsx, toJsxRuntime } from 'hast-util-to-jsx-runtime'; import * as runtime from 'react/jsx-runtime'; @@ -11,6 +11,7 @@ import { } from '@/generate/base'; import 'server-only'; import { getProject } from '@/get-project'; +import type { ReactNode } from 'react'; export interface AutoTypeTableProps { /** @@ -46,6 +47,11 @@ export interface AutoTypeTableProps { */ type?: string; + /** + * Override the function to render markdown into JSX nodes + */ + renderMarkdown?: typeof renderMarkdownDefault; + options?: GenerateDocumentationOptions; } @@ -72,6 +78,7 @@ export async function AutoTypeTable({ path, name, type, + renderMarkdown = renderMarkdownDefault, options = {}, }: AutoTypeTableProps): Promise { let typeName = name; @@ -125,7 +132,7 @@ export async function AutoTypeTable({ ); } -async function renderMarkdown(md: string): Promise { +async function renderMarkdownDefault(md: string): Promise { return toJsxRuntime(await renderMarkdownToHast(md), { Fragment: runtime.Fragment, jsx: runtime.jsx as Jsx, diff --git a/packages/typescript/tsup.config.ts b/packages/typescript/tsup.config.ts index b4b280958..dbf28e454 100644 --- a/packages/typescript/tsup.config.ts +++ b/packages/typescript/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - external: ['server-only', 'fumadocs-ui', 'react'], + external: ['server-only', 'fumadocs-ui', 'fumadocs-core', 'react'], dts: true, target: 'es6', format: 'esm', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bc10fa1f..270132720 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1064,15 +1064,12 @@ importers: hast-util-to-jsx-runtime: specifier: ^2.3.5 version: 2.3.5 - mdast-util-from-markdown: - specifier: ^2.0.2 - version: 2.0.2 - mdast-util-gfm: - specifier: ^3.1.0 - version: 3.1.0 - mdast-util-to-hast: - specifier: ^13.2.0 - version: 13.2.0 + remark: + specifier: ^15.0.1 + version: 15.0.1 + remark-rehype: + specifier: ^11.1.1 + version: 11.1.1 shiki: specifier: ^3.1.0 version: 3.1.0 @@ -1101,6 +1098,9 @@ importers: eslint-config-custom: specifier: workspace:* version: link:../eslint-config-custom + fumadocs-core: + specifier: workspace:* + version: link:../core fumadocs-ui: specifier: workspace:* version: link:../ui @@ -3763,9 +3763,6 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001699: - resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==} - caniuse-lite@1.0.30001701: resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==} @@ -9714,7 +9711,7 @@ snapshots: browserslist@4.24.2: dependencies: - caniuse-lite: 1.0.30001699 + caniuse-lite: 1.0.30001701 electron-to-chromium: 1.5.71 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) @@ -9758,8 +9755,6 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001699: {} - caniuse-lite@1.0.30001701: {} ccount@2.0.1: {}