MDX: Support customising index file output path in Vite

This commit is contained in:
Fuma Nama
2025-09-24 16:49:46 +08:00
parent 51268ec35a
commit af50bc8e32
33 changed files with 111 additions and 108 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': patch
---
Support customising index file output path in Vite
+1 -2
View File
@@ -1,7 +1,6 @@
import type { BunPlugin } from 'bun';
import { createMdxLoader } from '@/loaders/mdx';
import { resolvedConfig } from '@/loaders/config-loader';
import { findConfigFile } from '@/utils/config';
import { findConfigFile, resolvedConfig } from '@/loaders/config';
import { buildConfig } from '@/config/build';
import { parse } from 'node:querystring';
import { pathToFileURL } from 'node:url';
+2 -2
View File
@@ -1,4 +1,4 @@
import type { LoadedConfig } from '@/utils/config';
import type { LoadedConfig } from '@/loaders/config';
import type {
DocCollection,
DocsCollection,
@@ -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('@/mdx/preset');
const { getDefaultMDXOptions } = await import('@/loaders/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 '@/mdx/preset';
import type { DefaultMDXOptions } from '@/loaders/mdx/preset';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { frontmatterSchema, metaSchema } from '@/config/zod-4';
import type { PostprocessOptions } from '@/mdx/remark-postprocess';
import type { PostprocessOptions } from '@/loaders/mdx/remark-postprocess';
export type CollectionSchema<Schema extends StandardSchemaV1, Context> =
| Schema
+5 -2
View File
@@ -1,4 +1,7 @@
export * from './define';
export { getDefaultMDXOptions, type DefaultMDXOptions } from '../mdx/preset';
export {
getDefaultMDXOptions,
type DefaultMDXOptions,
} from '@/loaders/mdx/preset';
export * from './zod-4';
export * from '@/mdx/remark-include';
export * from '@/loaders/mdx/remark-include';
+1 -1
View File
@@ -1,6 +1,6 @@
import { type LoaderContext } from 'webpack';
import { createMdxLoader } from '@/loaders/mdx';
import { dynamicConfig } from '@/loaders/config-loader';
import { dynamicConfig } from '@/loaders/config';
import { toWebpack, type WebpackLoader } from '@/loaders/adapter';
export interface Options {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CompilerOptions } from '@/mdx/build-mdx';
import type { CompilerOptions } from '@/loaders/mdx/build-mdx';
import type { LoadFnOutput, LoadHook } from 'node:module';
import { fileURLToPath } from 'node:url';
import fs from 'node:fs/promises';
-28
View File
@@ -1,28 +0,0 @@
import { getConfigHash, loadConfig, LoadedConfig } from '@/utils/config';
export interface ConfigLoader {
getConfig: (hash?: string) => LoadedConfig | Promise<LoadedConfig>;
}
export function resolvedConfig(loaded: LoadedConfig): ConfigLoader {
return {
getConfig() {
return loaded;
},
};
}
export function dynamicConfig(
configPath: string,
outDir: string,
): ConfigLoader {
return {
async getConfig(hash) {
return loadConfig(
configPath,
outDir,
hash ?? (await getConfigHash(configPath)),
);
},
};
}
+48
View File
@@ -0,0 +1,48 @@
import path from 'node:path';
import type {
DocCollection,
DocsCollection,
GlobalConfig,
MetaCollection,
} from '@/config';
import type { ProcessorOptions } from '@mdx-js/mdx';
export interface ConfigLoader {
getConfig: (hash?: string) => LoadedConfig | Promise<LoadedConfig>;
}
export interface LoadedConfig {
collections: Map<string, DocCollection | MetaCollection | DocsCollection>;
global: GlobalConfig;
getDefaultMDXOptions(mode?: 'default' | 'remote'): Promise<ProcessorOptions>;
}
export function findConfigFile(): string {
return path.resolve('source.config.ts');
}
export function resolvedConfig(loaded: LoadedConfig): ConfigLoader {
return {
getConfig() {
return loaded;
},
};
}
export function dynamicConfig(
configPath: string,
outDir: string,
): ConfigLoader {
return {
async getConfig(hash) {
const { loadConfig, getConfigHash } = await import('./load');
return loadConfig(
configPath,
outDir,
hash ?? (await getConfigHash(configPath)),
);
},
};
}
@@ -1,26 +1,8 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import type {
DocCollection,
DocsCollection,
GlobalConfig,
MetaCollection,
} from '@/config/define';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { pathToFileURL } from 'node:url';
import { buildConfig } from '@/config/build';
export function findConfigFile(): string {
return path.resolve('source.config.ts');
}
export interface LoadedConfig {
collections: Map<string, DocCollection | MetaCollection | DocsCollection>;
global: GlobalConfig;
getDefaultMDXOptions(mode?: 'default' | 'remote'): Promise<ProcessorOptions>;
}
import type { LoadedConfig } from '@/loaders/config';
let cache: {
hash: string;
@@ -1,6 +1,6 @@
import { createProcessor, type ProcessorOptions } from '@mdx-js/mdx';
import type { VFile } from 'vfile';
import { remarkInclude } from '@/mdx/remark-include';
import { remarkInclude } from '@/loaders/mdx/remark-include';
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
import type { TableOfContents } from 'fumadocs-core/server';
import type { FC } from 'react';
@@ -9,7 +9,7 @@ import {
type ExtractedReference,
type PostprocessOptions,
remarkPostprocess,
} from '@/mdx/remark-postprocess';
} from '@/loaders/mdx/remark-postprocess';
type Processor = ReturnType<typeof createProcessor>;
@@ -1,12 +1,11 @@
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 '@/mdx/build-mdx';
import { buildMDX } from '@/loaders/mdx/build-mdx';
import type { SourceMap } from 'rollup';
import type { Loader } from '@/loaders/adapter';
import { z } from 'zod';
import type { ConfigLoader } from '@/loaders/config-loader';
import type { ConfigLoader } from '@/loaders/config';
import fs from 'node:fs/promises';
import path from 'node:path';
import { createHash } from 'node:crypto';
@@ -137,3 +136,13 @@ export function createMdxLoader(configLoader: ConfigLoader): Loader {
function generateCacheHash(input: string): string {
return createHash('md5').update(input).digest('hex');
}
function countLines(s: string) {
let num = 0;
for (const c of s) {
if (c === '\n') num++;
}
return num;
}
+2 -2
View File
@@ -1,7 +1,7 @@
import type { NextConfig } from 'next';
import type { Configuration } from 'webpack';
import { findConfigFile } from '@/utils/config';
import { start } from '@/map';
import { findConfigFile } from '@/loaders/config';
import { start } from './map';
import { type Options as MDXLoaderOptions } from '../loader-mdx';
import { readFileSync } from 'node:fs';
import type { TurbopackOptions } from 'next/dist/server/config-shared';
+3 -2
View File
@@ -1,7 +1,8 @@
import { findConfigFile, getConfigHash, loadConfig } from '@/utils/config';
import { getConfigHash, loadConfig } from '@/loaders/config/load';
import path from 'node:path';
import fs from 'node:fs/promises';
import { generateJS } from '@/map/generate';
import { generateJS } from '@/next/map/generate';
import { findConfigFile } from '@/loaders/config';
export * from './create';
@@ -1,10 +1,10 @@
import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { glob } from 'tinyglobby';
import type { LoadedConfig } from '@/utils/config';
import type { LoadedConfig } from '@/loaders/config';
import type { DocCollection, MetaCollection } from '@/config';
import { validate } from '@/utils/validation';
import { fileCache } from '@/map/file-cache';
import { fileCache } from '@/next/map/file-cache';
import type { AsyncRuntimeFile } from '@/runtime/next/types';
import { load } from 'js-yaml';
import { getGitTimestamp } from '@/utils/git-timestamp';
@@ -1,8 +1,8 @@
import * as path from 'node:path';
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 { getConfigHash, loadConfig } from '@/loaders/config/load';
import { generateJS } from '@/next/map/generate';
import { fileCache } from '@/next/map/file-cache';
import { ValidationError } from '@/utils/validation';
/**
@@ -47,7 +47,7 @@ export async function start(
await updateMapFile();
if (dev) {
const { watcher } = await import('@/map/watcher');
const { watcher } = await import('@/next/map/watcher');
const instance = watcher(configPath, config, [outPath]);
instance.on('ready', () => {
@@ -1,5 +1,5 @@
import { FSWatcher } from 'chokidar';
import { type LoadedConfig } from '@/utils/config';
import { type LoadedConfig } from '@/loaders/config';
export function watcher(
configPath: string,
+1 -2
View File
@@ -1,6 +1,5 @@
import { findConfigFile } from '@/utils/config';
import { dynamicConfig, findConfigFile } from '@/loaders/config';
import { createMdxLoader } from '@/loaders/mdx';
import { dynamicConfig } from '@/loaders/config-loader';
import { toNode } from '@/loaders/adapter';
export const load = toNode(
+2 -2
View File
@@ -1,7 +1,7 @@
import type { LoadedConfig } from '@/utils/config';
import type { LoadedConfig } from '@/loaders/config';
import { _runtime, createMDXSource } from './index';
import type { AsyncDocOut, RuntimeAsync } from './types';
import { buildMDX, CompiledMDXProperties } from '@/mdx/build-mdx';
import { buildMDX, CompiledMDXProperties } from '@/loaders/mdx/build-mdx';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { executeMdx } from '@fumadocs/mdx-remote/client';
import { pathToFileURL } from 'node:url';
+1 -1
View File
@@ -5,7 +5,7 @@ import {
type Source,
} from 'fumadocs-core/source';
import type { DocOut, MetaOut, Runtime } from './types';
import type { CompiledMDXProperties } from '@/mdx/build-mdx';
import type { CompiledMDXProperties } from '@/loaders/mdx/build-mdx';
import * as fs from 'node:fs/promises';
import { type FileInfo, missingProcessedMarkdown } from '@/runtime/shared';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { MetaData, PageData, Source } from 'fumadocs-core/source';
import type { LoadedConfig } from '@/utils/config';
import type { LoadedConfig } from '@/loaders/config';
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
import type {
AsyncDocCollectionEntry,
+1 -1
View File
@@ -2,7 +2,7 @@ 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/remark-postprocess';
import type { ExtractedReference } from '@/loaders/mdx/remark-postprocess';
export interface FileInfo {
/**
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CompiledMDXProperties } from '@/mdx/build-mdx';
import type { CompiledMDXProperties } from '@/loaders/mdx/build-mdx';
export type CompiledMDXFile<Frontmatter> = CompiledMDXProperties<Frontmatter> &
Record<string, unknown>;
-9
View File
@@ -1,9 +0,0 @@
export function countLines(s: string) {
let num = 0;
for (const c of s) {
if (c === '\n') num++;
}
return num;
}
+1 -1
View File
@@ -1,7 +1,7 @@
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
import { ident, toImportPath } from '@/utils/import-formatter';
import { generateGlob, getGlobBase } from '@/vite/generate-glob';
import type { LoadedConfig } from '@/utils/config';
import type { LoadedConfig } from '@/loaders/config';
import { getGlobPatterns } from '@/utils/collections';
function docs(name: string, collection: DocsCollection) {
+7 -14
View File
@@ -12,7 +12,7 @@ import * as path from 'node:path';
import { load } from 'js-yaml';
import { entry } from '@/vite/generate';
import { createMdxLoader } from '@/loaders/mdx';
import { resolvedConfig } from '@/loaders/config-loader';
import { resolvedConfig } from '@/loaders/config';
import { toVite } from '@/loaders/adapter';
const FumadocsDeps = ['fumadocs-core', 'fumadocs-ui', 'fumadocs-openapi'];
@@ -26,6 +26,7 @@ export interface PluginOptions {
generateIndexFile?:
| boolean
| {
out?: string;
/**
* add `.js` extensions to imports, needed for ESM without bundler resolution
*/
@@ -113,22 +114,14 @@ export default function mdx(
},
async buildStart() {
if (!generateIndexFile) return;
const { out = 'source.generated.ts', addJsExtension } =
typeof generateIndexFile === 'object' ? generateIndexFile : {};
console.log('[Fumadocs MDX] Generating index files');
const outDir = process.cwd();
const outFile = 'source.generated.ts';
await fs.writeFile(
path.join(outDir, outFile),
entry(
configPath,
loaded,
outDir,
typeof generateIndexFile === 'object'
? generateIndexFile.addJsExtension
: undefined,
),
);
const dir = path.dirname(out);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(out, entry(configPath, loaded, dir, addJsExtension));
},
async transform(value, id) {
+2 -1
View File
@@ -1,7 +1,8 @@
import { findConfigFile, loadConfig } from '@/utils/config';
import { loadConfig } from '@/loaders/config/load';
import fs from 'node:fs/promises';
import path from 'node:path';
import { entry } from '@/vite/generate';
import { findConfigFile } from '@/loaders/config';
export async function postInstall(
configPath = findConfigFile(),
+1 -1
View File
@@ -3,7 +3,7 @@ import * as path from 'node:path';
import { expect, test } from 'vitest';
import { z } from 'zod';
import { ValidationError } from '@/utils/validation';
import { generateJS } from '@/map/generate';
import { generateJS } from '@/next/map/generate';
import { defineCollections } from '@/config';
import { fumaMatter } from '@/utils/fuma-matter';
+1 -1
View File
@@ -2,7 +2,7 @@ import { expect, test } from 'vitest';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import remarkMdx from 'remark-mdx';
import { remarkInclude } from '@/mdx/remark-include';
import { remarkInclude } from '@/loaders/mdx/remark-include';
import remarkDirective from 'remark-directive';
import fs from 'node:fs/promises';
import { remark } from 'remark';