mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Support type-safe collection generation
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Support type-safe collection generation
|
||||
@@ -26,11 +26,13 @@ export function createMdxPlugin(options: MdxPluginOptions = {}): BunPlugin {
|
||||
name: 'bun-plugin-fumadocs-mdx',
|
||||
async setup(build) {
|
||||
const importPath = pathToFileURL(configPath).href;
|
||||
const core = await createCore({
|
||||
const core = createCore({
|
||||
environment,
|
||||
outDir,
|
||||
configPath,
|
||||
}).init({
|
||||
});
|
||||
|
||||
await core.init({
|
||||
config: buildConfig(await import(importPath)),
|
||||
});
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@ import type { Core } from '@/core';
|
||||
|
||||
async function compileConfig(core: Core) {
|
||||
const { build } = await import('esbuild');
|
||||
const { configPath, outDir } = core.getOptions();
|
||||
|
||||
const transformed = await build({
|
||||
entryPoints: [{ in: core._options.configPath, out: 'source.config' }],
|
||||
entryPoints: [{ in: configPath, out: 'source.config' }],
|
||||
bundle: true,
|
||||
outdir: core._options.outDir,
|
||||
outdir: outDir,
|
||||
target: 'node20',
|
||||
write: true,
|
||||
platform: 'node',
|
||||
|
||||
@@ -6,9 +6,11 @@ import type {
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
import { removeFileCache } from './utils/codegen/cache';
|
||||
import { validate } from './utils/validation';
|
||||
import type { VFile } from 'vfile';
|
||||
import type { IndexFilePlugin } from './plugins/index-file';
|
||||
import type { PostprocessOptions } from './config';
|
||||
import { ident } from './utils/codegen';
|
||||
|
||||
type Awaitable<T> = T | Promise<T>;
|
||||
|
||||
@@ -33,7 +35,7 @@ export interface TransformOptions<Collection> {
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
export interface Plugin extends IndexFilePlugin {
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
@@ -157,7 +159,6 @@ export function createCore(
|
||||
}
|
||||
|
||||
const core = {
|
||||
_options: options,
|
||||
/**
|
||||
* Convenient cache store, reset when config changes
|
||||
*/
|
||||
@@ -166,6 +167,7 @@ export function createCore(
|
||||
config = await newConfig;
|
||||
this.cache.clear();
|
||||
plugins = await getPlugins([
|
||||
postprocessPlugin(),
|
||||
...defaultPlugins,
|
||||
...(config.global.plugins ?? []),
|
||||
]);
|
||||
@@ -174,8 +176,9 @@ export function createCore(
|
||||
const out = await plugin.config?.call(pluginContext, config);
|
||||
if (out) config = out;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
getOptions() {
|
||||
return options;
|
||||
},
|
||||
getConfig(): LoadedConfig {
|
||||
return config;
|
||||
@@ -186,11 +189,13 @@ export function createCore(
|
||||
getCompiledConfigPath(): string {
|
||||
return path.join(options.outDir, 'source.config.mjs');
|
||||
},
|
||||
getPlugins() {
|
||||
return plugins;
|
||||
},
|
||||
getPluginContext(): PluginContext {
|
||||
return pluginContext;
|
||||
},
|
||||
async initServer(server: ServerContext): Promise<void> {
|
||||
server.watcher?.on('all', async (event, file) => {
|
||||
if (event === 'change') removeFileCache(file);
|
||||
});
|
||||
|
||||
for (const plugin of plugins) {
|
||||
await plugin.configureServer?.call(pluginContext, server);
|
||||
}
|
||||
@@ -284,4 +289,46 @@ export function createCore(
|
||||
return core;
|
||||
}
|
||||
|
||||
function postprocessPlugin(): Plugin {
|
||||
const LinkReferenceTypes = `{
|
||||
/**
|
||||
* extracted references (e.g. hrefs, paths), useful for analyzing relationships between pages.
|
||||
*/
|
||||
extractedReferences?: import('fumadocs-mdx').ExtractedReference[];
|
||||
}`;
|
||||
|
||||
return {
|
||||
'index-file': {
|
||||
generateTypeConfig() {
|
||||
const lines: string[] = [];
|
||||
lines.push('{');
|
||||
lines.push(' DocData: {');
|
||||
for (const collection of this.core.getConfig().collectionList) {
|
||||
let postprocessOptions: Partial<PostprocessOptions> | undefined;
|
||||
switch (collection.type) {
|
||||
case 'doc':
|
||||
postprocessOptions = collection.postprocess;
|
||||
break;
|
||||
case 'docs':
|
||||
postprocessOptions = collection.docs.postprocess;
|
||||
break;
|
||||
}
|
||||
|
||||
if (postprocessOptions?.extractLinkReferences) {
|
||||
lines.push(ident(`${collection.name}: ${LinkReferenceTypes},`, 2));
|
||||
}
|
||||
}
|
||||
lines.push(' }');
|
||||
lines.push('}');
|
||||
return lines.join('\n');
|
||||
},
|
||||
serverOptions(options) {
|
||||
options.doc ??= {};
|
||||
options.doc.passthroughs ??= [];
|
||||
options.doc.passthroughs.push('extractedReferences');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type Core = ReturnType<typeof createCore>;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export type * from './core';
|
||||
export type { ExtractedReference } from '@/loaders/mdx/remark-postprocess';
|
||||
export type { CompiledMDXProperties } from '@/loaders/mdx/build-mdx';
|
||||
|
||||
@@ -27,7 +27,7 @@ export function createStandaloneConfigLoader({
|
||||
async function getConfigHash(): Promise<string> {
|
||||
if (mode === 'production') return 'static';
|
||||
|
||||
const stats = await fs.stat(core._options.configPath).catch(() => {
|
||||
const stats = await fs.stat(core.getOptions().configPath).catch(() => {
|
||||
throw new Error('Cannot find config file');
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import type { FC } from 'react';
|
||||
import type { MDXProps } from 'mdx/types';
|
||||
import {
|
||||
type ExtractedReference,
|
||||
type PostprocessOptions,
|
||||
remarkPostprocess,
|
||||
} from '@/loaders/mdx/remark-postprocess';
|
||||
@@ -32,21 +31,12 @@ export interface CompilerOptions {
|
||||
addDependency: (file: string) => void;
|
||||
}
|
||||
|
||||
// TODO: allow plugins to customise the type
|
||||
export interface CompiledMDXProperties<Frontmatter = Record<string, unknown>> {
|
||||
frontmatter: Frontmatter;
|
||||
structuredData: StructuredData;
|
||||
toc: TOCItemType[];
|
||||
default: FC<MDXProps>;
|
||||
|
||||
/**
|
||||
* Added by the `last-modified` plugin.
|
||||
*/
|
||||
lastModified?: Date;
|
||||
/**
|
||||
* Enable from `postprocess` option.
|
||||
*/
|
||||
extractedReferences?: ExtractedReference[];
|
||||
/**
|
||||
* Enable from `postprocess` option.
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ export function createMDX(createOptions: CreateMDXOptions = {}) {
|
||||
|
||||
return (nextConfig: NextConfig = {}): NextConfig => {
|
||||
const loaderOptions: WebpackLoaderOptions = {
|
||||
...core._options,
|
||||
...core.getOptions(),
|
||||
compiledConfigPath: core.getCompiledConfigPath(),
|
||||
isDev,
|
||||
};
|
||||
@@ -133,13 +133,14 @@ async function init(dev: boolean, core: Core): Promise<void> {
|
||||
|
||||
async function devServer() {
|
||||
const { FSWatcher } = await import('chokidar');
|
||||
const { configPath, outDir } = core.getOptions();
|
||||
const watcher = new FSWatcher({
|
||||
ignoreInitial: true,
|
||||
persistent: true,
|
||||
ignored: [core._options.outDir],
|
||||
ignored: [outDir],
|
||||
});
|
||||
|
||||
watcher.add(core._options.configPath);
|
||||
watcher.add(configPath);
|
||||
for (const collection of core.getConfig().collectionList) {
|
||||
if (collection.type === 'docs') {
|
||||
watcher.add(collection.docs.dir);
|
||||
@@ -153,8 +154,9 @@ async function init(dev: boolean, core: Core): Promise<void> {
|
||||
console.log('[MDX] started dev server');
|
||||
});
|
||||
|
||||
const absoluteConfigPath = path.resolve(configPath);
|
||||
watcher.on('all', async (_event, file) => {
|
||||
if (path.resolve(file) === path.resolve(core._options.configPath)) {
|
||||
if (path.resolve(file) === absoluteConfigPath) {
|
||||
// skip plugin listeners
|
||||
watcher.removeAllListeners();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Core, Plugin } from '@/core';
|
||||
import type { Core, Plugin, PluginContext } from '@/core';
|
||||
import type {
|
||||
LoadedConfig,
|
||||
CollectionItem,
|
||||
@@ -8,11 +8,12 @@ import type {
|
||||
import path from 'path';
|
||||
import { type CodeGen, createCodegen, ident } from '@/utils/codegen';
|
||||
import { glob } from 'tinyglobby';
|
||||
import { readFileWithCache } from '@/utils/codegen/cache';
|
||||
import { readFileWithCache, removeFileCache } from '@/utils/fs-cache';
|
||||
import { createHash } from 'crypto';
|
||||
import type { LazyEntry } from '@/runtime/dynamic';
|
||||
import type { EmitEntry } from '@/core';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
import type { ServerOptions } from '@/runtime/server';
|
||||
|
||||
export interface IndexFilePluginOptions {
|
||||
target?: 'default' | 'vite';
|
||||
@@ -35,6 +36,20 @@ export interface IndexFilePluginOptions {
|
||||
dynamic?: boolean;
|
||||
}
|
||||
|
||||
export interface IndexFilePlugin {
|
||||
['index-file']?: {
|
||||
generateTypeConfig?: (this: PluginContext) => string | void;
|
||||
serverOptions?: (this: PluginContext, options: ServerOptions) => void;
|
||||
};
|
||||
}
|
||||
|
||||
interface FileGenContext {
|
||||
core: Core;
|
||||
codegen: CodeGen;
|
||||
serverOptions: ServerOptions;
|
||||
tc: string;
|
||||
}
|
||||
|
||||
export default function indexFile(
|
||||
options: IndexFilePluginOptions = {},
|
||||
): Plugin {
|
||||
@@ -54,6 +69,33 @@ export default function indexFile(
|
||||
);
|
||||
}
|
||||
|
||||
function generateConfigs(core: Core): {
|
||||
serverOptions: ServerOptions;
|
||||
tc: string;
|
||||
} {
|
||||
const serverOptions: ServerOptions = {};
|
||||
const typeConfigs: string[] = [];
|
||||
const ctx = core.getPluginContext();
|
||||
|
||||
for (const plugin of core.getPlugins()) {
|
||||
const indexFilePlugin = plugin['index-file'];
|
||||
if (!indexFilePlugin) continue;
|
||||
indexFilePlugin.serverOptions?.call(ctx, serverOptions);
|
||||
|
||||
const config = indexFilePlugin.generateTypeConfig?.call(ctx);
|
||||
if (config) typeConfigs.push(config);
|
||||
}
|
||||
|
||||
if (typeConfigs.length === 0) {
|
||||
typeConfigs.push('{ DocData: {} }');
|
||||
}
|
||||
|
||||
return {
|
||||
serverOptions,
|
||||
tc: typeConfigs.join(' & '),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'index-file',
|
||||
config(v) {
|
||||
@@ -64,6 +106,8 @@ export default function indexFile(
|
||||
if (!server.watcher) return;
|
||||
|
||||
server.watcher.on('all', async (event, file) => {
|
||||
removeFileCache(file);
|
||||
|
||||
// dynamic collections always require re-generation on change
|
||||
if (dynamicCollections.length === 0) {
|
||||
// vite uses `import.meta.glob`, no need to re-generate
|
||||
@@ -89,62 +133,56 @@ export default function indexFile(
|
||||
},
|
||||
async emit() {
|
||||
const globCache = new Map<string, Promise<string[]>>();
|
||||
const makeCodeGen = () =>
|
||||
createCodegen({
|
||||
const { serverOptions, tc } = generateConfigs(this.core);
|
||||
|
||||
const toEmitEntry = async (
|
||||
path: string,
|
||||
content: (ctx: FileGenContext) => Promise<void>,
|
||||
): Promise<EmitEntry> => {
|
||||
const codegen = createCodegen({
|
||||
target,
|
||||
outDir: this.outDir,
|
||||
jsExtension: addJsExtension,
|
||||
globCache,
|
||||
});
|
||||
|
||||
async function toEmitEntry(
|
||||
path: string,
|
||||
content: Promise<string>,
|
||||
): Promise<EmitEntry> {
|
||||
await content({
|
||||
core: this.core,
|
||||
codegen,
|
||||
serverOptions,
|
||||
tc,
|
||||
});
|
||||
return {
|
||||
path,
|
||||
content: await content,
|
||||
content: codegen.toString(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const out: Promise<EmitEntry>[] = [
|
||||
toEmitEntry(
|
||||
'server.ts',
|
||||
generateServerIndexFile(makeCodeGen(), config, this.configPath),
|
||||
),
|
||||
toEmitEntry('server.ts', generateServerIndexFile),
|
||||
];
|
||||
|
||||
if (dynamic)
|
||||
out.push(
|
||||
toEmitEntry(
|
||||
'dynamic.ts',
|
||||
generateDynamicIndexFile(this.core, makeCodeGen()),
|
||||
),
|
||||
);
|
||||
out.push(toEmitEntry('dynamic.ts', generateDynamicIndexFile));
|
||||
|
||||
if (browser)
|
||||
out.push(
|
||||
toEmitEntry(
|
||||
'browser.ts',
|
||||
generateBrowserIndexFile(makeCodeGen(), config, this.configPath),
|
||||
),
|
||||
);
|
||||
out.push(toEmitEntry('browser.ts', generateBrowserIndexFile));
|
||||
|
||||
return await Promise.all(out);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function generateServerIndexFile(
|
||||
codegen: CodeGen,
|
||||
config: LoadedConfig,
|
||||
configPath: string,
|
||||
): Promise<string> {
|
||||
async function generateServerIndexFile({
|
||||
core,
|
||||
codegen,
|
||||
serverOptions,
|
||||
tc,
|
||||
}: FileGenContext) {
|
||||
codegen.lines.push(
|
||||
`import { fromConfig } from 'fumadocs-mdx/runtime/server';`,
|
||||
`import type * as Config from '${codegen.formatImportPath(configPath)}';`,
|
||||
`import { server } from 'fumadocs-mdx/runtime/server';`,
|
||||
`import type * as Config from '${codegen.formatImportPath(core.getOptions().configPath)}';`,
|
||||
'',
|
||||
`const create = fromConfig<typeof Config>();`,
|
||||
`const create = server<typeof Config, ${tc}>(${JSON.stringify(serverOptions)});`,
|
||||
);
|
||||
|
||||
async function generateCollectionObject(
|
||||
@@ -202,24 +240,27 @@ async function generateServerIndexFile(
|
||||
}
|
||||
|
||||
await codegen.pushAsync(
|
||||
config.collectionList.map(async (collection) => {
|
||||
core.getConfig().collectionList.map(async (collection) => {
|
||||
const obj = await generateCollectionObject(collection);
|
||||
if (!obj) return;
|
||||
|
||||
return `\nexport const ${collection.name} = ${obj};`;
|
||||
}),
|
||||
);
|
||||
|
||||
return codegen.toString();
|
||||
}
|
||||
|
||||
async function generateDynamicIndexFile(core: Core, codegen: CodeGen) {
|
||||
const { configPath } = core._options;
|
||||
async function generateDynamicIndexFile({
|
||||
core,
|
||||
codegen,
|
||||
serverOptions,
|
||||
tc,
|
||||
}: FileGenContext) {
|
||||
const { configPath } = core.getOptions();
|
||||
codegen.lines.push(
|
||||
`import { fromConfigDynamic } from 'fumadocs-mdx/runtime/dynamic';`,
|
||||
`import { dynamic } from 'fumadocs-mdx/runtime/dynamic';`,
|
||||
`import * as Config from '${codegen.formatImportPath(configPath)}';`,
|
||||
'',
|
||||
`const create = await fromConfigDynamic(Config);`,
|
||||
`const create = await dynamic<typeof Config, ${tc}>(Config, ${JSON.stringify(core.getOptions())}, ${JSON.stringify(serverOptions)});`,
|
||||
);
|
||||
|
||||
async function generateCollectionObjectEntry(
|
||||
@@ -296,20 +337,14 @@ async function generateDynamicIndexFile(core: Core, codegen: CodeGen) {
|
||||
return `\nexport const ${collection.name} = ${obj};`;
|
||||
}),
|
||||
);
|
||||
|
||||
return codegen.toString();
|
||||
}
|
||||
|
||||
async function generateBrowserIndexFile(
|
||||
codegen: CodeGen,
|
||||
config: LoadedConfig,
|
||||
configPath: string,
|
||||
): Promise<string> {
|
||||
async function generateBrowserIndexFile({ core, codegen, tc }: FileGenContext) {
|
||||
codegen.lines.push(
|
||||
`import { fromConfig } from 'fumadocs-mdx/runtime/browser';`,
|
||||
`import type * as Config from '${codegen.formatImportPath(configPath)}';`,
|
||||
`import { browser } from 'fumadocs-mdx/runtime/browser';`,
|
||||
`import type * as Config from '${codegen.formatImportPath(core.getOptions().configPath)}';`,
|
||||
'',
|
||||
`const create = fromConfig<typeof Config>();`,
|
||||
`const create = browser<typeof Config, ${tc}>();`,
|
||||
);
|
||||
|
||||
async function generateCollectionObject(
|
||||
@@ -331,7 +366,7 @@ async function generateBrowserIndexFile(
|
||||
codegen.lines.push('const browserCollections = {');
|
||||
|
||||
await codegen.pushAsync(
|
||||
config.collectionList.map(async (collection) => {
|
||||
core.getConfig().collectionList.map(async (collection) => {
|
||||
const obj = await generateCollectionObject(collection);
|
||||
if (!obj) return;
|
||||
|
||||
@@ -340,7 +375,6 @@ async function generateBrowserIndexFile(
|
||||
);
|
||||
|
||||
codegen.lines.push('};', 'export default browserCollections;');
|
||||
return codegen.toString();
|
||||
}
|
||||
|
||||
function generateDocCollectionFrontmatterGlob(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'node:path';
|
||||
import { x } from 'tinyexec';
|
||||
import type { Plugin } from '@/core';
|
||||
import { ident } from '@/utils/codegen';
|
||||
|
||||
const cache = new Map<string, Promise<Date>>();
|
||||
|
||||
@@ -15,24 +16,57 @@ export interface LastModifiedPluginOptions {
|
||||
* @defaultValue 'git'
|
||||
*/
|
||||
versionControl?: 'git';
|
||||
|
||||
/**
|
||||
* Filter the collections to include by names
|
||||
*/
|
||||
filter?: (collection: string) => boolean;
|
||||
}
|
||||
|
||||
const ExtendTypes = `{
|
||||
/**
|
||||
* Last modified date of document file, obtained from version control.
|
||||
*
|
||||
*/
|
||||
lastModified?: Date;
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Injects `lastModified` property to page exports.
|
||||
*/
|
||||
export default function lastModified(
|
||||
options: LastModifiedPluginOptions = {},
|
||||
): Plugin {
|
||||
const { versionControl = 'git' } = options;
|
||||
const { versionControl = 'git', filter = () => true } = options;
|
||||
|
||||
return {
|
||||
name: 'last-modified',
|
||||
'index-file': {
|
||||
generateTypeConfig() {
|
||||
const lines: string[] = [];
|
||||
lines.push('{');
|
||||
lines.push(' DocData: {');
|
||||
for (const collection of this.core.getConfig().collectionList) {
|
||||
if (filter(collection.name)) {
|
||||
lines.push(ident(`${collection.name}: ${ExtendTypes},`, 2));
|
||||
}
|
||||
}
|
||||
lines.push(' }');
|
||||
lines.push('}');
|
||||
return lines.join('\n');
|
||||
},
|
||||
serverOptions(options) {
|
||||
options.doc ??= {};
|
||||
options.doc.passthroughs ??= [];
|
||||
options.doc.passthroughs.push('lastModified');
|
||||
},
|
||||
},
|
||||
doc: {
|
||||
async vfile(file) {
|
||||
if (!filter(this.collection.name)) return;
|
||||
|
||||
if (versionControl === 'git') {
|
||||
const timestamp = await getGitTimestamp(this.filePath).then((v) =>
|
||||
v?.getTime(),
|
||||
);
|
||||
const timestamp = await getGitTimestamp(this.filePath);
|
||||
if (timestamp === undefined) return;
|
||||
|
||||
file.data['mdx-export'] ??= [];
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { type ReactNode, type FC, lazy, createElement } from 'react';
|
||||
import type { CompiledMDXFile } from './server';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import type { DocCollection, DocsCollection } from '@/config/define';
|
||||
import type { CompiledMDXProperties } from '@/loaders/mdx/build-mdx';
|
||||
import type { InternalTypeConfig } from './types';
|
||||
|
||||
export interface ClientLoaderOptions<Frontmatter, Props> {
|
||||
type CompiledMDXFile<
|
||||
Name extends string,
|
||||
Frontmatter,
|
||||
TC extends InternalTypeConfig,
|
||||
> = CompiledMDXProperties<Frontmatter> &
|
||||
TC['DocData'][Name] &
|
||||
Record<string, unknown>;
|
||||
|
||||
export interface ClientLoaderOptions<Doc, Props> {
|
||||
/**
|
||||
* Loader ID (usually your collection name)
|
||||
*
|
||||
@@ -15,11 +24,11 @@ export interface ClientLoaderOptions<Frontmatter, Props> {
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
component: (loaded: CompiledMDXFile<Frontmatter>, props: Props) => ReactNode;
|
||||
component: (loaded: Doc, props: Props) => ReactNode;
|
||||
}
|
||||
|
||||
export interface ClientLoader<Frontmatter, Props> {
|
||||
preload: (path: string) => Promise<CompiledMDXFile<Frontmatter>>;
|
||||
export interface ClientLoader<Doc, Props> {
|
||||
preload: (path: string) => Promise<Doc>;
|
||||
/**
|
||||
* Get a component that renders content with `React.lazy`.
|
||||
*/
|
||||
@@ -31,37 +40,39 @@ export interface ClientLoader<Frontmatter, Props> {
|
||||
useContent: (path: string, props: Props) => ReactNode;
|
||||
}
|
||||
|
||||
export type BrowserCreate<Config> = ReturnType<typeof fromConfig<Config>>;
|
||||
export type BrowserCreate<Config, TC extends InternalTypeConfig> = ReturnType<
|
||||
typeof browser<Config, TC>
|
||||
>;
|
||||
|
||||
export interface DocCollectionEntry<
|
||||
Name extends string = string,
|
||||
Frontmatter = unknown,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> {
|
||||
raw: Record<string, () => Promise<CompiledMDXFile<Name, Frontmatter, TC>>>;
|
||||
|
||||
export interface DocCollectionEntry<Frontmatter> {
|
||||
raw: Record<string, () => Promise<CompiledMDXFile<Frontmatter>>>;
|
||||
createClientLoader: <Props extends object>(
|
||||
options: ClientLoaderOptions<Frontmatter, Props>,
|
||||
) => ClientLoader<Frontmatter, Props>;
|
||||
options: ClientLoaderOptions<CompiledMDXFile<Name, Frontmatter, TC>, Props>,
|
||||
) => ClientLoader<CompiledMDXFile<Name, Frontmatter, TC>, Props>;
|
||||
}
|
||||
|
||||
export function fromConfig<Config>() {
|
||||
export function browser<Config, TC extends InternalTypeConfig>() {
|
||||
return {
|
||||
doc<Name extends keyof Config>(
|
||||
doc<Name extends keyof Config & string>(
|
||||
_name: Name,
|
||||
glob: Record<string, () => Promise<unknown>>,
|
||||
) {
|
||||
const raw = glob as Record<
|
||||
string,
|
||||
() => Promise<CompiledMDXFile<unknown>>
|
||||
>;
|
||||
|
||||
const out: DocCollectionEntry<unknown> = {
|
||||
raw,
|
||||
const out: DocCollectionEntry = {
|
||||
raw: glob as DocCollectionEntry['raw'],
|
||||
createClientLoader({ id = _name as string, ...options }) {
|
||||
return createClientLoader(raw, { id, ...options });
|
||||
return createClientLoader(this.raw, { id, ...options });
|
||||
},
|
||||
};
|
||||
|
||||
return out as Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? DocCollectionEntry<StandardSchemaV1.InferOutput<Schema>>
|
||||
? DocCollectionEntry<Name, StandardSchemaV1.InferOutput<Schema>, TC>
|
||||
: never;
|
||||
},
|
||||
};
|
||||
@@ -70,20 +81,20 @@ export function fromConfig<Config>() {
|
||||
const loaderStore = new Map<
|
||||
string,
|
||||
{
|
||||
preloaded: Map<string, CompiledMDXFile<any>>;
|
||||
preloaded: Map<string, CompiledMDXProperties>;
|
||||
}
|
||||
>();
|
||||
|
||||
export function createClientLoader<Frontmatter, Props extends object = object>(
|
||||
globEntries: Record<string, () => Promise<CompiledMDXFile<Frontmatter>>>,
|
||||
options: ClientLoaderOptions<Frontmatter, Props>,
|
||||
): ClientLoader<Frontmatter, Props> {
|
||||
export function createClientLoader<
|
||||
Doc = CompiledMDXProperties,
|
||||
Props extends object = object,
|
||||
>(
|
||||
globEntries: Record<string, () => Promise<Doc>>,
|
||||
options: ClientLoaderOptions<Doc, Props>,
|
||||
): ClientLoader<Doc, Props> {
|
||||
const { id = '', component } = options;
|
||||
const renderers: Record<string, FC<Props>> = {};
|
||||
const loaders = new Map<
|
||||
string,
|
||||
() => Promise<CompiledMDXFile<Frontmatter>>
|
||||
>();
|
||||
const loaders = new Map<string, () => Promise<Doc>>();
|
||||
const store = loaderStore.get(id) ?? {
|
||||
preloaded: new Map(),
|
||||
};
|
||||
|
||||
@@ -4,8 +4,9 @@ import { executeMdx } from '@fumadocs/mdx-remote/client';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
import fs from 'node:fs/promises';
|
||||
import { type FileInfo, fromConfig } from './server';
|
||||
import { server, type ServerOptions } from './server';
|
||||
import { type CoreOptions, createCore } from '@/core';
|
||||
import type { FileInfo, InternalTypeConfig } from './types';
|
||||
|
||||
export interface LazyEntry<Data = unknown> {
|
||||
info: FileInfo;
|
||||
@@ -14,19 +15,22 @@ export interface LazyEntry<Data = unknown> {
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
export type CreateDynamic<Config> = ReturnType<
|
||||
typeof fromConfigDynamic<Config>
|
||||
>;
|
||||
export type CreateDynamic<
|
||||
Config,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> = ReturnType<typeof dynamic<Config, TC>>;
|
||||
|
||||
export async function fromConfigDynamic<Config>(
|
||||
export async function dynamic<Config, TC extends InternalTypeConfig>(
|
||||
configExports: Config,
|
||||
coreOptions: CoreOptions,
|
||||
serverOptions?: ServerOptions,
|
||||
) {
|
||||
const core = await createCore(coreOptions).init({
|
||||
const core = createCore(coreOptions);
|
||||
await core.init({
|
||||
config: buildConfig(configExports as Record<string, unknown>),
|
||||
});
|
||||
|
||||
const create = fromConfig<Config>();
|
||||
const create = server<Config, TC>(serverOptions);
|
||||
|
||||
function getDocCollection(name: string): DocCollectionItem | undefined {
|
||||
const collection = core.getConfig().getCollection(name);
|
||||
@@ -70,7 +74,7 @@ export async function fromConfigDynamic<Config>(
|
||||
}
|
||||
|
||||
return {
|
||||
async doc<Name extends keyof Config>(
|
||||
async doc<Name extends keyof Config & string>(
|
||||
name: Name,
|
||||
base: string,
|
||||
entries: LazyEntry<unknown>[],
|
||||
@@ -83,7 +87,7 @@ export async function fromConfigDynamic<Config>(
|
||||
|
||||
return create.docLazy(name, base, head, body);
|
||||
},
|
||||
async docs<Name extends keyof Config>(
|
||||
async docs<Name extends keyof Config & string>(
|
||||
name: Name,
|
||||
base: string,
|
||||
meta: Record<string, unknown>,
|
||||
|
||||
@@ -5,127 +5,80 @@ import type {
|
||||
VirtualFile,
|
||||
} from 'fumadocs-core/source';
|
||||
import * as path from 'node:path';
|
||||
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import type { MDXContent } from 'mdx/types';
|
||||
import type { ExtractedReference } from '@/loaders/mdx/remark-postprocess';
|
||||
import type { Root } from 'mdast';
|
||||
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import type { CompiledMDXProperties } from '@/loaders/mdx/build-mdx';
|
||||
|
||||
export interface FileInfo {
|
||||
/**
|
||||
* virtualized path for Source API
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* the file path in file system
|
||||
*/
|
||||
fullPath: string;
|
||||
}
|
||||
|
||||
export interface DocData {
|
||||
/**
|
||||
* Compiled MDX content (as component)
|
||||
*/
|
||||
body: MDXContent;
|
||||
|
||||
/**
|
||||
* table of contents generated from content.
|
||||
*/
|
||||
toc: TOCItemType[];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Added by the `last-modified` plugin.
|
||||
*/
|
||||
lastModified?: Date;
|
||||
|
||||
/**
|
||||
* extracted references (e.g. hrefs, paths), useful for analyzing relationships between pages.
|
||||
*/
|
||||
extractedReferences?: ExtractedReference[];
|
||||
}
|
||||
|
||||
export interface DocMethods {
|
||||
/**
|
||||
* file info
|
||||
*/
|
||||
info: FileInfo;
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
getMDAST: () => Promise<Root>;
|
||||
}
|
||||
|
||||
export interface MetaMethods {
|
||||
/**
|
||||
* file info
|
||||
*/
|
||||
info: FileInfo;
|
||||
}
|
||||
import type {
|
||||
InternalTypeConfig,
|
||||
DocData,
|
||||
DocMethods,
|
||||
FileInfo,
|
||||
MetaMethods,
|
||||
} from './types';
|
||||
|
||||
export type MetaCollectionEntry<Data> = Data & MetaMethods;
|
||||
|
||||
export type DocCollectionEntry<Frontmatter> = DocData &
|
||||
DocMethods &
|
||||
export type DocCollectionEntry<
|
||||
Name extends string = string,
|
||||
Frontmatter = unknown,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> = DocData & DocMethods & Frontmatter & TC['DocData'][Name];
|
||||
|
||||
export type AsyncDocCollectionEntry<
|
||||
Name extends string = string,
|
||||
Frontmatter = unknown,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> = {
|
||||
load: () => Promise<DocData & TC['DocData'][Name]>;
|
||||
} & DocMethods &
|
||||
Frontmatter;
|
||||
|
||||
export type AsyncDocCollectionEntry<Frontmatter> = DocMethods & {
|
||||
load: () => Promise<DocData>;
|
||||
} & Frontmatter;
|
||||
|
||||
export type CompiledMDXFile<Frontmatter> = CompiledMDXProperties<Frontmatter> &
|
||||
Record<string, unknown>;
|
||||
|
||||
export interface DocsCollectionEntry<
|
||||
Frontmatter extends PageData,
|
||||
Meta extends MetaData,
|
||||
Name extends string = string,
|
||||
Frontmatter extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> {
|
||||
docs: DocCollectionEntry<Frontmatter>[];
|
||||
docs: DocCollectionEntry<Name, Frontmatter, TC>[];
|
||||
meta: MetaCollectionEntry<Meta>[];
|
||||
toFumadocsSource: () => Source<{
|
||||
pageData: DocCollectionEntry<Frontmatter>;
|
||||
pageData: DocCollectionEntry<Name, Frontmatter, TC>;
|
||||
metaData: MetaCollectionEntry<Meta>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface AsyncDocsCollectionEntry<
|
||||
Frontmatter extends PageData,
|
||||
Meta extends MetaData,
|
||||
Name extends string = string,
|
||||
Frontmatter extends PageData = PageData,
|
||||
Meta extends MetaData = MetaData,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> {
|
||||
docs: AsyncDocCollectionEntry<Frontmatter>[];
|
||||
docs: AsyncDocCollectionEntry<Name, Frontmatter, TC>[];
|
||||
meta: MetaCollectionEntry<Meta>[];
|
||||
toFumadocsSource: () => Source<{
|
||||
pageData: AsyncDocCollectionEntry<Frontmatter>;
|
||||
pageData: AsyncDocCollectionEntry<Name, Frontmatter, TC>;
|
||||
metaData: MetaCollectionEntry<Meta>;
|
||||
}>;
|
||||
}
|
||||
|
||||
type AwaitableGlobEntries<T> = Record<string, T | (() => Promise<T>)>;
|
||||
|
||||
export type ServerCreate<Config> = ReturnType<typeof fromConfig<Config>>;
|
||||
export type ServerCreate<
|
||||
Config,
|
||||
TC extends InternalTypeConfig = InternalTypeConfig,
|
||||
> = ReturnType<typeof server<Config, TC>>;
|
||||
|
||||
export interface ServerOptions {
|
||||
doc?: {
|
||||
passthroughs?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export function server<Config, TC extends InternalTypeConfig>(
|
||||
options: ServerOptions = {},
|
||||
) {
|
||||
const { doc: { passthroughs: docPassthroughs = [] } = {} } = options;
|
||||
|
||||
export function fromConfig<Config>() {
|
||||
function fileInfo(file: string, base: string): FileInfo {
|
||||
if (file.startsWith('./')) {
|
||||
file = file.slice(2);
|
||||
@@ -137,43 +90,48 @@ export function fromConfig<Config>() {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDocData(entry: CompiledMDXFile<any>): DocData {
|
||||
return {
|
||||
function mapDocData(entry: CompiledMDXProperties): DocData {
|
||||
const data: DocData = {
|
||||
body: entry.default,
|
||||
toc: entry.toc,
|
||||
extractedReferences: entry.extractedReferences,
|
||||
structuredData: entry.structuredData,
|
||||
lastModified: entry.lastModified,
|
||||
_exports: entry,
|
||||
_exports: entry as unknown as Record<string, unknown>,
|
||||
};
|
||||
|
||||
for (const key of docPassthroughs) {
|
||||
// @ts-expect-error -- handle passthrough properties
|
||||
data[key] = entry[key];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return {
|
||||
async doc<Name extends keyof Config>(
|
||||
async doc<Name extends keyof Config & string>(
|
||||
_name: Name,
|
||||
base: string,
|
||||
glob: AwaitableGlobEntries<unknown>,
|
||||
) {
|
||||
const out = await Promise.all(
|
||||
Object.entries(glob).map(async ([k, v]) => {
|
||||
const data: CompiledMDXFile<unknown> =
|
||||
const data: CompiledMDXProperties =
|
||||
typeof v === 'function' ? await v() : v;
|
||||
|
||||
return {
|
||||
...mapDocData(data),
|
||||
...(data.frontmatter as object),
|
||||
...createDocMethods(fileInfo(k, base), () => data),
|
||||
} satisfies DocCollectionEntry<unknown>;
|
||||
} satisfies DocCollectionEntry;
|
||||
}),
|
||||
);
|
||||
|
||||
return out as unknown as Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? DocCollectionEntry<StandardSchemaV1.InferOutput<Schema>>[]
|
||||
? DocCollectionEntry<Name, StandardSchemaV1.InferOutput<Schema>, TC>[]
|
||||
: never;
|
||||
},
|
||||
async docLazy<Name extends keyof Config>(
|
||||
async docLazy<Name extends keyof Config & string>(
|
||||
_name: Name,
|
||||
base: string,
|
||||
head: AwaitableGlobEntries<unknown>,
|
||||
@@ -182,7 +140,7 @@ export function fromConfig<Config>() {
|
||||
const out = await Promise.all(
|
||||
Object.entries(head).map(async ([k, v]) => {
|
||||
const data = typeof v === 'function' ? await v() : v;
|
||||
const content = body[k] as () => Promise<CompiledMDXFile<unknown>>;
|
||||
const content = body[k] as () => Promise<CompiledMDXProperties>;
|
||||
|
||||
return {
|
||||
...data,
|
||||
@@ -190,17 +148,21 @@ export function fromConfig<Config>() {
|
||||
async load() {
|
||||
return mapDocData(await content());
|
||||
},
|
||||
} satisfies AsyncDocCollectionEntry<unknown>;
|
||||
} satisfies AsyncDocCollectionEntry;
|
||||
}),
|
||||
);
|
||||
|
||||
return out as unknown as Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? AsyncDocCollectionEntry<StandardSchemaV1.InferOutput<Schema>>[]
|
||||
? AsyncDocCollectionEntry<
|
||||
Name,
|
||||
StandardSchemaV1.InferOutput<Schema>,
|
||||
TC
|
||||
>[]
|
||||
: never;
|
||||
},
|
||||
async meta<Name extends keyof Config>(
|
||||
async meta<Name extends keyof Config & string>(
|
||||
_name: Name,
|
||||
base: string,
|
||||
glob: AwaitableGlobEntries<unknown>,
|
||||
@@ -223,7 +185,7 @@ export function fromConfig<Config>() {
|
||||
: never;
|
||||
},
|
||||
|
||||
async docs<Name extends keyof Config>(
|
||||
async docs<Name extends keyof Config & string>(
|
||||
name: Name,
|
||||
base: string,
|
||||
metaGlob: AwaitableGlobEntries<unknown>,
|
||||
@@ -235,7 +197,7 @@ export function fromConfig<Config>() {
|
||||
toFumadocsSource() {
|
||||
return toFumadocsSource(this.docs, this.meta);
|
||||
},
|
||||
} satisfies DocsCollectionEntry<PageData, MetaData>;
|
||||
} satisfies DocsCollectionEntry;
|
||||
|
||||
return entry as Config[Name] extends DocsCollection<
|
||||
infer Page,
|
||||
@@ -244,14 +206,16 @@ export function fromConfig<Config>() {
|
||||
? StandardSchemaV1.InferOutput<Page> extends PageData
|
||||
? StandardSchemaV1.InferOutput<Meta> extends MetaData
|
||||
? DocsCollectionEntry<
|
||||
Name,
|
||||
StandardSchemaV1.InferOutput<Page>,
|
||||
StandardSchemaV1.InferOutput<Meta>
|
||||
StandardSchemaV1.InferOutput<Meta>,
|
||||
TC
|
||||
>
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
},
|
||||
async docsLazy<Name extends keyof Config>(
|
||||
async docsLazy<Name extends keyof Config & string>(
|
||||
name: Name,
|
||||
base: string,
|
||||
metaGlob: AwaitableGlobEntries<unknown>,
|
||||
@@ -264,7 +228,7 @@ export function fromConfig<Config>() {
|
||||
toFumadocsSource() {
|
||||
return toFumadocsSource(this.docs, this.meta);
|
||||
},
|
||||
} satisfies AsyncDocsCollectionEntry<PageData, MetaData>;
|
||||
} satisfies AsyncDocsCollectionEntry;
|
||||
|
||||
return entry as Config[Name] extends DocsCollection<
|
||||
infer Page,
|
||||
@@ -273,8 +237,10 @@ export function fromConfig<Config>() {
|
||||
? StandardSchemaV1.InferOutput<Page> extends PageData
|
||||
? StandardSchemaV1.InferOutput<Meta> extends MetaData
|
||||
? AsyncDocsCollectionEntry<
|
||||
Name,
|
||||
StandardSchemaV1.InferOutput<Page>,
|
||||
StandardSchemaV1.InferOutput<Meta>
|
||||
StandardSchemaV1.InferOutput<Meta>,
|
||||
TC
|
||||
>
|
||||
: never
|
||||
: never
|
||||
@@ -323,7 +289,7 @@ export function toFumadocsSource<
|
||||
|
||||
function createDocMethods(
|
||||
info: FileInfo,
|
||||
load: () => CompiledMDXProperties<any> | Promise<CompiledMDXProperties<any>>,
|
||||
load: () => CompiledMDXProperties | Promise<CompiledMDXProperties>,
|
||||
): DocMethods {
|
||||
return {
|
||||
info,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { StructuredData } from 'fumadocs-core/mdx-plugins/remark-structure';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import type { Root } from 'mdast';
|
||||
import type { MDXContent } from 'mdx/types';
|
||||
|
||||
export interface DocData {
|
||||
/**
|
||||
* Compiled MDX content (as component)
|
||||
*/
|
||||
body: MDXContent;
|
||||
|
||||
/**
|
||||
* table of contents generated from content.
|
||||
*/
|
||||
toc: TOCItemType[];
|
||||
|
||||
/**
|
||||
* structured data for document search indexing.
|
||||
*/
|
||||
structuredData: StructuredData;
|
||||
|
||||
/**
|
||||
* Raw exports from the compiled MDX file.
|
||||
*/
|
||||
_exports: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
/**
|
||||
* virtualized path for Source API
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* the file path in file system
|
||||
*/
|
||||
fullPath: string;
|
||||
}
|
||||
|
||||
export interface DocMethods {
|
||||
/**
|
||||
* file info
|
||||
*/
|
||||
info: FileInfo;
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
getMDAST: () => Promise<Root>;
|
||||
}
|
||||
|
||||
export interface MetaMethods {
|
||||
/**
|
||||
* file info
|
||||
*/
|
||||
info: FileInfo;
|
||||
}
|
||||
|
||||
export interface InternalTypeConfig {
|
||||
/**
|
||||
* collection name -> collection properties
|
||||
*/
|
||||
DocData: Record<string, unknown>;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ function toFullPath(file: string) {
|
||||
return file;
|
||||
}
|
||||
|
||||
export async function readFileWithCache(file: string): Promise<string> {
|
||||
export function readFileWithCache(file: string): Promise<string> {
|
||||
const fullPath = toFullPath(file);
|
||||
const cached = map.get(fullPath);
|
||||
if (cached) return cached;
|
||||
@@ -44,9 +44,11 @@ export default async function mdx(
|
||||
pluginOptions: PluginOptions = {},
|
||||
): Promise<Plugin> {
|
||||
const options = applyDefaults(pluginOptions);
|
||||
const core = await createViteCore(options).init({
|
||||
const core = createViteCore(options);
|
||||
await core.init({
|
||||
config: buildConfig(config),
|
||||
});
|
||||
|
||||
const configLoader = createIntegratedConfigLoader(core);
|
||||
const mdxLoader = toVite(createMdxLoader(configLoader));
|
||||
const metaLoader = toVite(
|
||||
@@ -104,7 +106,6 @@ export default async function mdx(
|
||||
export async function postInstall(pluginOptions: PluginOptions = {}) {
|
||||
const { loadConfig } = await import('@/config/load-from-file');
|
||||
const core = createViteCore(applyDefaults(pluginOptions));
|
||||
|
||||
await core.init({
|
||||
config: loadConfig(core, true),
|
||||
});
|
||||
|
||||
+15
-6
@@ -2,28 +2,37 @@
|
||||
// @ts-nocheck
|
||||
import { frontmatter as __fd_glob_1 } from "./generate-index/folder/test.mdx?collection=docs&only=frontmatter"
|
||||
import { frontmatter as __fd_glob_0 } from "./generate-index/index.mdx?collection=docs&only=frontmatter"
|
||||
import { fromConfig } from 'fumadocs-mdx/runtime/server';
|
||||
import { server } from 'fumadocs-mdx/runtime/server';
|
||||
import type * as Config from './config';
|
||||
|
||||
const create = fromConfig<typeof Config>();
|
||||
const create = server<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>({"doc":{"passthroughs":["extractedReferences"]}});
|
||||
|
||||
export const docs = await create.docLazy("docs", "packages/mdx/test/fixtures/generate-index", {"index.mdx": __fd_glob_0, "folder/test.mdx": __fd_glob_1, }, {"index.mdx": () => import("./generate-index/index.mdx?collection=docs"), "folder/test.mdx": () => import("./generate-index/folder/test.mdx?collection=docs"), });
|
||||
```
|
||||
|
||||
```ts title="dynamic.ts"
|
||||
// @ts-nocheck
|
||||
import { fromConfigDynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import { dynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import * as Config from './config';
|
||||
|
||||
const create = await fromConfigDynamic(Config);
|
||||
const create = await dynamic<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>(Config, {"configPath":"packages/mdx/test/fixtures/config.ts","environment":"test","outDir":"packages/mdx/test/fixtures"}, {"doc":{"passthroughs":["extractedReferences"]}});
|
||||
```
|
||||
|
||||
```ts title="browser.ts"
|
||||
// @ts-nocheck
|
||||
import { fromConfig } from 'fumadocs-mdx/runtime/browser';
|
||||
import { browser } from 'fumadocs-mdx/runtime/browser';
|
||||
import type * as Config from './config';
|
||||
|
||||
const create = fromConfig<typeof Config>();
|
||||
const create = browser<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>();
|
||||
const browserCollections = {
|
||||
docs: create.doc("docs", {"index.mdx": () => import("./generate-index/index.mdx?collection=docs"), "folder/test.mdx": () => import("./generate-index/folder/test.mdx?collection=docs"), }),
|
||||
};
|
||||
|
||||
+15
-6
@@ -1,28 +1,37 @@
|
||||
```ts title="server.ts"
|
||||
// @ts-nocheck
|
||||
import { default as __fd_glob_0 } from "./generate-index/meta.json?collection=docs"
|
||||
import { fromConfig } from 'fumadocs-mdx/runtime/server';
|
||||
import { server } from 'fumadocs-mdx/runtime/server';
|
||||
import type * as Config from './config';
|
||||
|
||||
const create = fromConfig<typeof Config>();
|
||||
const create = server<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>({"doc":{"passthroughs":["extractedReferences"]}});
|
||||
|
||||
export const docs = await create.meta("docs", "packages/mdx/test/fixtures/generate-index", {"meta.json": __fd_glob_0, });
|
||||
```
|
||||
|
||||
```ts title="dynamic.ts"
|
||||
// @ts-nocheck
|
||||
import { fromConfigDynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import { dynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import * as Config from './config';
|
||||
|
||||
const create = await fromConfigDynamic(Config);
|
||||
const create = await dynamic<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>(Config, {"configPath":"packages/mdx/test/fixtures/config.ts","environment":"test","outDir":"packages/mdx/test/fixtures"}, {"doc":{"passthroughs":["extractedReferences"]}});
|
||||
```
|
||||
|
||||
```ts title="browser.ts"
|
||||
// @ts-nocheck
|
||||
import { fromConfig } from 'fumadocs-mdx/runtime/browser';
|
||||
import { browser } from 'fumadocs-mdx/runtime/browser';
|
||||
import type * as Config from './config';
|
||||
|
||||
const create = fromConfig<typeof Config>();
|
||||
const create = browser<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>();
|
||||
const browserCollections = {
|
||||
};
|
||||
export default browserCollections;
|
||||
|
||||
+15
-6
@@ -2,28 +2,37 @@
|
||||
// @ts-nocheck
|
||||
import * as __fd_glob_1 from "./generate-index/folder/test.mdx?collection=docs"
|
||||
import * as __fd_glob_0 from "./generate-index/index.mdx?collection=docs"
|
||||
import { fromConfig } from 'fumadocs-mdx/runtime/server';
|
||||
import { server } from 'fumadocs-mdx/runtime/server';
|
||||
import type * as Config from './config';
|
||||
|
||||
const create = fromConfig<typeof Config>();
|
||||
const create = server<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>({"doc":{"passthroughs":["extractedReferences"]}});
|
||||
|
||||
export const docs = await create.doc("docs", "packages/mdx/test/fixtures/generate-index", {"index.mdx": __fd_glob_0, "folder/test.mdx": __fd_glob_1, });
|
||||
```
|
||||
|
||||
```ts title="dynamic.ts"
|
||||
// @ts-nocheck
|
||||
import { fromConfigDynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import { dynamic } from 'fumadocs-mdx/runtime/dynamic';
|
||||
import * as Config from './config';
|
||||
|
||||
const create = await fromConfigDynamic(Config);
|
||||
const create = await dynamic<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>(Config, {"configPath":"packages/mdx/test/fixtures/config.ts","environment":"test","outDir":"packages/mdx/test/fixtures"}, {"doc":{"passthroughs":["extractedReferences"]}});
|
||||
```
|
||||
|
||||
```ts title="browser.ts"
|
||||
// @ts-nocheck
|
||||
import { fromConfig } from 'fumadocs-mdx/runtime/browser';
|
||||
import { browser } from 'fumadocs-mdx/runtime/browser';
|
||||
import type * as Config from './config';
|
||||
|
||||
const create = fromConfig<typeof Config>();
|
||||
const create = browser<typeof Config, {
|
||||
DocData: {
|
||||
}
|
||||
}>();
|
||||
const browserCollections = {
|
||||
docs: create.doc("docs", {"index.mdx": () => import("./generate-index/index.mdx?collection=docs"), "folder/test.mdx": () => import("./generate-index/folder/test.mdx?collection=docs"), }),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user