MDX: Support postinstall script on Vite

This commit is contained in:
Fuma Nama
2025-09-06 15:39:36 +08:00
parent 0d556676a6
commit 2566eef704
7 changed files with 104 additions and 50 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': patch
---
Support postinstall script on Vite
@@ -4,6 +4,7 @@ import remarkMdx from 'remark-mdx';
import { remarkInclude } from 'fumadocs-mdx/config';
import { source } from '@/lib/source';
import type { InferPageType } from 'fumadocs-core/source';
import fs from 'node:fs/promises';
const processor = remark()
.use(remarkMdx)
@@ -13,8 +14,8 @@ const processor = remark()
export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await processor.process({
path: page.data._file.absolutePath,
value: page.data.content,
path: page.absolutePath,
value: await fs.readFile(page.absolutePath),
});
// note: it doesn't escape frontmatter, it's up to you.
+3 -2
View File
@@ -5,6 +5,7 @@ import { remarkAutoTypeTable } from 'fumadocs-typescript';
import { remarkInclude } from 'fumadocs-mdx/config';
import { type Page } from '@/lib/source';
import { remarkNpm } from 'fumadocs-core/mdx-plugins';
import fs from 'node:fs/promises';
const processor = remark()
.use(remarkMdx)
@@ -23,8 +24,8 @@ export async function getLLMText(page: Page) {
}[page.slugs[0]] ?? page.slugs[0];
const processed = await processor.process({
path: page.data._file.absolutePath,
value: page.data.content,
path: page.absolutePath,
value: await fs.readFile(page.absolutePath),
});
return `# ${category}: ${page.data.title}
+41 -11
View File
@@ -2,22 +2,52 @@ import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import { findConfigFile, getConfigHash, loadConfig } from '@/utils/config';
import { generateJS } from '@/map/generate';
import { existsSync } from 'node:fs';
import { entry } from '@/vite/generate';
export async function postInstall(
configPath = findConfigFile(),
outDir = '.source',
outDir?: string,
): Promise<void> {
const jsOut = path.resolve(outDir, 'index.ts');
const hash = await getConfigHash(configPath);
const config = await loadConfig(configPath, outDir, hash, true);
const isNext =
existsSync('next.config.js') ||
existsSync('next.config.mjs') ||
existsSync('next.config.ts');
// clean past results
await fs.rm(path.dirname(jsOut), { recursive: true });
if (isNext) {
await onNext(configPath, outDir ?? '.source');
} else {
await onVite(configPath, outDir ?? process.cwd());
}
await fs.mkdir(path.dirname(jsOut), { recursive: true });
await fs.writeFile(
jsOut,
await generateJS(configPath, config, { relativeTo: outDir }, hash),
);
console.log('[MDX] types generated');
}
async function onNext(configPath: string, outDir: string) {
const config = await loadConfig(configPath, outDir, undefined, true);
const outPath = path.join(outDir, 'index.ts');
// clean past results
await fs.rm(outDir, { recursive: true });
await fs.mkdir(outDir, { recursive: true });
const hash = await getConfigHash(configPath);
await fs.writeFile(
outPath,
await generateJS(configPath, config, { relativeTo: outDir }, hash),
);
}
async function onVite(
configPath: string,
outDir: string,
addJsExtension?: boolean,
) {
const config = await loadConfig(configPath, 'node_modules', undefined, true);
const outFile = 'source.generated.ts';
await fs.writeFile(
path.join(outDir, outFile),
entry(configPath, config, outDir, addJsExtension),
);
}
+2 -2
View File
@@ -96,7 +96,7 @@ async function compileConfig(configPath: string, outDir: string) {
export async function loadConfig(
configPath: string,
outDir: string,
hash: string,
hash?: string,
build = false,
): Promise<LoadedConfig> {
if (cache && cache.hash === hash) {
@@ -114,7 +114,7 @@ export async function loadConfig(
);
});
cache = { config, hash };
if (hash) cache = { config, hash };
return await config;
}
+37 -1
View File
@@ -1,6 +1,7 @@
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
import { ident } from '@/utils/import-formatter';
import { ident, toImportPath } from '@/utils/import-formatter';
import { generateGlob } from '@/vite/generate-glob';
import type { LoadedConfig } from '@/utils/config';
export function docs(name: string, collection: DocsCollection) {
const obj = [
@@ -29,3 +30,38 @@ export function meta(name: string, collection: MetaCollection) {
import: 'default',
})})`;
}
export function entry(
configPath: string,
config: LoadedConfig,
outDir: string,
jsExtension?: boolean,
) {
const lines = [
'/// <reference types="vite/client" />',
`import { fromConfig } from 'fumadocs-mdx/runtime/vite';`,
`import type * as Config from '${toImportPath(configPath, {
relativeTo: outDir,
jsExtension,
})}';`,
'',
`export const create = fromConfig<typeof Config>();`,
];
for (const [name, collection] of config.collections.entries()) {
let body: string;
if (collection.type === 'docs') {
body = docs(name, collection);
} else if (collection.type === 'meta') {
body = meta(name, collection);
} else {
body = doc(name, collection);
}
lines.push('');
lines.push(`export const ${name} = ${body};`);
}
return lines.join('\n');
}
+13 -32
View File
@@ -12,13 +12,12 @@ import { countLines } from '@/utils/count-lines';
import { fumaMatter } from '@/utils/fuma-matter';
import { validate, ValidationError } from '@/utils/validation';
import { z } from 'zod';
import { toImportPath } from '@/utils/import-formatter';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { load } from 'js-yaml';
import type { SourceMap, TransformPluginContext } from 'rollup';
import { getGitTimestamp } from '@/utils/git-timestamp';
import { doc, docs, meta } from '@/vite/generate';
import { entry } from '@/vite/generate';
const FumadocsDeps = ['fumadocs-core', 'fumadocs-ui', 'fumadocs-openapi'];
@@ -202,38 +201,20 @@ export default function mdx(
if (!generateIndexFile) return;
console.log('[Fumadocs MDX] Generating index files');
const outdir = process.cwd();
const outDir = process.cwd();
const outFile = 'source.generated.ts';
const lines = [
'/// <reference types="vite/client" />',
`import { fromConfig } from 'fumadocs-mdx/runtime/vite';`,
`import type * as Config from '${toImportPath(configPath, {
relativeTo: outdir,
jsExtension:
typeof generateIndexFile === 'object'
? generateIndexFile.addJsExtension
: undefined,
})}';`,
'',
`export const create = fromConfig<typeof Config>();`,
];
for (const [name, collection] of loaded.collections.entries()) {
let body: string;
if (collection.type === 'docs') {
body = docs(name, collection);
} else if (collection.type === 'meta') {
body = meta(name, collection);
} else {
body = doc(name, collection);
}
lines.push('');
lines.push(`export const ${name} = ${body};`);
}
await fs.writeFile(path.join(outdir, outFile), lines.join('\n'));
await fs.writeFile(
path.join(outDir, outFile),
entry(
configPath,
loaded,
outDir,
typeof generateIndexFile === 'object'
? generateIndexFile.addJsExtension
: undefined,
),
);
},
async transform(value, id) {