MDX: remove deprecated APIs

This commit is contained in:
Fuma Nama
2025-09-20 15:43:34 +08:00
parent 2862a10c2d
commit f11f89d1a9
21 changed files with 192 additions and 210 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': major
---
[Next.js] Rename `_file` prop on page data to `info`, and replace `_file.absolutePath` with `info.fullPath`.
+4 -2
View File
@@ -1,5 +1,7 @@
---
'fumadocs-mdx': minor
'fumadocs-mdx': major
---
Support `getText()` API & `postprocess.includeProcessedMarkdown` option, deprecate `content` in favour of `getText()`.
**Support `getText()` API & `postprocess.includeProcessedMarkdown` option.**
[Next.js] removed `content` on page data in favour of `getText()`.
+1 -1
View File
@@ -61,7 +61,7 @@ function toFile(page: InferPageType<typeof source>): FileObject {
return {
data: page.data,
url: page.url,
path: page.absolutePath,
path: page.data.info.fullPath,
content: page.data.content,
};
}
+1 -1
View File
@@ -49,7 +49,7 @@ export function buildConfig(config: Record<string, unknown>): LoadedConfig {
const input = this.global.mdxOptions;
async function uncached(): Promise<ProcessorOptions> {
const options = typeof input === 'function' ? await input() : input;
const { getDefaultMDXOptions } = await import('@/utils/mdx-options');
const { getDefaultMDXOptions } = await import('@/mdx/preset');
if (options?.preset === 'minimal') return options;
return getDefaultMDXOptions({
+2 -2
View File
@@ -1,8 +1,8 @@
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { DefaultMDXOptions } from '@/utils/mdx-options';
import type { DefaultMDXOptions } from '@/mdx/preset';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { frontmatterSchema, metaSchema } from '@/config/zod-4';
import type { PostprocessOptions } from '@/mdx-plugins/remark-postprocess';
import type { PostprocessOptions } from '@/mdx/remark-postprocess';
export type CollectionSchema<Schema extends StandardSchemaV1, Context> =
| Schema
+2 -5
View File
@@ -1,7 +1,4 @@
export * from './define';
export {
getDefaultMDXOptions,
type DefaultMDXOptions,
} from '../utils/mdx-options';
export { getDefaultMDXOptions, type DefaultMDXOptions } from '../mdx/preset';
export * from './zod-4';
export * from '../mdx-plugins/remark-include';
export * from '@/mdx/remark-include';
+2 -5
View File
@@ -23,8 +23,5 @@ export const frontmatterSchema = z.object({
});
export * from './define';
export {
getDefaultMDXOptions,
type DefaultMDXOptions,
} from '../utils/mdx-options';
export * from '../mdx-plugins/remark-include';
export { getDefaultMDXOptions, type DefaultMDXOptions } from '../mdx/preset';
export * from '@/mdx/remark-include';
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CompilerOptions } from '@/utils/build-mdx';
import type { CompilerOptions } from '@/mdx/build-mdx';
import type { LoadFnOutput, LoadHook } from 'node:module';
import { fileURLToPath } from 'node:url';
import fs from 'node:fs/promises';
+1 -1
View File
@@ -2,7 +2,7 @@ import { fumaMatter } from '@/utils/fuma-matter';
import { validate } from '@/utils/validation';
import { getGitTimestamp } from '@/utils/git-timestamp';
import { countLines } from '@/utils/count-lines';
import { buildMDX } from '@/utils/build-mdx';
import { buildMDX } from '@/mdx/build-mdx';
import type { SourceMap } from 'rollup';
import type { Loader } from '@/loaders/adapter';
import { z } from 'zod';
@@ -1,76 +0,0 @@
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,6 +1,6 @@
import { createProcessor, type ProcessorOptions } from '@mdx-js/mdx';
import type { DataMap, VFile } from 'vfile';
import { remarkInclude } from '@/mdx-plugins/remark-include';
import type { VFile } from 'vfile';
import { remarkInclude } from '@/mdx/remark-include';
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
import type { TableOfContents } from 'fumadocs-core/server';
import type { FC } from 'react';
@@ -9,8 +9,7 @@ import {
type ExtractedReference,
type PostprocessOptions,
remarkPostprocess,
} from '@/mdx-plugins/remark-postprocess';
import { remarkMdxExport } from '@/mdx-plugins/remark-mdx-export';
} from '@/mdx/remark-postprocess';
type Processor = ReturnType<typeof createProcessor>;
@@ -51,22 +50,35 @@ export interface CompiledMDXProperties<Frontmatter = Record<string, unknown>> {
_markdown?: string;
}
export type { DataMap };
export interface FumadocsDataMap {
/**
* [Fumadocs MDX] raw frontmatter, you can modify it
*/
frontmatter?: Record<string, unknown>;
/**
* [Fumadocs MDX] additional ESM exports to write
*/
'mdx-export'?: { name: string; value: unknown }[];
extractedReferences: ExtractedReference[];
/**
* [Fumadocs MDX] The compiler object from loader
*/
_compiler?: CompilerOptions;
_getProcessor?: (format: 'md' | 'mdx') => Processor;
/**
* [Fumadocs MDX] Processed Markdown content before `remark-rehype`.
*/
_markdown?: string;
}
declare module 'vfile' {
interface DataMap {
/**
* [Fumadocs MDX] raw frontmatter, you can modify it
*/
frontmatter?: Record<string, unknown>;
/**
* [Fumadocs MDX] The compiler object from loader
*/
_compiler?: CompilerOptions;
_getProcessor?: (format: 'md' | 'mdx') => Processor;
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- extend data map
interface DataMap extends FumadocsDataMap {}
}
/**
@@ -92,18 +104,19 @@ export async function buildMDX(
remarkPlugins: [
remarkInclude,
...(rest.remarkPlugins ?? []),
[remarkPostprocess, options.postprocess],
[
remarkMdxExport,
remarkPostprocess,
{
dataNames: [
...options.postprocess,
valueToExport: [
...(options.postprocess?.valueToExport ?? []),
'structuredData',
'extractedReferences',
'frontmatter',
'lastModified',
'_markdown',
],
},
} satisfies PostprocessOptions,
],
],
format,
@@ -4,11 +4,11 @@ import type { Code, 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 { DataMap } from '@/utils/build-mdx';
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx';
import remarkParse from 'remark-parse';
import remarkMdx from 'remark-mdx';
import { remarkHeading } from 'fumadocs-core/mdx-plugins';
import type { DataMap } from 'vfile';
export interface Params {
lang?: string;
@@ -2,23 +2,18 @@ import type { Processor, Transformer } from 'unified';
import type { Root, RootContent } from 'mdast';
import { visit } from 'unist-util-visit';
import { toMarkdown } from 'mdast-util-to-markdown';
declare module 'vfile' {
interface DataMap {
extractedReferences: ExtractedReference[];
/**
* [Fumadocs MDX] Processed Markdown content before `remark-rehype`.
*/
_markdown?: string;
}
}
import { valueToEstree } from 'estree-util-value-to-estree';
export interface ExtractedReference {
href: string;
}
export interface PostprocessOptions {
/**
* Properties to export from `vfile.data`
*/
valueToExport?: string[];
/**
* stringify MDAST and export via `_markdown`.
*/
@@ -31,7 +26,10 @@ export interface PostprocessOptions {
*/
export function remarkPostprocess(
this: Processor,
{ includeProcessedMarkdown = false }: PostprocessOptions = {},
{
includeProcessedMarkdown = false,
valueToExport = [],
}: PostprocessOptions = {},
): Transformer<Root, Root> {
return (tree, file) => {
let title: string | undefined;
@@ -58,6 +56,7 @@ export function remarkPostprocess(
}
file.data.extractedReferences = urls;
if (includeProcessedMarkdown) {
file.data._markdown = toMarkdown(tree, {
...this.data('settings'),
@@ -65,6 +64,56 @@ export function remarkPostprocess(
extensions: this.data('toMarkdownExtensions') || [],
});
}
for (const { name, value } of file.data['mdx-export'] ?? []) {
tree.children.unshift(getMdastExport(name, value));
}
for (const name of valueToExport) {
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),
},
],
},
},
],
},
},
};
}
+5 -8
View File
@@ -1,7 +1,7 @@
import type { LoadedConfig } from '@/utils/config';
import { _runtime, createMDXSource } from './index';
import type { AsyncDocOut, RuntimeAsync } from './types';
import { buildMDX, CompiledMDXProperties } from '@/utils/build-mdx';
import { buildMDX, CompiledMDXProperties } from '@/mdx/build-mdx';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { executeMdx } from '@fumadocs/mdx-remote/client';
import { pathToFileURL } from 'node:url';
@@ -28,7 +28,7 @@ export const _runtimeAsync: RuntimeAsync = {
const collection = getDocCollection(config, collectionName);
const initMdxOptions = getOptions(config, collection);
return files.map(({ info: file, data, content, lastModified }) => {
return files.map(({ info, data, content, lastModified }) => {
let cachedResult: CompiledMDXProperties | undefined;
async function compileAndLoad() {
@@ -42,10 +42,10 @@ export const _runtimeAsync: RuntimeAsync = {
data: {
lastModified,
},
filePath: file.absolutePath,
filePath: info.fullPath,
});
const result = await executeMdx(String(compiled.value), {
baseUrl: pathToFileURL(file.absolutePath),
baseUrl: pathToFileURL(info.fullPath),
});
return (cachedResult = result as CompiledMDXProperties);
@@ -53,10 +53,7 @@ export const _runtimeAsync: RuntimeAsync = {
return {
...data,
_file: file,
get content() {
return `${content.matter}${content.body}`;
},
info,
async getText(type) {
if (type === 'raw') {
return `${content.matter}${content.body}`;
+19 -31
View File
@@ -4,26 +4,19 @@ import {
type PageData,
type Source,
} from 'fumadocs-core/source';
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>();
import type { DocOut, Runtime } from './types';
import type { CompiledMDXProperties } from '@/mdx/build-mdx';
import * as fs from 'node:fs/promises';
import { type FileInfo, missingProcessedMarkdown } from '@/runtime/shared';
export const _runtime: Runtime = {
doc(files) {
return files.map((file) => {
const data = file.data as unknown as CompiledMDXProperties;
const filePath = file.info.absolutePath;
const filePath = file.info.fullPath;
return {
_file: file.info,
info: file.info,
_exports: data as unknown as Record<string, unknown>,
body: data.default,
lastModified: data.lastModified,
@@ -31,18 +24,9 @@ export const _runtime: Runtime = {
structuredData: data.structuredData,
extractedReferences: data.extractedReferences,
...data.frontmatter,
get content() {
const cached = cache.get(filePath);
if (cached) return cached;
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();
return (await fs.readFile(filePath)).toString();
}
if (typeof data._markdown !== 'string') missingProcessedMarkdown();
@@ -73,9 +57,13 @@ export const _runtime: Runtime = {
},
};
export interface AnyCollectionEntry {
info: FileInfo;
}
export function createMDXSource<
Doc extends PageData & BaseCollectionEntry,
Meta extends MetaData & BaseCollectionEntry,
Doc extends PageData & AnyCollectionEntry,
Meta extends MetaData & AnyCollectionEntry,
>(
docs: Doc[],
meta: Meta[] = [],
@@ -93,8 +81,8 @@ export function createMDXSource<
}
interface ResolveOptions {
docs: BaseCollectionEntry[];
meta: BaseCollectionEntry[];
docs: AnyCollectionEntry[];
meta: AnyCollectionEntry[];
rootDir?: string;
}
@@ -105,8 +93,8 @@ export function resolveFiles({ docs, meta }: ResolveOptions): VirtualFile[] {
for (const entry of docs) {
outputs.push({
type: 'page',
absolutePath: entry._file.absolutePath,
path: entry._file.path,
absolutePath: entry.info.fullPath,
path: entry.info.path,
data: entry,
});
}
@@ -114,8 +102,8 @@ export function resolveFiles({ docs, meta }: ResolveOptions): VirtualFile[] {
for (const entry of meta) {
outputs.push({
type: 'meta',
absolutePath: entry._file.absolutePath,
path: entry._file.path,
absolutePath: entry.info.fullPath,
path: entry.info.path,
data: entry,
});
}
+7 -15
View File
@@ -5,13 +5,10 @@ import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
import type {
AsyncDocCollectionEntry,
DocCollectionEntry,
FileInfo,
MetaCollectionEntry,
} from '@/runtime/shared';
export interface FileInfo {
path: string;
absolutePath: string;
}
export interface RuntimeFile {
info: FileInfo;
data: Record<string, unknown>;
@@ -25,14 +22,11 @@ export interface AsyncRuntimeFile {
}
export type DocOut<Schema extends StandardSchemaV1 = StandardSchemaV1> =
DocCollectionEntry<StandardSchemaV1.InferOutput<Schema>> & {
_file: FileInfo;
};
DocCollectionEntry<StandardSchemaV1.InferOutput<Schema>>;
export type MetaOut<Schema extends StandardSchemaV1> =
StandardSchemaV1.InferOutput<Schema> & {
_file: FileInfo;
};
export type MetaOut<Schema extends StandardSchemaV1> = MetaCollectionEntry<
StandardSchemaV1.InferOutput<Schema>
>;
export interface Runtime {
doc: <C>(
@@ -62,9 +56,7 @@ export interface Runtime {
}
export type AsyncDocOut<Schema extends StandardSchemaV1 = StandardSchemaV1> =
AsyncDocCollectionEntry<StandardSchemaV1.InferOutput<Schema>> & {
_file: FileInfo;
};
AsyncDocCollectionEntry<StandardSchemaV1.InferOutput<Schema>>;
export interface RuntimeAsync {
doc: <C>(
+22 -5
View File
@@ -2,7 +2,19 @@ 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 } from '@/mdx/remark-postprocess';
export interface FileInfo {
/**
* virtualized path for Source API
*/
path: string;
/**
* the file path in file system
*/
fullPath: string;
}
export interface DocData {
/**
@@ -40,11 +52,9 @@ export interface DocData {
export interface DocMethods {
/**
* Read the original content of file from file system.
*
* @deprecated use `await getText('raw')` instead.
* file info
*/
get content(): string;
info: FileInfo;
/**
* get document as text.
@@ -55,6 +65,13 @@ export interface DocMethods {
getText: (type: 'raw' | 'processed') => Promise<string>;
}
export type MetaCollectionEntry<Data> = Data & {
/**
* file info
*/
info: FileInfo;
};
export type DocCollectionEntry<Frontmatter> = Override<
DocData & DocMethods,
Frontmatter
+19 -20
View File
@@ -11,6 +11,7 @@ import {
AsyncDocCollectionEntry,
DocCollectionEntry,
DocData,
FileInfo,
missingProcessedMarkdown,
} from '@/runtime/shared';
import fs from 'node:fs/promises';
@@ -57,17 +58,15 @@ export function fromConfig<Config>(): ServerCreate<Config> {
}
function mapPageData<Frontmatter>(
filePath: string,
info: FileInfo,
entry: CompiledMDXFile<Frontmatter>,
): DocCollectionEntry<Frontmatter> {
return {
...mapDocData(entry),
get content(): string {
throw new Error('not implemented on Vite.');
},
info,
async getText(type) {
if (type === 'raw') {
return (await fs.readFile(filePath)).toString();
return (await fs.readFile(info.fullPath)).toString();
}
if (typeof entry._markdown !== 'string') missingProcessedMarkdown();
@@ -78,21 +77,19 @@ export function fromConfig<Config>(): ServerCreate<Config> {
}
function mapPageDataLazy<Frontmatter>(
filePath: string,
info: FileInfo,
head: Frontmatter,
content: () => Promise<CompiledMDXFile<Frontmatter>>,
): AsyncDocCollectionEntry<Frontmatter> {
return {
...head,
info,
async load() {
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();
return (await fs.readFile(info.fullPath)).toString();
}
const entry = await content();
@@ -108,13 +105,16 @@ 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);
const info: FileInfo = {
path: file,
fullPath: path.join(content.base, file),
};
return {
type: 'page',
path: file,
absolutePath: fullPath,
data: mapPageData(fullPath, await content()),
absolutePath: info.fullPath,
data: mapPageData(info, await content()),
} satisfies VirtualFile;
}),
...Object.entries(meta).map(async ([file, content]) => {
@@ -132,17 +132,16 @@ 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);
const info: FileInfo = {
path: file,
fullPath: path.join(doc.base, file),
};
return {
type: 'page',
path: file,
absolutePath: fullPath,
data: mapPageDataLazy(
fullPath,
await frontmatter(),
doc.body[file],
),
absolutePath: info.fullPath,
data: mapPageDataLazy(info, await frontmatter(), doc.body[file]),
} satisfies VirtualFile;
}),
...Object.entries(meta).map(async ([file, content]) => {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CompiledMDXProperties } from '@/utils/build-mdx';
import type { CompiledMDXProperties } from '@/mdx/build-mdx';
export type CompiledMDXFile<Frontmatter> = CompiledMDXProperties<Frontmatter> &
Record<string, unknown>;
+3 -1
View File
@@ -1549,6 +1549,9 @@ importers:
lru-cache:
specifier: ^11.2.1
version: 11.2.1
mdast-util-to-markdown:
specifier: ^2.1.2
version: 2.1.2
picocolors:
specifier: ^1.1.1
version: 1.1.1
@@ -5759,7 +5762,6 @@ packages:
[email protected]:
resolution: {integrity: sha512-y0lJ02dS90U3PJm+7KAKY8Se95AQvP5Xm77LouUwrpNOHpv59kBG4SK1+9iE1cAhpUaFipq+0EJ56S6MmE3row==}
cpu: [arm64, x64, aarch64]
os: [darwin, linux, win32]
hasBin: true