MDX: Support getText() API

This commit is contained in:
Fuma Nama
2025-09-19 23:31:13 +08:00
parent 0903fb4671
commit 2862a10c2d
15 changed files with 395 additions and 205 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': minor
---
Unify doc collection entry for both Vite and Next.js integrations
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': minor
---
Support `getText()` API & `postprocess.includeProcessedMarkdown` option, deprecate `content` in favour of `getText()`.
+1
View File
@@ -77,6 +77,7 @@
"estree-util-value-to-estree": "^3.4.0",
"js-yaml": "^4.1.0",
"lru-cache": "^11.2.1",
"mdast-util-to-markdown": "^2.1.2",
"picocolors": "^1.1.1",
"remark-mdx": "^3.1.1",
"remark-parse": "^11.0.0",
+2
View File
@@ -2,6 +2,7 @@ import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { DefaultMDXOptions } from '@/utils/mdx-options';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { frontmatterSchema, metaSchema } from '@/config/zod-4';
import type { PostprocessOptions } from '@/mdx-plugins/remark-postprocess';
export type CollectionSchema<Schema extends StandardSchemaV1, Context> =
| Schema
@@ -37,6 +38,7 @@ export interface DocCollection<
> extends BaseCollection {
type: 'doc';
postprocess?: PostprocessOptions;
mdxOptions?: ProcessorOptions;
/**
+1 -1
View File
@@ -98,7 +98,7 @@ export function toWebpack(loader: Loader): WebpackLoader {
try {
const result = await loader({
filePath: this.resourcePath,
query: parse(this.resourceQuery),
query: parse(this.resourceQuery.slice(1)),
source,
development: this.mode === 'development',
compiler: this,
+8 -9
View File
@@ -10,6 +10,7 @@ import type { ConfigLoader } from '@/loaders/config-loader';
import fs from 'node:fs/promises';
import path from 'node:path';
import { createHash } from 'node:crypto';
import type { DocCollection } from '@/config';
const querySchema = z
.object({
@@ -62,22 +63,19 @@ export function createMdxLoader(configLoader: ConfigLoader): Loader {
? loaded.collections.get(parsed.collection)
: undefined;
let schema;
let mdxOptions;
let docCollection: DocCollection | undefined;
switch (collection?.type) {
case 'doc':
mdxOptions = collection.mdxOptions;
schema = collection.schema;
docCollection = collection;
break;
case 'docs':
mdxOptions = collection.docs.mdxOptions;
schema = collection.docs.schema;
docCollection = collection.docs;
break;
}
if (schema) {
if (docCollection?.schema) {
matter.data = await validate(
schema,
docCollection.schema,
matter.data,
{
source: value,
@@ -107,7 +105,8 @@ export function createMdxLoader(configLoader: ConfigLoader): Loader {
'\n'.repeat(lineOffset) + matter.content,
{
development: isDevelopment,
...(mdxOptions ?? (await loaded.getDefaultMDXOptions())),
...(docCollection?.mdxOptions ?? (await loaded.getDefaultMDXOptions())),
postprocess: docCollection?.postprocess,
data,
filePath,
frontmatter: matter.data as Record<string, unknown>,
@@ -0,0 +1,76 @@
import type { Processor, Transformer } from 'unified';
import type { Root, RootContent } from 'mdast';
import { valueToEstree } from 'estree-util-value-to-estree';
export interface RemarkMdxExportOptions {
/**
* Values to export from `vfile.data`
*/
dataNames?: string[];
}
declare module 'vfile' {
interface DataMap {
/**
* [Fumadocs MDX] additional ESM exports to write
*/
'mdx-export'?: { name: string; value: unknown }[];
}
}
export function remarkMdxExport(
this: Processor,
{ dataNames = [] }: RemarkMdxExportOptions = {},
): Transformer<Root, Root> {
return (tree, file) => {
for (const { name, value } of file.data['mdx-export'] ?? []) {
tree.children.unshift(getMdastExport(name, value));
}
for (const name of dataNames) {
if (!(name in file.data)) continue;
tree.children.unshift(getMdastExport(name, file.data[name]));
}
};
}
/**
* MDX.js first converts javascript (with esm support) into mdast nodes with remark-mdx, then handle the other remark plugins
*
* Therefore, if we want to inject an export, we must convert the object into AST, then add the mdast node
*/
function getMdastExport(name: string, value: unknown): RootContent {
return {
type: 'mdxjsEsm',
value: '',
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ExportNamedDeclaration',
attributes: [],
specifiers: [],
source: null,
declaration: {
type: 'VariableDeclaration',
kind: 'let',
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name,
},
init: valueToEstree(value),
},
],
},
},
],
},
},
};
}
@@ -1,11 +1,16 @@
import type { Transformer } from 'unified';
import type { Processor, Transformer } from 'unified';
import type { Root, RootContent } from 'mdast';
import { visit } from 'unist-util-visit';
import { valueToEstree } from 'estree-util-value-to-estree';
import { toMarkdown } from 'mdast-util-to-markdown';
declare module 'vfile' {
interface DataMap {
extractedReferences: ExtractedReference[];
/**
* [Fumadocs MDX] Processed Markdown content before `remark-rehype`.
*/
_markdown?: string;
}
}
@@ -15,19 +20,19 @@ export interface ExtractedReference {
export interface PostprocessOptions {
/**
* Values to export from `vfile.data`
* stringify MDAST and export via `_markdown`.
*/
injectExports: string[];
includeProcessedMarkdown?: boolean;
}
/**
* - write exports
* - collect references
* - write frontmatter (auto-title & description)
*/
export function remarkPostprocess({
injectExports,
}: PostprocessOptions): Transformer<Root, Root> {
export function remarkPostprocess(
this: Processor,
{ includeProcessedMarkdown = false }: PostprocessOptions = {},
): Transformer<Root, Root> {
return (tree, file) => {
let title: string | undefined;
const urls: ExtractedReference[] = [];
@@ -53,11 +58,12 @@ export function remarkPostprocess({
}
file.data.extractedReferences = urls;
for (const name of injectExports) {
if (!(name in file.data)) continue;
tree.children.unshift(getMdastExport(name, file.data[name]));
if (includeProcessedMarkdown) {
file.data._markdown = toMarkdown(tree, {
...this.data('settings'),
// @ts-expect-error - from https://github.com/remarkjs/remark/blob/main/packages/remark-stringify/lib/index.js
extensions: this.data('toMarkdownExtensions') || [],
});
}
};
}
@@ -70,43 +76,3 @@ function flattenNode(node: RootContent): string {
return '';
}
/**
* MDX.js first converts javascript (with esm support) into mdast nodes with remark-mdx, then handle the other remark plugins
*
* Therefore, if we want to inject an export, we must convert the object into AST, then add the mdast node
*/
function getMdastExport(name: string, value: unknown): RootContent {
return {
type: 'mdxjsEsm',
value: '',
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ExportNamedDeclaration',
attributes: [],
specifiers: [],
source: null,
declaration: {
type: 'VariableDeclaration',
kind: 'let',
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name,
},
init: valueToEstree(value),
},
],
},
},
],
},
},
};
}
+55 -28
View File
@@ -1,57 +1,84 @@
import type { LoadedConfig } from '@/utils/config';
import { type StructuredData } from 'fumadocs-core/mdx-plugins';
import { _runtime, createMDXSource } from './index';
import type { RuntimeAsync } from './types';
import { buildMDX } from '@/utils/build-mdx';
import type { AsyncDocOut, RuntimeAsync } from './types';
import { buildMDX, CompiledMDXProperties } from '@/utils/build-mdx';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { executeMdx } from '@fumadocs/mdx-remote/client';
import { pathToFileURL } from 'node:url';
import { type DocData, missingProcessedMarkdown } from '@/runtime/shared';
import type { DocCollection } from '@/config';
function getDocCollection(config: LoadedConfig, collection: string) {
const col = config.collections.get(collection);
if (col?.type === 'doc' && col.mdxOptions) return col;
if (col?.type === 'docs' && col.docs.mdxOptions) return col.docs;
}
async function getOptions(
config: LoadedConfig,
collection: string,
collection?: DocCollection,
): Promise<ProcessorOptions> {
const col = config.collections.get(collection);
if (col?.type === 'doc' && col.mdxOptions) return col.mdxOptions;
if (col?.type === 'docs' && col.docs.mdxOptions) return col.docs.mdxOptions;
return config.getDefaultMDXOptions('remote');
return (
collection?.mdxOptions ?? (await config.getDefaultMDXOptions('remote'))
);
}
export const _runtimeAsync: RuntimeAsync = {
doc(files, collection, config) {
doc(files, collectionName, config) {
const collection = getDocCollection(config, collectionName);
const initMdxOptions = getOptions(config, collection);
return files.map(({ info: file, data, content, lastModified }) => {
let cachedResult: CompiledMDXProperties | undefined;
async function compileAndLoad() {
if (cachedResult) return cachedResult;
const mdxOptions = await initMdxOptions;
const compiled = await buildMDX(collectionName, content.body, {
...mdxOptions,
development: false,
frontmatter: data,
postprocess: collection?.postprocess,
data: {
lastModified,
},
filePath: file.absolutePath,
});
const result = await executeMdx(String(compiled.value), {
baseUrl: pathToFileURL(file.absolutePath),
});
return (cachedResult = result as CompiledMDXProperties);
}
return {
...data,
_file: file,
get content() {
return `${content.matter}${content.body}`;
},
async getText(type) {
if (type === 'raw') {
return `${content.matter}${content.body}`;
}
const out = await compileAndLoad();
if (typeof out._markdown !== 'string') missingProcessedMarkdown();
return out._markdown;
},
async load() {
const mdxOptions = await initMdxOptions;
const out = await buildMDX(collection, content.body, {
...mdxOptions,
development: false,
frontmatter: data as Record<string, unknown>,
data: {
lastModified,
},
filePath: file.absolutePath,
});
const { default: body, ...rest } = await executeMdx(String(out), {
baseUrl: pathToFileURL(file.absolutePath),
});
const out = await compileAndLoad();
return {
structuredData: out.data.structuredData as StructuredData,
body,
...rest,
_exports: out as unknown as Record<string, unknown>,
body: out.default,
lastModified,
};
toc: out.toc,
extractedReferences: out.extractedReferences,
structuredData: out.structuredData,
} satisfies DocData;
},
};
} satisfies AsyncDocOut;
}) as any;
},
docs(docs, metas, collection, config) {
+33 -12
View File
@@ -4,32 +4,51 @@ import {
type PageData,
type Source,
} from 'fumadocs-core/source';
import type { BaseCollectionEntry, FileInfo, Runtime } from './types';
import fs from 'node:fs';
import type { DocOut, FileInfo, Runtime } from './types';
import { readFileSync } from 'node:fs';
import type { CompiledMDXProperties } from '@/utils/build-mdx';
import { readFile } from 'node:fs/promises';
import { missingProcessedMarkdown } from '@/runtime/shared';
export interface BaseCollectionEntry {
_file: FileInfo;
}
const cache = new Map<string, string>();
export const _runtime: Runtime = {
doc(files) {
return files.map((file) => {
const { default: body, frontmatter, ...exports } = file.data;
const data = file.data as unknown as CompiledMDXProperties;
const filePath = file.info.absolutePath;
return {
body,
...exports,
...(frontmatter as object),
_file: file.info,
_exports: file.data,
_exports: data as unknown as Record<string, unknown>,
body: data.default,
lastModified: data.lastModified,
toc: data.toc,
structuredData: data.structuredData,
extractedReferences: data.extractedReferences,
...data.frontmatter,
get content() {
const path = (this as { _file: FileInfo })._file.absolutePath;
const cached = cache.get(path);
const cached = cache.get(filePath);
if (cached) return cached;
const content = fs.readFileSync(path).toString();
cache.set(path, content);
const content = readFileSync(filePath).toString();
cache.set(filePath, content);
return content;
},
};
async getText(type) {
console.log(data);
if (type === 'raw') {
return (await readFile(filePath)).toString();
}
if (typeof data._markdown !== 'string') missingProcessedMarkdown();
return data._markdown;
},
} satisfies DocOut;
}) as any;
},
meta(files) {
@@ -103,3 +122,5 @@ export function resolveFiles({ docs, meta }: ResolveOptions): VirtualFile[] {
return outputs;
}
export type * from './types';
+16 -37
View File
@@ -2,25 +2,16 @@ import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { MetaData, PageData, Source } from 'fumadocs-core/source';
import type { LoadedConfig } from '@/utils/config';
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
import type { CompiledMDXProperties } from '@/utils/build-mdx';
export interface BaseCollectionEntry {
/**
* Raw file path of collection entry, including absolute path (not normalized).
*/
_file: FileInfo;
}
import type {
AsyncDocCollectionEntry,
DocCollectionEntry,
} from '@/runtime/shared';
export interface FileInfo {
path: string;
absolutePath: string;
}
export interface MarkdownProps
extends Omit<CompiledMDXProperties, 'frontmatter' | 'default'> {
body: CompiledMDXProperties['default'];
}
export interface RuntimeFile {
info: FileInfo;
data: Record<string, unknown>;
@@ -33,25 +24,15 @@ export interface AsyncRuntimeFile {
lastModified?: Date;
}
type DocOut<Schema extends StandardSchemaV1> = Override<
MarkdownProps & {
/**
* Other exports in the compiled Markdown/MDX file
*/
_exports: Record<string, unknown>;
export type DocOut<Schema extends StandardSchemaV1 = StandardSchemaV1> =
DocCollectionEntry<StandardSchemaV1.InferOutput<Schema>> & {
_file: FileInfo;
};
/**
* Read the original content of file from file system.
*/
get content(): string;
},
StandardSchemaV1.InferOutput<Schema> & BaseCollectionEntry
>;
type Override<A, B> = Omit<A, keyof B> & B;
type MetaOut<Schema extends StandardSchemaV1> =
StandardSchemaV1.InferOutput<Schema> & BaseCollectionEntry;
export type MetaOut<Schema extends StandardSchemaV1> =
StandardSchemaV1.InferOutput<Schema> & {
_file: FileInfo;
};
export interface Runtime {
doc: <C>(
@@ -80,12 +61,10 @@ export interface Runtime {
: never;
}
type AsyncDocOut<Schema extends StandardSchemaV1> =
StandardSchemaV1.InferOutput<Schema> &
BaseCollectionEntry & {
content: string;
load: () => Promise<MarkdownProps & Record<string, unknown>>;
};
export type AsyncDocOut<Schema extends StandardSchemaV1 = StandardSchemaV1> =
AsyncDocCollectionEntry<StandardSchemaV1.InferOutput<Schema>> & {
_file: FileInfo;
};
export interface RuntimeAsync {
doc: <C>(
+76
View File
@@ -0,0 +1,76 @@
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
import type { TableOfContents } from 'fumadocs-core/server';
import type { FC } from 'react';
import type { MDXProps } from 'mdx/types';
import type { ExtractedReference } from '@/mdx-plugins/remark-postprocess';
export interface DocData {
/**
* Compiled MDX content (as component)
*/
body: FC<MDXProps>;
/**
* table of contents generated from content.
*/
toc: TableOfContents;
/**
* structured data for document search indexing.
*/
structuredData: StructuredData;
/**
* Raw exports from the compiled MDX file.
*/
_exports: Record<string, unknown>;
/**
* Last modified date of document file, obtained from version control.
*
* Only available when `lastModifiedTime` is enabled on global config.
*/
lastModified?: Date;
/**
* extracted references (e.g. hrefs, paths), useful for analyzing relationships between pages.
*/
extractedReferences?: ExtractedReference[];
}
export interface DocMethods {
/**
* Read the original content of file from file system.
*
* @deprecated use `await getText('raw')` instead.
*/
get content(): string;
/**
* get document as text.
*
* - `type: raw` - read the original content from file system.
* - `type: processed` - get the processed Markdown content, only available when `includeProcessedMarkdown` is enabled on collection config.
*/
getText: (type: 'raw' | 'processed') => Promise<string>;
}
export type DocCollectionEntry<Frontmatter> = Override<
DocData & DocMethods,
Frontmatter
>;
export type AsyncDocCollectionEntry<Frontmatter> = Override<
DocMethods & {
load: () => Promise<DocData>;
},
Frontmatter
>;
type Override<A, B> = Omit<A, keyof B> & B;
export function missingProcessedMarkdown(): never {
throw new Error(
"getText('processed') requires `includeProcessedMarkdown` to be enabled in your collection config.",
);
}
+63 -47
View File
@@ -5,11 +5,15 @@ import type {
Source,
VirtualFile,
} from 'fumadocs-core/source';
import type { CompiledMDXProperties } from '@/utils/build-mdx';
import type { FC } from 'react';
import type { MDXProps } from 'mdx/types';
import { type BaseCreate, fromConfigBase } from '@/runtime/vite/base';
import * as path from 'node:path';
import {
AsyncDocCollectionEntry,
DocCollectionEntry,
DocData,
missingProcessedMarkdown,
} from '@/runtime/shared';
import fs from 'node:fs/promises';
// for server-side usage of renderers
export { createClientLoader, toClientRenderer } from './browser';
@@ -17,39 +21,13 @@ export type { ClientLoader, ClientLoaderOptions } from './browser';
export type { CompiledMDXFile } from './types';
type Override<A, B> = Omit<A, keyof B> & B;
type MDXFileToPageData<Frontmatter> = Override<
Omit<CompiledMDXProperties<Frontmatter>, 'frontmatter' | 'default'>,
Frontmatter & {
/**
* @deprecated use `body` instead.
*/
default: FC<MDXProps>;
_exports: Record<string, unknown>;
body: FC<MDXProps>;
}
>;
type MDXFileToPageDataLazy<Frontmatter> = Override<
Frontmatter,
{
load: () => Promise<
Omit<CompiledMDXFile<Frontmatter>, 'default'> & {
body: FC<MDXProps>;
}
>;
}
>;
export interface ServerCreate<Config> extends BaseCreate<Config> {
sourceAsync: <DocOut extends PageData, MetaOut extends MetaData>(
doc: DocMap<DocOut>,
meta: MetaMap<MetaOut>,
) => Promise<
Source<{
pageData: MDXFileToPageData<DocOut>;
pageData: DocCollectionEntry<DocOut>;
metaData: MetaOut;
}>
>;
@@ -59,7 +37,7 @@ export interface ServerCreate<Config> extends BaseCreate<Config> {
meta: MetaMap<MetaOut>,
) => Promise<
Source<{
pageData: MDXFileToPageDataLazy<DocOut>;
pageData: AsyncDocCollectionEntry<DocOut>;
metaData: MetaOut;
}>
>;
@@ -67,29 +45,59 @@ export interface ServerCreate<Config> extends BaseCreate<Config> {
export function fromConfig<Config>(): ServerCreate<Config> {
const base = fromConfigBase<Config>();
function mapPageData<Frontmatter>(entry: CompiledMDXFile<Frontmatter>) {
const { toc, structuredData, lastModified, frontmatter } = entry;
function mapDocData(entry: CompiledMDXFile<any>): DocData {
return {
...frontmatter,
default: entry.default,
body: entry.default,
toc,
structuredData,
lastModified,
toc: entry.toc,
extractedReferences: entry.extractedReferences,
structuredData: entry.structuredData,
lastModified: entry.lastModified,
_exports: entry,
} as MDXFileToPageData<Frontmatter>;
};
}
function mapPageData<Frontmatter>(
filePath: string,
entry: CompiledMDXFile<Frontmatter>,
): DocCollectionEntry<Frontmatter> {
return {
...mapDocData(entry),
get content(): string {
throw new Error('not implemented on Vite.');
},
async getText(type) {
if (type === 'raw') {
return (await fs.readFile(filePath)).toString();
}
if (typeof entry._markdown !== 'string') missingProcessedMarkdown();
return entry._markdown;
},
...entry.frontmatter,
};
}
function mapPageDataLazy<Frontmatter>(
filePath: string,
head: Frontmatter,
content: () => Promise<CompiledMDXFile<Frontmatter>>,
): MDXFileToPageDataLazy<Frontmatter> {
): AsyncDocCollectionEntry<Frontmatter> {
return {
...head,
async load() {
const { default: body, ...rest } = await content();
return { body, ...rest };
return mapDocData(await content());
},
get content(): string {
throw new Error('not implemented on Vite.');
},
async getText(type) {
if (type === 'raw') {
return (await fs.readFile(filePath)).toString();
}
const entry = await content();
if (typeof entry._markdown !== 'string') missingProcessedMarkdown();
return entry._markdown;
},
};
}
@@ -100,11 +108,13 @@ export function fromConfig<Config>(): ServerCreate<Config> {
async sourceAsync(doc, meta) {
const virtualFiles: Promise<VirtualFile>[] = [
...Object.entries(doc).map(async ([file, content]) => {
const fullPath = path.join(content.base, file);
return {
type: 'page',
path: file,
absolutePath: path.join(content.base, file),
data: mapPageData(await content()),
absolutePath: fullPath,
data: mapPageData(fullPath, await content()),
} satisfies VirtualFile;
}),
...Object.entries(meta).map(async ([file, content]) => {
@@ -122,11 +132,17 @@ export function fromConfig<Config>(): ServerCreate<Config> {
async sourceLazy(doc, meta) {
const virtualFiles: Promise<VirtualFile>[] = [
...Object.entries(doc.head).map(async ([file, frontmatter]) => {
const fullPath = path.join(doc.base, file);
return {
type: 'page',
path: file,
absolutePath: path.join(doc.base, file),
data: mapPageDataLazy(await frontmatter(), doc.body[file]),
absolutePath: fullPath,
data: mapPageDataLazy(
fullPath,
await frontmatter(),
doc.body[file],
),
} satisfies VirtualFile;
}),
...Object.entries(meta).map(async ([file, content]) => {
+25 -2
View File
@@ -5,7 +5,12 @@ import type { StructuredData } from 'fumadocs-core/mdx-plugins';
import type { TableOfContents } from 'fumadocs-core/server';
import type { FC } from 'react';
import type { MDXProps } from 'mdx/types';
import type { ExtractedReference } from '@/mdx-plugins/remark-postprocess';
import {
type ExtractedReference,
type PostprocessOptions,
remarkPostprocess,
} from '@/mdx-plugins/remark-postprocess';
import { remarkMdxExport } from '@/mdx-plugins/remark-mdx-export';
type Processor = ReturnType<typeof createProcessor>;
@@ -25,6 +30,7 @@ interface BuildMDXOptions extends ProcessorOptions {
data?: Record<string, unknown>;
_compiler?: CompilerOptions;
postprocess?: PostprocessOptions;
}
export interface CompilerOptions {
@@ -42,6 +48,7 @@ export interface CompiledMDXProperties<Frontmatter = Record<string, unknown>> {
*/
lastModified?: Date;
extractedReferences?: ExtractedReference[];
_markdown?: string;
}
export type { DataMap };
@@ -82,7 +89,23 @@ export async function buildMDX(
processor = createProcessor({
outputFormat: 'program',
...rest,
remarkPlugins: [remarkInclude, ...(rest.remarkPlugins ?? [])],
remarkPlugins: [
remarkInclude,
...(rest.remarkPlugins ?? []),
[remarkPostprocess, options.postprocess],
[
remarkMdxExport,
{
dataNames: [
'structuredData',
'extractedReferences',
'frontmatter',
'lastModified',
'_markdown',
],
},
],
],
format,
});
+10 -16
View File
@@ -1,10 +1,6 @@
import * as plugins from 'fumadocs-core/mdx-plugins';
import type { ProcessorOptions } from '@mdx-js/mdx';
import type { Pluggable } from 'unified';
import {
type PostprocessOptions,
remarkPostprocess,
} from '@/mdx-plugins/remark-postprocess';
type ResolvePlugins = Pluggable[] | ((v: Pluggable[]) => Pluggable[]);
@@ -56,14 +52,6 @@ export function getDefaultMDXOptions({
_withoutBundler = false,
...mdxOptions
}: DefaultMDXOptions): ProcessorOptions {
const mdxExports = [
'structuredData',
'extractedReferences',
'frontmatter',
'lastModified',
...valueToExport,
];
const remarkPlugins = pluginOption(
(v) => [
plugins.remarkGfm,
@@ -93,10 +81,16 @@ export function getDefaultMDXOptions({
plugins.remarkStructure,
remarkStructureOptions,
],
[
remarkPostprocess,
{ injectExports: mdxExports } satisfies PostprocessOptions,
],
() => {
return (_, file) => {
file.data['mdx-export'] ??= [];
for (const name of valueToExport) {
if (name in file.data)
file.data['mdx-export'].push({ name, value: file.data[name] });
}
};
},
],
mdxOptions.remarkPlugins,
);