MDX: Automatic fallback to Zod v3 when app has explicit v3 dependency

This commit is contained in:
Fuma Nama
2025-08-12 01:00:42 +08:00
parent 054dcc597a
commit f43f7142f7
13 changed files with 103 additions and 27 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': patch
---
Automatic fallback to Zod v3 when app has explicit v3 dependency
+5
View File
@@ -18,6 +18,11 @@
"types": "./dist/config/index.d.ts",
"require": "./dist/config/index.cjs"
},
"./config/zod-3": {
"import": "./dist/config/zod-3.js",
"types": "./dist/config/zod-3.d.ts",
"require": "./dist/config/zod-3.cjs"
},
"./next": {
"import": "./dist/next/index.js",
"types": "./dist/next/index.d.ts",
+1 -1
View File
@@ -1,7 +1,7 @@
import { frontmatterSchema, metaSchema } from '@/utils/schema';
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';
export type CollectionSchema<Schema extends StandardSchemaV1, Context> =
| Schema
+1 -1
View File
@@ -3,5 +3,5 @@ export {
getDefaultMDXOptions,
type DefaultMDXOptions,
} from '../utils/mdx-options';
export { frontmatterSchema, metaSchema } from '../utils/schema';
export * from './zod-4';
export * from '../mdx-plugins/remark-include';
+30
View File
@@ -0,0 +1,30 @@
// for backward compatibility
// while Fumadocs expose Zod v4 schemas, some Zod v3 users use `.extend()` on schemas
// we can expose v3 schema instead
import { z } from 'zod/v3';
export const metaSchema = z.object({
title: z.string().optional(),
pages: z.array(z.string()).optional(),
description: z.string().optional(),
root: z.boolean().optional(),
defaultOpen: z.boolean().optional(),
icon: z.string().optional(),
});
export const frontmatterSchema = z.object({
title: z.string(),
description: z.string().optional(),
icon: z.string().optional(),
full: z.boolean().optional(),
// Fumadocs OpenAPI generated
_openapi: z.object({}).passthrough().optional(),
});
export * from './define';
export {
getDefaultMDXOptions,
type DefaultMDXOptions,
} from '../utils/mdx-options';
export * from '../mdx-plugins/remark-include';
+20
View File
@@ -0,0 +1,20 @@
import { z } from 'zod';
export const metaSchema = z.object({
title: z.string().optional(),
pages: z.array(z.string()).optional(),
description: z.string().optional(),
root: z.boolean().optional(),
defaultOpen: z.boolean().optional(),
icon: z.string().optional(),
});
export const frontmatterSchema = z.object({
title: z.string(),
description: z.string().optional(),
icon: z.string().optional(),
full: z.boolean().optional(),
// Fumadocs OpenAPI generated
_openapi: z.looseObject({}).optional(),
});
+1 -1
View File
@@ -4,7 +4,7 @@ import { type LoaderContext } from 'webpack';
import { getConfigHash, loadConfig } from '@/utils/config';
import { buildMDX } from '@/utils/build-mdx';
import { getGitTimestamp } from './utils/git-timestamp';
import { validate, ValidationError } from '@/utils/schema';
import { validate, ValidationError } from '@/utils/validation';
import { fumaMatter } from '@/utils/fuma-matter';
import { countLines } from '@/utils/count-lines';
+1 -1
View File
@@ -3,7 +3,7 @@ import * as fs from 'node:fs/promises';
import { glob } from 'tinyglobby';
import type { LoadedConfig } from '@/utils/config';
import type { DocCollection, MetaCollection } from '@/config';
import { validate } from '@/utils/schema';
import { validate } from '@/utils/validation';
import { fileCache } from '@/map/file-cache';
import type { AsyncRuntimeFile, FileInfo } from '@/runtime/types';
import { load } from 'js-yaml';
+1 -1
View File
@@ -3,7 +3,7 @@ import * as fs from 'node:fs/promises';
import { getConfigHash, loadConfig } from '@/utils/config';
import { generateJS } from '@/map/generate';
import { fileCache } from '@/map/file-cache';
import { ValidationError } from '@/utils/schema';
import { ValidationError } from '@/utils/validation';
/**
* Start a MDX server that builds index and manifest files.
+36 -1
View File
@@ -9,6 +9,7 @@ import type {
import type { ProcessorOptions } from '@mdx-js/mdx';
import { pathToFileURL } from 'node:url';
import { buildConfig } from '@/config/build';
import type { Plugin } from 'esbuild';
export function findConfigFile(): string {
return path.resolve('source.config.ts');
@@ -27,6 +28,39 @@ let cache: {
config: Promise<LoadedConfig>;
} | null = null;
async function isZod3() {
try {
const content = JSON.parse(
(await fs.readFile('node_modules/zod/package.json')).toString(),
);
const version = content.version;
return typeof version === 'string' && version.startsWith('3.');
} catch {
return false;
}
}
function createCompatZodPlugin(): Plugin {
return {
name: 'replace-zod-import',
async setup(build) {
const usingZod3 = await isZod3();
if (!usingZod3) return;
console.warn(
'[Fumadocs MDX] Noticed Zod v3 in your node_modules, we recommend upgrading to Zod v4 for better compatibility.',
);
build.onResolve({ filter: /^fumadocs-mdx\/config$/ }, () => {
return {
path: 'fumadocs-mdx/config/zod-3',
external: true,
};
});
},
};
}
async function compileConfig(configPath: string, outDir: string) {
const { build } = await import('esbuild');
@@ -34,11 +68,12 @@ async function compileConfig(configPath: string, outDir: string) {
entryPoints: [{ in: configPath, out: 'source.config' }],
bundle: true,
outdir: outDir,
target: 'node18',
target: 'node20',
write: true,
platform: 'node',
format: 'esm',
packages: 'external',
plugins: [createCompatZodPlugin()],
outExtension: {
'.js': '.mjs',
},
@@ -1,27 +1,7 @@
import { z } from 'zod';
import type { CollectionSchema } from '@/config';
import type { StandardSchemaV1 } from '@standard-schema/spec';
import picocolors from 'picocolors';
export const metaSchema = z.object({
title: z.string().optional(),
pages: z.array(z.string()).optional(),
description: z.string().optional(),
root: z.boolean().optional(),
defaultOpen: z.boolean().optional(),
icon: z.string().optional(),
});
export const frontmatterSchema = z.object({
title: z.string(),
description: z.string().optional(),
icon: z.string().optional(),
full: z.boolean().optional(),
// Fumadocs OpenAPI generated
_openapi: z.looseObject({}).optional(),
});
export class ValidationError extends Error {
title: string;
issues: readonly StandardSchemaV1.Issue[];
+1 -1
View File
@@ -4,7 +4,7 @@ import { buildMDX } from '@/utils/build-mdx';
import { parse } from 'node:querystring';
import { countLines } from '@/utils/count-lines';
import { fumaMatter } from '@/utils/fuma-matter';
import { validate, ValidationError } from '@/utils/schema';
import { validate, ValidationError } from '@/utils/validation';
import { z } from 'zod';
import { ident, toImportPath } from '@/utils/import-formatter';
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
+1
View File
@@ -4,6 +4,7 @@ export default defineConfig({
entry: [
'./src/{index,loader-mdx}.ts',
'./src/{config,next,vite}/index.ts',
'./src/config/zod-3.ts',
'./src/runtime/{async,vite}.ts',
],
format: ['esm', 'cjs'],