mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Support Async Mode for Vite
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': minor
|
||||
---
|
||||
|
||||
Support Async Mode for Vite
|
||||
@@ -40,16 +40,15 @@ export const _runtimeAsync: RuntimeAsync = {
|
||||
},
|
||||
filePath: file.absolutePath,
|
||||
});
|
||||
const executed = await executeMdx(String(out), {
|
||||
const { default: body, ...rest } = await executeMdx(String(out), {
|
||||
baseUrl: pathToFileURL(file.absolutePath),
|
||||
});
|
||||
|
||||
return {
|
||||
body: executed.default,
|
||||
toc: executed.toc,
|
||||
lastModified,
|
||||
structuredData: out.data.structuredData as StructuredData,
|
||||
_exports: executed,
|
||||
body,
|
||||
...rest,
|
||||
lastModified,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,7 +23,6 @@ export interface MarkdownProps {
|
||||
body: FC<MDXProps>;
|
||||
structuredData: StructuredData;
|
||||
toc: TableOfContents;
|
||||
_exports: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Only available when `lastModifiedTime` is enabled on MDX loader
|
||||
@@ -45,6 +44,11 @@ export interface AsyncRuntimeFile {
|
||||
|
||||
type DocOut<Schema extends StandardSchemaV1> = Override<
|
||||
MarkdownProps & {
|
||||
/**
|
||||
* Other exports in the compiled Markdown/MDX file
|
||||
*/
|
||||
_exports: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Read the original content of file from file system.
|
||||
*/
|
||||
@@ -89,7 +93,7 @@ type AsyncDocOut<Schema extends StandardSchemaV1> =
|
||||
StandardSchemaV1.InferOutput<Schema> &
|
||||
BaseCollectionEntry & {
|
||||
content: string;
|
||||
load: () => Promise<MarkdownProps>;
|
||||
load: () => Promise<MarkdownProps & Record<string, unknown>>;
|
||||
};
|
||||
|
||||
export interface RuntimeAsync {
|
||||
|
||||
@@ -26,66 +26,68 @@ interface CompiledMDXProperties<Frontmatter> {
|
||||
export type CompiledMDXFile<Frontmatter> = CompiledMDXProperties<Frontmatter> &
|
||||
Record<string, unknown>;
|
||||
|
||||
type MDXFileToPageData<Frontmatter> = Frontmatter &
|
||||
Omit<CompiledMDXProperties<Frontmatter>, 'frontmatter'> & {
|
||||
_exports: Record<string, unknown>;
|
||||
};
|
||||
type Override<A, B> = Omit<A, keyof B> & B;
|
||||
|
||||
type AttachGlobValue<GlobValue, Attach> =
|
||||
GlobValue extends () => Promise<unknown> ? () => Promise<Attach> : Attach;
|
||||
type MDXFileToPageData<Frontmatter> = Override<
|
||||
Omit<CompiledMDXProperties<Frontmatter>, 'frontmatter' | 'default'>,
|
||||
Frontmatter & {
|
||||
/**
|
||||
* @deprecated use `body` instead.
|
||||
*/
|
||||
default: FC<MDXProps>;
|
||||
|
||||
_exports: Record<string, unknown>;
|
||||
body: FC<MDXProps>;
|
||||
}
|
||||
>;
|
||||
|
||||
type MDXFileToPageDataLazy<Frontmatter> = Override<
|
||||
Frontmatter,
|
||||
{
|
||||
load: () => Promise<
|
||||
Omit<CompiledMDXFile<Frontmatter>, 'default'> & {
|
||||
body: FC<MDXProps>;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
|
||||
interface LazyDocMap<Frontmatter> {
|
||||
head: Record<string, () => Promise<Frontmatter>>;
|
||||
body: Record<string, () => Promise<CompiledMDXFile<Frontmatter>>>;
|
||||
}
|
||||
|
||||
export function fromConfig<Config>(): {
|
||||
doc: <Name extends keyof Config, GlobValue>(
|
||||
doc: <Name extends keyof Config>(
|
||||
name: Name,
|
||||
glob: Record<string, GlobValue>,
|
||||
) => Config[Name] extends DocCollection<infer Schema>
|
||||
glob: Record<string, () => Promise<unknown>>,
|
||||
) => Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? Record<
|
||||
string,
|
||||
AttachGlobValue<
|
||||
GlobValue,
|
||||
CompiledMDXFile<StandardSchemaV1.InferOutput<Schema>>
|
||||
>
|
||||
() => Promise<CompiledMDXFile<StandardSchemaV1.InferOutput<Schema>>>
|
||||
>
|
||||
: never;
|
||||
|
||||
meta: <Name extends keyof Config, GlobValue>(
|
||||
docLazy: <Name extends keyof Config>(
|
||||
name: Name,
|
||||
glob: Record<string, GlobValue>,
|
||||
) => Config[Name] extends MetaCollection<infer Schema>
|
||||
? AttachGlobValue<GlobValue, StandardSchemaV1.InferOutput<Schema>>
|
||||
headGlob: Record<string, () => Promise<unknown>>,
|
||||
bodyGlob: Record<string, () => Promise<unknown>>,
|
||||
) => Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? LazyDocMap<StandardSchemaV1.InferOutput<Schema>>
|
||||
: never;
|
||||
|
||||
docs: <Name extends keyof Config, DocGlobValue, MetaGlobValue>(
|
||||
meta: <Name extends keyof Config>(
|
||||
name: Name,
|
||||
options: {
|
||||
meta: Record<string, MetaGlobValue>;
|
||||
doc: Record<string, DocGlobValue>;
|
||||
},
|
||||
) => Config[Name] extends DocsCollection<infer DocSchema, infer MetaSchema>
|
||||
? {
|
||||
doc: Record<
|
||||
string,
|
||||
AttachGlobValue<
|
||||
DocGlobValue,
|
||||
CompiledMDXFile<StandardSchemaV1.InferOutput<DocSchema>>
|
||||
>
|
||||
>;
|
||||
meta: Record<
|
||||
string,
|
||||
AttachGlobValue<
|
||||
MetaGlobValue,
|
||||
StandardSchemaV1.InferOutput<MetaSchema>
|
||||
>
|
||||
>;
|
||||
}
|
||||
glob: Record<string, () => Promise<unknown>>,
|
||||
) => Config[Name] extends
|
||||
| MetaCollection<infer Schema>
|
||||
| DocsCollection<StandardSchemaV1, infer Schema>
|
||||
? Record<string, () => Promise<StandardSchemaV1.InferOutput<Schema>>>
|
||||
: never;
|
||||
source: <DocOut extends PageData, MetaOut extends MetaData>(
|
||||
doc: Record<string, CompiledMDXFile<DocOut>>,
|
||||
meta: Record<string, MetaOut>,
|
||||
) => Source<{
|
||||
pageData: MDXFileToPageData<DocOut>;
|
||||
metaData: MetaOut;
|
||||
}>;
|
||||
|
||||
sourceAsync: <DocOut extends PageData, MetaOut extends MetaData>(
|
||||
doc: Record<string, () => Promise<CompiledMDXFile<DocOut>>>,
|
||||
@@ -96,6 +98,16 @@ export function fromConfig<Config>(): {
|
||||
metaData: MetaOut;
|
||||
}>
|
||||
>;
|
||||
|
||||
sourceLazy: <DocOut extends PageData, MetaOut extends MetaData>(
|
||||
doc: LazyDocMap<DocOut>,
|
||||
meta: Record<string, () => Promise<MetaOut>>,
|
||||
) => Promise<
|
||||
Source<{
|
||||
pageData: MDXFileToPageDataLazy<DocOut>;
|
||||
metaData: MetaOut;
|
||||
}>
|
||||
>;
|
||||
} {
|
||||
function normalize(entries: Record<string, unknown>) {
|
||||
const out: Record<string, unknown> = {};
|
||||
@@ -114,6 +126,7 @@ export function fromConfig<Config>(): {
|
||||
return {
|
||||
...frontmatter,
|
||||
default: entry.default,
|
||||
body: entry.default,
|
||||
toc,
|
||||
structuredData,
|
||||
lastModified,
|
||||
@@ -121,6 +134,19 @@ export function fromConfig<Config>(): {
|
||||
};
|
||||
}
|
||||
|
||||
function mapPageDataLazy<Frontmatter>(
|
||||
head: Frontmatter,
|
||||
content: () => Promise<CompiledMDXFile<Frontmatter>>,
|
||||
): MDXFileToPageDataLazy<Frontmatter> {
|
||||
return {
|
||||
...head,
|
||||
async load() {
|
||||
const { default: body, ...rest } = await content();
|
||||
return { body, ...rest };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
doc(_, glob) {
|
||||
return normalize(glob) as any;
|
||||
@@ -128,56 +154,51 @@ export function fromConfig<Config>(): {
|
||||
meta(_, glob) {
|
||||
return normalize(glob) as any;
|
||||
},
|
||||
docs(_, { doc, meta }) {
|
||||
docLazy(_, head, body) {
|
||||
return {
|
||||
doc: normalize(doc),
|
||||
meta: normalize(meta),
|
||||
head: normalize(head),
|
||||
body: normalize(body),
|
||||
} as any;
|
||||
},
|
||||
source(doc, meta) {
|
||||
const virtualFiles: VirtualFile[] = [];
|
||||
for (const [file, content] of Object.entries(doc)) {
|
||||
virtualFiles.push({
|
||||
type: 'page',
|
||||
path: file,
|
||||
data: mapPageData(content),
|
||||
});
|
||||
}
|
||||
|
||||
for (const [file, content] of Object.entries(meta)) {
|
||||
virtualFiles.push({
|
||||
type: 'meta',
|
||||
path: file,
|
||||
data: content,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
files: virtualFiles,
|
||||
};
|
||||
},
|
||||
async sourceAsync(doc, meta) {
|
||||
const virtualFiles: VirtualFile[] = [];
|
||||
const virtualFiles: Promise<VirtualFile>[] = [
|
||||
...Object.entries(doc).map(async ([file, content]) => {
|
||||
return {
|
||||
type: 'page',
|
||||
path: file,
|
||||
data: mapPageData(await content()),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
...Object.entries(meta).map(async ([file, content]) => {
|
||||
return {
|
||||
type: 'meta',
|
||||
path: file,
|
||||
data: await content(),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
];
|
||||
|
||||
for (const [file, content] of Object.entries(doc)) {
|
||||
virtualFiles.push({
|
||||
type: 'page',
|
||||
path: file,
|
||||
data: mapPageData(await content()),
|
||||
});
|
||||
}
|
||||
return { files: await Promise.all(virtualFiles) };
|
||||
},
|
||||
async sourceLazy(doc, meta) {
|
||||
const virtualFiles: Promise<VirtualFile>[] = [
|
||||
...Object.entries(doc.head).map(async ([file, frontmatter]) => {
|
||||
return {
|
||||
type: 'page',
|
||||
path: file,
|
||||
data: mapPageDataLazy(await frontmatter(), doc.body[file]),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
...Object.entries(meta).map(async ([file, content]) => {
|
||||
return {
|
||||
type: 'meta',
|
||||
path: file,
|
||||
data: await content(),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
];
|
||||
|
||||
for (const [file, content] of Object.entries(meta)) {
|
||||
virtualFiles.push({
|
||||
type: 'meta',
|
||||
path: file,
|
||||
data: await content(),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
files: virtualFiles,
|
||||
};
|
||||
return { files: await Promise.all(virtualFiles) };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import type { AnyCollection, DocCollection, MetaCollection } from '@/config';
|
||||
import { getGlobPatterns } from '@/utils/collections';
|
||||
|
||||
interface GlobOptions {
|
||||
query: Record<string, string>;
|
||||
base?: string;
|
||||
eager?: boolean;
|
||||
import?: string;
|
||||
}
|
||||
|
||||
export function generateGlob(
|
||||
name: string,
|
||||
collection: MetaCollection | DocCollection,
|
||||
globOptions?: Partial<GlobOptions>,
|
||||
) {
|
||||
const patterns = mapGlobPatterns(getGlobPatterns(collection));
|
||||
const options: Record<string, unknown> = {
|
||||
const options: GlobOptions = {
|
||||
...globOptions,
|
||||
query: {
|
||||
...globOptions?.query,
|
||||
collection: name,
|
||||
},
|
||||
base: getGlobBase(collection),
|
||||
};
|
||||
|
||||
if (collection.type === 'meta') {
|
||||
options.import = 'default';
|
||||
}
|
||||
|
||||
return `import.meta.glob(${JSON.stringify(patterns)}, ${JSON.stringify(options, null, 2)})`;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
|
||||
import { ident } from '@/utils/import-formatter';
|
||||
import { generateGlob } from '@/vite/generate-glob';
|
||||
|
||||
export function docs(name: string, collection: DocsCollection) {
|
||||
const obj = [
|
||||
ident(`doc: ${doc(name, collection.docs)}`),
|
||||
ident(`meta: ${meta(name, collection.meta)}`),
|
||||
].join(',\n');
|
||||
|
||||
return `{\n${obj}\n}`;
|
||||
}
|
||||
|
||||
export function doc(name: string, collection: DocCollection) {
|
||||
if (collection.async) {
|
||||
return `create.docLazy("${name}", ${generateGlob(name, collection, {
|
||||
query: {
|
||||
only: 'frontmatter',
|
||||
},
|
||||
import: 'frontmatter',
|
||||
})}, ${generateGlob(name, collection)})`;
|
||||
}
|
||||
|
||||
return `create.doc("${name}", ${generateGlob(name, collection)})`;
|
||||
}
|
||||
|
||||
export function meta(name: string, collection: MetaCollection) {
|
||||
return `create.meta("${name}", ${generateGlob(name, collection, {
|
||||
import: 'default',
|
||||
})})`;
|
||||
}
|
||||
@@ -6,16 +6,20 @@ import { countLines } from '@/utils/count-lines';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
import { validate, ValidationError } from '@/utils/validation';
|
||||
import { z } from 'zod';
|
||||
import { ident, toImportPath } from '@/utils/import-formatter';
|
||||
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
|
||||
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 { generateGlob } from '@/vite/generate-glob';
|
||||
import { getGitTimestamp } from '@/utils/git-timestamp';
|
||||
import { doc, docs, meta } from '@/vite/generate';
|
||||
|
||||
const onlySchema = z.literal(['frontmatter', 'all']);
|
||||
const querySchema = z
|
||||
.object({
|
||||
only: z.literal(['frontmatter', 'all']).default('all'),
|
||||
collection: z.string().optional(),
|
||||
})
|
||||
.loose();
|
||||
|
||||
export interface PluginOptions {
|
||||
/**
|
||||
@@ -100,15 +104,11 @@ export default function mdx(
|
||||
): Promise<TransformResult | null> {
|
||||
const matter = fumaMatter(value);
|
||||
const isDevelopment = this.environment.mode === 'dev';
|
||||
const parsed = parse(query) as {
|
||||
collection?: string;
|
||||
only?: string;
|
||||
};
|
||||
const parsed = querySchema.parse(parse(query));
|
||||
|
||||
const collection = parsed.collection
|
||||
? loaded.collections.get(parsed.collection)
|
||||
: undefined;
|
||||
const only = parsed.only ? onlySchema.parse(parsed.only) : 'all';
|
||||
|
||||
let schema;
|
||||
let mdxOptions;
|
||||
@@ -135,7 +135,7 @@ export default function mdx(
|
||||
);
|
||||
}
|
||||
|
||||
if (only === 'frontmatter') {
|
||||
if (parsed.only === 'frontmatter') {
|
||||
return {
|
||||
code: `export const frontmatter = ${JSON.stringify(matter.data)}`,
|
||||
map: null,
|
||||
@@ -199,32 +199,19 @@ export default function mdx(
|
||||
`export const create = fromConfig<typeof Config>();`,
|
||||
];
|
||||
|
||||
function docs(name: string, collection: DocsCollection) {
|
||||
const args = [
|
||||
ident(`doc: ${generateGlob(name, collection.docs)}`),
|
||||
ident(`meta: ${generateGlob(name, collection.meta)}`),
|
||||
].join(',\n');
|
||||
|
||||
return `export const ${name} = create.docs("${name}", {\n${args}\n});`;
|
||||
}
|
||||
|
||||
function doc(name: string, collection: DocCollection) {
|
||||
return `export const ${name} = create.doc("${name}", ${generateGlob(name, collection)});`;
|
||||
}
|
||||
|
||||
function meta(name: string, collection: MetaCollection) {
|
||||
return `export const ${name} = create.meta("${name}", ${generateGlob(name, collection)});`;
|
||||
}
|
||||
|
||||
for (const [name, collection] of loaded.collections.entries()) {
|
||||
lines.push('');
|
||||
let body: string;
|
||||
|
||||
if (collection.type === 'docs') {
|
||||
lines.push(docs(name, collection));
|
||||
body = docs(name, collection);
|
||||
} else if (collection.type === 'meta') {
|
||||
lines.push(meta(name, collection));
|
||||
body = meta(name, collection);
|
||||
} else {
|
||||
lines.push(doc(name, collection));
|
||||
body = doc(name, collection);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push(`export const ${name} = ${body};`);
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(outdir, outFile), lines.join('\n'));
|
||||
|
||||
Reference in New Issue
Block a user