Core: enforce MDX stringifier by default

This commit is contained in:
Fuma Nama
2026-02-22 15:24:13 +08:00
parent 16360ef127
commit 1a614de584
16 changed files with 83 additions and 88 deletions
+8
View File
@@ -0,0 +1,8 @@
---
'fumadocs-obsidian': patch
'fumadocs-docgen': patch
'fumadocs-core': patch
'fumadocs-mdx': patch
---
enforce MDX stringifier by default
@@ -4,11 +4,12 @@ import remarkGfm from 'remark-gfm';
import type { PluggableList, Processor, Transformer } from 'unified';
import { visit } from 'unist-util-visit';
import { toMdxExport } from './mdast-utils';
import type {
MdxJsxAttribute,
MdxJsxExpressionAttribute,
MdxJsxFlowElement,
MdxJsxTextElement,
import {
mdxToMarkdown,
type MdxJsxAttribute,
type MdxJsxExpressionAttribute,
type MdxJsxFlowElement,
type MdxJsxTextElement,
} from 'mdast-util-mdx';
import { type Handle, type Options, toMarkdown } from 'mdast-util-to-markdown';
import { remarkHeading } from './remark-heading';
@@ -189,9 +190,10 @@ export function remarkStructure(
};
};
if (frontmatter._openapi?.structuredData) {
data.headings.push(...frontmatter._openapi.structuredData.headings);
data.contents.push(...frontmatter._openapi.structuredData.contents);
const openapiData = frontmatter._openapi?.structuredData;
if (openapiData) {
data.headings.push(...openapiData.headings);
data.contents.push(...openapiData.contents);
}
}
@@ -299,13 +301,14 @@ export function defaultStringifier(config: Options & StringifyOptions = {}): Str
return true;
},
...customExtension
} = config;
function modHandler(handler: Handle, ctx: StringifierContext): Handle {
return function (node: Nodes, parent, state, info) {
const { structuredData, _string } = node.data ?? {};
if (structuredData) ctx.addContent(...structuredData.contents);
if (_string) return typeof _string === 'function' ? _string() : _string;
if (node.data?.structuredData) ctx.addContent(...node.data.structuredData.contents);
if (node.data?._string)
return typeof node.data._string === 'function' ? node.data._string() : node.data._string;
const visibility = filterElement(node);
if (visibility === false) return '';
@@ -349,38 +352,37 @@ export function defaultStringifier(config: Options & StringifyOptions = {}): Str
};
}
const handlers: Record<string, Handle> = {
link(node: Link, _, state, info) {
return state.containerPhrasing(node, info);
},
heading(node: Heading, _, state, info) {
return state.containerPhrasing(node, info);
},
image() {
return '';
},
_custom(node: CustomRootNode, _, state, info) {
const handlers: Record<string, Handle> = state.handlers;
for (const k in handlers) {
handlers[k] = modHandler(handlers[k], node.ctx);
}
const customToMarkdown: Options = {
handlers: {
link(node: Link, _, state, info) {
return state.containerPhrasing(node, info);
},
heading(node: Heading, _, state, info) {
return state.containerPhrasing(node, info);
},
image() {
return '';
},
_custom(node: CustomRootNode, _, state, info) {
const handlers: Record<string, Handle> = state.handlers;
for (const k in handlers) {
handlers[k] = modHandler(handlers[k], node.ctx);
}
return state.handle(node.root, undefined, state, info);
},
...config.handlers,
return state.handle(node.root, undefined, state, info);
},
} as Record<string, Handle>,
};
return function (root, ctx) {
// from https://github.com/remarkjs/remark/blob/main/packages/remark-stringify/lib/index.js
const defaultExtensions = this.data('toMarkdownExtensions') ?? [];
return toMarkdown({ type: '_custom', root, ctx } satisfies CustomRootNode as never, {
...this.data('settings'),
...config,
extensions: config.extensions
? [...defaultExtensions, ...config.extensions]
: defaultExtensions,
handlers,
extensions: [
mdxToMarkdown(),
...(this.data('toMarkdownExtensions') ?? []),
customToMarkdown,
customExtension,
],
});
};
}
+3 -3
View File
@@ -56,7 +56,7 @@
"@types/node": "25.2.3",
"eslint-config-custom": "workspace:*",
"fumadocs-core": "workspace:*",
"mdast-util-mdx-jsx": "^3.2.0",
"mdast-util-mdx": "^3.0.0",
"remark": "^15.0.1",
"tsconfig": "workspace:*",
"tsdown": "^0.20.3",
@@ -67,10 +67,10 @@
"@types/hast": "*",
"@types/mdast": "*",
"fumadocs-core": "^15.7.2 || ^16.0.0",
"mdast-util-mdx-jsx": "*"
"mdast-util-mdx": "*"
},
"peerDependenciesMeta": {
"mdast-util-mdx-jsx": {
"mdast-util-mdx": {
"optional": true
},
"@types/hast": {
+1 -1
View File
@@ -1,4 +1,4 @@
/// <reference types="mdast-util-mdx-jsx" />
/// <reference types="mdast-util-mdx" />
import type { Transformer } from 'unified';
import type { Root, RootContent } from 'mdast';
import { visit } from 'unist-util-visit';
+1 -1
View File
@@ -1,5 +1,5 @@
import type { Expression, Program } from 'estree';
import type { MdxJsxAttribute } from 'mdast-util-mdx-jsx';
import type { MdxJsxAttribute } from 'mdast-util-mdx';
export function createElement(name: string, attributes: object[], children?: unknown): object {
const element: Record<string, unknown> = {
+2 -7
View File
@@ -73,10 +73,10 @@
"esbuild": "^0.27.3",
"estree-util-value-to-estree": "^3.5.0",
"js-yaml": "^4.1.1",
"mdast-util-mdx": "^3.0.0",
"mdast-util-to-markdown": "^2.1.2",
"picocolors": "^1.1.1",
"picomatch": "^4.0.3",
"remark-mdx": "^3.1.1",
"tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"unified": "^11.0.5",
@@ -97,12 +97,11 @@
"eslint-config-custom": "workspace:*",
"fumadocs-core": "workspace:*",
"mdast-util-directive": "^3.1.0",
"mdast-util-mdx-jsx": "^3.2.0",
"next": "16.1.6",
"react": "^19.2.4",
"remark": "^15.0.1",
"remark-directive": "^4.0.0",
"remark-stringify": "^11.0.0",
"remark-mdx": "^3.1.1",
"rollup": "^4.57.1",
"tsconfig": "workspace:*",
"tsdown": "^0.20.3",
@@ -116,7 +115,6 @@
"@types/react": "*",
"fumadocs-core": "^15.0.0 || ^16.0.0",
"mdast-util-directive": "*",
"mdast-util-mdx-jsx": "*",
"next": "^15.3.0 || ^16.0.0",
"react": "*",
"vite": "6.x.x || 7.x.x"
@@ -131,9 +129,6 @@
"@types/mdx": {
"optional": true
},
"mdast-util-mdx-jsx": {
"optional": true
},
"react": {
"optional": true
},
@@ -4,7 +4,7 @@ import type { Code, Node, Root, RootContent } from 'mdast';
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { fumaMatter } from '@/utils/fuma-matter';
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx';
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx';
import { remarkHeading } from 'fumadocs-core/mdx-plugins';
import { VFile } from 'vfile';
import type { Directives } from 'mdast-util-directive';
@@ -4,8 +4,8 @@ import { visit } from 'unist-util-visit';
import { toMarkdown } from 'mdast-util-to-markdown';
import { valueToEstree } from 'estree-util-value-to-estree';
import { removePosition } from 'unist-util-remove-position';
import remarkMdx from 'remark-mdx';
import { flattenNode } from './mdast-utils';
import { mdxToMarkdown } from 'mdast-util-mdx';
export interface ExtractedReference {
href: string;
@@ -60,15 +60,6 @@ export function remarkPostprocess(
valueToExport = [],
}: PostprocessOptions,
): Transformer<Root, Root> {
let _stringifyProcessor: Processor | undefined;
const getStringifyProcessor = () => {
return (_stringifyProcessor ??=
_format === 'mdx'
? this
: // force Markdown processor to stringify MDX nodes
this().use(remarkMdx).freeze());
};
return (tree, file) => {
const frontmatter = (file.data.frontmatter ??= {});
if (!frontmatter.title) {
@@ -105,13 +96,15 @@ export function remarkPostprocess(
if (includeProcessedMarkdown) {
const { headingIds = true } =
typeof includeProcessedMarkdown === 'object' ? includeProcessedMarkdown : {};
const processor = getStringifyProcessor();
const defaultExtensions = this.data('toMarkdownExtensions');
const extensions = defaultExtensions ? [...defaultExtensions] : [];
if (_format === 'md') extensions.push(mdxToMarkdown());
const markdown = toMarkdown(tree, {
...processor.data('settings'),
// from https://github.com/remarkjs/remark/blob/main/packages/remark-stringify/lib/index.js
extensions: processor.data('toMarkdownExtensions') || [],
...this.data('settings'),
extensions,
handlers: {
heading: (node: Heading) => {
heading(node: Heading) {
const id = node.data?.hProperties?.id;
const content = flattenNode(node);
return headingIds && id ? `${content} [#${id}]` : content;
+1 -1
View File
@@ -4,7 +4,7 @@
"paths": {
"@/*": ["./src/*"]
},
"types": ["remark-stringify", "@types/node", "@types/mdx"],
"types": ["@types/node", "@types/mdx"],
"jsx": "react-jsx",
"noEmit": true,
"lib": ["ESNext"],
+1 -1
View File
@@ -66,7 +66,7 @@
"@types/react-dom": "^19.2.3",
"eslint-config-custom": "workspace:*",
"fumadocs-core": "workspace:*",
"mdast-util-mdx-jsx": "^3.2.0",
"mdast-util-mdx": "^3.0.0",
"tsconfig": "workspace:*",
"tsdown": "^0.20.3",
"typescript": "^5.9.3"
+1 -1
View File
@@ -100,7 +100,7 @@ export async function convertVaultFiles(
});
const mdast = await processor.run(processor.parse(vfile), vfile);
const string = stringifier.stringify(mdast as Root);
const string = stringifier.stringify(mdast as Root, vfile);
let frontmatter: Record<string, unknown> = {
...file.frontmatter,
title: path.basename(file.path, path.extname(file.path)),
@@ -2,7 +2,7 @@ import { visit } from 'unist-util-visit';
import type { Transformer } from 'unified';
import type { Root } from 'mdast';
import { replace } from '@/utils/mdast-replace';
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
const Regex = /(?<!\\)\^(?<block_id>\w+)$/m;
@@ -1,7 +1,7 @@
import { visit } from 'unist-util-visit';
import type { Paragraph, Parent, PhrasingContent, Root, RootContent } from 'mdast';
import type { Transformer } from 'unified';
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
import { getFileHref, getHeadingHash } from '@/utils/get-refs';
import { ParsedFile } from '@/build-storage';
import { VaultResolver } from '@/build-resolver';
+1 -1
View File
@@ -1,5 +1,5 @@
import type { BlockContent, DefinitionContent } from 'mdast';
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
import type { MdxJsxFlowElement } from 'mdast-util-mdx';
export function createCallout(
type: string,
+2 -2
View File
@@ -30,8 +30,8 @@ export function parseTags(tags: RawTag[]): TypedTags {
typed.params ??= [];
typed.params.push({
name: param.trim(),
description: description.trim(),
name: param,
description,
});
continue;
}
+12 -15
View File
@@ -2115,9 +2115,9 @@ importers:
fumadocs-core:
specifier: workspace:*
version: link:../core
mdast-util-mdx-jsx:
specifier: ^3.2.0
version: 3.2.0
mdast-util-mdx:
specifier: ^3.0.0
version: 3.0.0
remark:
specifier: ^15.0.1
version: 15.0.1
@@ -2153,6 +2153,9 @@ importers:
js-yaml:
specifier: ^4.1.1
version: 4.1.1
mdast-util-mdx:
specifier: ^3.0.0
version: 3.0.0
mdast-util-to-markdown:
specifier: ^2.1.2
version: 2.1.2
@@ -2162,9 +2165,6 @@ importers:
picomatch:
specifier: ^4.0.3
version: 4.0.3
remark-mdx:
specifier: ^3.1.1
version: 3.1.1
tinyexec:
specifier: ^1.0.2
version: 1.0.2
@@ -2220,9 +2220,6 @@ importers:
mdast-util-directive:
specifier: ^3.1.0
version: 3.1.0
mdast-util-mdx-jsx:
specifier: ^3.2.0
version: 3.2.0
next:
specifier: 16.1.6
version: 16.1.6(@babel/[email protected])(@opentelemetry/[email protected])([email protected])([email protected]([email protected]))([email protected])
@@ -2235,9 +2232,9 @@ importers:
remark-directive:
specifier: ^4.0.0
version: 4.0.0
remark-stringify:
specifier: ^11.0.0
version: 11.0.0
remark-mdx:
specifier: ^3.1.1
version: 3.1.1
rollup:
specifier: ^4.57.1
version: 4.57.1
@@ -2372,9 +2369,9 @@ importers:
fumadocs-core:
specifier: workspace:*
version: link:../core
mdast-util-mdx-jsx:
specifier: ^3.2.0
version: 3.2.0
mdast-util-mdx:
specifier: ^3.0.0
version: 3.0.0
tsconfig:
specifier: workspace:*
version: link:../tsconfig