mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Support absolutePath on Vite
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': minor
|
||||
---
|
||||
|
||||
Support `absolutePath` on Vite
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
VirtualFile,
|
||||
} from 'fumadocs-core/source';
|
||||
import type { CompiledMDXProperties } from '@/utils/build-mdx';
|
||||
import path from 'node:path';
|
||||
|
||||
export type CompiledMDXFile<Frontmatter> = CompiledMDXProperties<Frontmatter> &
|
||||
Record<string, unknown>;
|
||||
@@ -39,7 +40,15 @@ type MDXFileToPageDataLazy<Frontmatter> = Override<
|
||||
}
|
||||
>;
|
||||
|
||||
type DocMap<Frontmatter> = Record<
|
||||
string,
|
||||
(() => Promise<CompiledMDXFile<Frontmatter>>) & { base: string }
|
||||
>;
|
||||
|
||||
type MetaMap<Data> = Record<string, (() => Promise<Data>) & { base: string }>;
|
||||
|
||||
interface LazyDocMap<Frontmatter> {
|
||||
base: string;
|
||||
head: Record<string, () => Promise<Frontmatter>>;
|
||||
body: Record<string, () => Promise<CompiledMDXFile<Frontmatter>>>;
|
||||
}
|
||||
@@ -47,18 +56,17 @@ interface LazyDocMap<Frontmatter> {
|
||||
export function fromConfig<Config>(): {
|
||||
doc: <Name extends keyof Config>(
|
||||
name: Name,
|
||||
base: string,
|
||||
glob: Record<string, () => Promise<unknown>>,
|
||||
) => Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? Record<
|
||||
string,
|
||||
() => Promise<CompiledMDXFile<StandardSchemaV1.InferOutput<Schema>>>
|
||||
>
|
||||
? DocMap<StandardSchemaV1.InferOutput<Schema>>
|
||||
: never;
|
||||
|
||||
docLazy: <Name extends keyof Config>(
|
||||
name: Name,
|
||||
base: string,
|
||||
headGlob: Record<string, () => Promise<unknown>>,
|
||||
bodyGlob: Record<string, () => Promise<unknown>>,
|
||||
) => Config[Name] extends
|
||||
@@ -69,16 +77,17 @@ export function fromConfig<Config>(): {
|
||||
|
||||
meta: <Name extends keyof Config>(
|
||||
name: Name,
|
||||
base: string,
|
||||
glob: Record<string, () => Promise<unknown>>,
|
||||
) => Config[Name] extends
|
||||
| MetaCollection<infer Schema>
|
||||
| DocsCollection<StandardSchemaV1, infer Schema>
|
||||
? Record<string, () => Promise<StandardSchemaV1.InferOutput<Schema>>>
|
||||
? MetaMap<StandardSchemaV1.InferOutput<Schema>>
|
||||
: never;
|
||||
|
||||
sourceAsync: <DocOut extends PageData, MetaOut extends MetaData>(
|
||||
doc: Record<string, () => Promise<CompiledMDXFile<DocOut>>>,
|
||||
meta: Record<string, () => Promise<MetaOut>>,
|
||||
doc: DocMap<DocOut>,
|
||||
meta: MetaMap<MetaOut>,
|
||||
) => Promise<
|
||||
Source<{
|
||||
pageData: MDXFileToPageData<DocOut>;
|
||||
@@ -88,7 +97,7 @@ export function fromConfig<Config>(): {
|
||||
|
||||
sourceLazy: <DocOut extends PageData, MetaOut extends MetaData>(
|
||||
doc: LazyDocMap<DocOut>,
|
||||
meta: Record<string, () => Promise<MetaOut>>,
|
||||
meta: MetaMap<MetaOut>,
|
||||
) => Promise<
|
||||
Source<{
|
||||
pageData: MDXFileToPageDataLazy<DocOut>;
|
||||
@@ -96,18 +105,20 @@ export function fromConfig<Config>(): {
|
||||
}>
|
||||
>;
|
||||
} {
|
||||
function normalize(entries: Record<string, unknown>) {
|
||||
const out: Record<string, unknown> = {};
|
||||
function normalize<T>(entries: Record<string, T>, base?: string) {
|
||||
const out: Record<string, T> = {};
|
||||
|
||||
for (const k in entries) {
|
||||
const mappedK = k.startsWith('./') ? k.slice(2) : k;
|
||||
|
||||
if (base) Object.assign(entries[k] as object, { base });
|
||||
out[mappedK] = entries[k];
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function mapPageData<Frontmatter>(
|
||||
entry: CompiledMDXFile<Frontmatter>,
|
||||
): MDXFileToPageData<Frontmatter> {
|
||||
function mapPageData<Frontmatter>(entry: CompiledMDXFile<Frontmatter>) {
|
||||
const { toc, structuredData, lastModified, frontmatter } = entry;
|
||||
|
||||
return {
|
||||
@@ -135,14 +146,15 @@ export function fromConfig<Config>(): {
|
||||
}
|
||||
|
||||
return {
|
||||
doc(_, glob) {
|
||||
return normalize(glob) as any;
|
||||
doc(_, base, glob) {
|
||||
return normalize(glob, base) as any;
|
||||
},
|
||||
meta(_, glob) {
|
||||
return normalize(glob) as any;
|
||||
meta(_, base, glob) {
|
||||
return normalize(glob, base) as any;
|
||||
},
|
||||
docLazy(_, head, body) {
|
||||
docLazy(_, base, head, body) {
|
||||
return {
|
||||
base,
|
||||
head: normalize(head),
|
||||
body: normalize(body),
|
||||
} as any;
|
||||
@@ -153,6 +165,7 @@ export function fromConfig<Config>(): {
|
||||
return {
|
||||
type: 'page',
|
||||
path: file,
|
||||
absolutePath: path.join(content.base, file),
|
||||
data: mapPageData(await content()),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
@@ -160,6 +173,7 @@ export function fromConfig<Config>(): {
|
||||
return {
|
||||
type: 'meta',
|
||||
path: file,
|
||||
absolutePath: path.join(content.base, file),
|
||||
data: await content(),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
@@ -173,6 +187,7 @@ export function fromConfig<Config>(): {
|
||||
return {
|
||||
type: 'page',
|
||||
path: file,
|
||||
absolutePath: path.join(doc.base, file),
|
||||
data: mapPageDataLazy(await frontmatter(), doc.body[file]),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
@@ -180,6 +195,7 @@ export function fromConfig<Config>(): {
|
||||
return {
|
||||
type: 'meta',
|
||||
path: file,
|
||||
absolutePath: path.join(content.base, file),
|
||||
data: await content(),
|
||||
} satisfies VirtualFile;
|
||||
}),
|
||||
@@ -205,12 +221,16 @@ export interface ClientLoaderOptions<Frontmatter, Props> {
|
||||
component: (loaded: CompiledMDXFile<Frontmatter>, props: Props) => ReactNode;
|
||||
}
|
||||
|
||||
type ClientRenderer<Props> = Record<string, FC<Props>>;
|
||||
|
||||
export interface ClientLoader<Frontmatter, Props> {
|
||||
preload: (path: string) => Promise<CompiledMDXFile<Frontmatter>>;
|
||||
/**
|
||||
* Get a component that renders content with `React.lazy`
|
||||
*/
|
||||
getComponent: (path: string) => FC<Props>;
|
||||
|
||||
getRenderer: () => ClientRenderer<Props>;
|
||||
}
|
||||
|
||||
const loaderStore = new Map<
|
||||
@@ -225,12 +245,31 @@ export function createClientLoader<Frontmatter, Props = object>(
|
||||
options: ClientLoaderOptions<Frontmatter, Props>,
|
||||
): ClientLoader<Frontmatter, Props> {
|
||||
const { id = '', component } = options;
|
||||
let renderer: ClientRenderer<Props> | undefined;
|
||||
const store = loaderStore.get(id) ?? {
|
||||
preloaded: new Map(),
|
||||
};
|
||||
loaderStore.set(id, store);
|
||||
|
||||
let renderer;
|
||||
function getRenderer() {
|
||||
if (renderer) return renderer;
|
||||
|
||||
renderer = {};
|
||||
for (const k in files) {
|
||||
const OnDemand = lazy(async () => {
|
||||
const loaded = await files[k]();
|
||||
return { default: (props) => component(loaded, props) };
|
||||
});
|
||||
|
||||
renderer[k] = (props: Props) => {
|
||||
const cached = store.preloaded.get(k);
|
||||
if (!cached) return createElement(OnDemand, props);
|
||||
return component(cached, props);
|
||||
};
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
return {
|
||||
async preload(path) {
|
||||
@@ -238,45 +277,16 @@ export function createClientLoader<Frontmatter, Props = object>(
|
||||
store.preloaded.set(path, loaded);
|
||||
return loaded;
|
||||
},
|
||||
getRenderer,
|
||||
getComponent(path) {
|
||||
renderer ??= toClientRenderer(files, component, {
|
||||
cache: store.preloaded,
|
||||
});
|
||||
return renderer[path];
|
||||
return getRenderer()[path];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface ClientRendererOptions<Frontmatter> {
|
||||
cache?: Map<string, CompiledMDXFile<Frontmatter>>;
|
||||
}
|
||||
|
||||
type ClientRenderer<Props> = Record<string, FC<Props>>;
|
||||
|
||||
export function toClientRenderer<Frontmatter, Props = object>(
|
||||
files: Record<string, () => Promise<CompiledMDXFile<Frontmatter>>>,
|
||||
component: (loaded: CompiledMDXFile<Frontmatter>, props: Props) => ReactNode,
|
||||
options: ClientRendererOptions<Frontmatter> = {},
|
||||
): ClientRenderer<Props> {
|
||||
const { cache } = options;
|
||||
const renderer: ClientRenderer<Props> = {};
|
||||
|
||||
for (const k in files) {
|
||||
const OnDemand = lazy(async () => {
|
||||
const loaded = await files[k]();
|
||||
return { default: (props) => component(loaded, props) };
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
renderer[k] = (props: Props) => {
|
||||
const cached = cache.get(k);
|
||||
if (!cached) return createElement(OnDemand, props);
|
||||
return component(cached, props);
|
||||
};
|
||||
} else {
|
||||
renderer[k] = OnDemand;
|
||||
}
|
||||
}
|
||||
|
||||
return renderer;
|
||||
return createClientLoader(files, { component }).getRenderer();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { AnyCollection, DocCollection, MetaCollection } from '@/config';
|
||||
import { getGlobPatterns } from '@/utils/collections';
|
||||
import type { AnyCollection } from '@/config';
|
||||
|
||||
interface GlobOptions {
|
||||
query: Record<string, string>;
|
||||
@@ -10,32 +9,32 @@ interface GlobOptions {
|
||||
|
||||
export function generateGlob(
|
||||
name: string,
|
||||
collection: MetaCollection | DocCollection,
|
||||
patterns: string[],
|
||||
globOptions?: Partial<GlobOptions>,
|
||||
) {
|
||||
const patterns = mapGlobPatterns(getGlobPatterns(collection));
|
||||
const options: GlobOptions = {
|
||||
...globOptions,
|
||||
query: {
|
||||
...globOptions?.query,
|
||||
collection: name,
|
||||
},
|
||||
base: getGlobBase(collection),
|
||||
};
|
||||
|
||||
return `import.meta.glob(${JSON.stringify(patterns)}, ${JSON.stringify(options, null, 2)})`;
|
||||
return `import.meta.glob(${JSON.stringify(mapGlobPatterns(patterns))}, ${JSON.stringify(options, null, 2)})`;
|
||||
}
|
||||
|
||||
function mapGlobPatterns(patterns: string[]) {
|
||||
return patterns.map((file) => {
|
||||
if (file.startsWith('./')) return file;
|
||||
if (file.startsWith('/')) return `.${file}`;
|
||||
|
||||
return `./${file}`;
|
||||
});
|
||||
return patterns.map(enforceRelative);
|
||||
}
|
||||
|
||||
function getGlobBase(collection: AnyCollection) {
|
||||
function enforceRelative(file: string) {
|
||||
if (file.startsWith('./')) return file;
|
||||
if (file.startsWith('/')) return `.${file}`;
|
||||
|
||||
return `./${file}`;
|
||||
}
|
||||
|
||||
export function getGlobBase(collection: AnyCollection) {
|
||||
let dir = collection.dir;
|
||||
|
||||
if (Array.isArray(dir)) {
|
||||
@@ -47,8 +46,5 @@ function getGlobBase(collection: AnyCollection) {
|
||||
dir = dir[0];
|
||||
}
|
||||
|
||||
if (!dir.startsWith('./') && !dir.startsWith('/')) {
|
||||
return '/' + dir;
|
||||
}
|
||||
return dir;
|
||||
return enforceRelative(dir);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { DocCollection, DocsCollection, MetaCollection } from '@/config';
|
||||
import { ident, toImportPath } from '@/utils/import-formatter';
|
||||
import { generateGlob } from '@/vite/generate-glob';
|
||||
import { generateGlob, getGlobBase } from '@/vite/generate-glob';
|
||||
import type { LoadedConfig } from '@/utils/config';
|
||||
import { getGlobPatterns } from '@/utils/collections';
|
||||
|
||||
export function docs(name: string, collection: DocsCollection) {
|
||||
function docs(name: string, collection: DocsCollection) {
|
||||
const obj = [
|
||||
ident(`doc: ${doc(name, collection.docs)}`),
|
||||
ident(`meta: ${meta(name, collection.meta)}`),
|
||||
@@ -12,22 +13,35 @@ export function docs(name: string, collection: DocsCollection) {
|
||||
return `{\n${obj}\n}`;
|
||||
}
|
||||
|
||||
export function doc(name: string, collection: DocCollection) {
|
||||
function doc(name: string, collection: DocCollection) {
|
||||
const patterns = getGlobPatterns(collection);
|
||||
const base = getGlobBase(collection);
|
||||
const docGlob = generateGlob(name, patterns, {
|
||||
base,
|
||||
});
|
||||
|
||||
if (collection.async) {
|
||||
return `create.docLazy("${name}", ${generateGlob(name, collection, {
|
||||
const headBlob = generateGlob(name, patterns, {
|
||||
query: {
|
||||
only: 'frontmatter',
|
||||
},
|
||||
import: 'frontmatter',
|
||||
})}, ${generateGlob(name, collection)})`;
|
||||
base,
|
||||
});
|
||||
|
||||
return `create.docLazy("${name}", "${base}", ${headBlob}, ${docGlob})`;
|
||||
}
|
||||
|
||||
return `create.doc("${name}", ${generateGlob(name, collection)})`;
|
||||
return `create.doc("${name}", "${base}", ${docGlob})`;
|
||||
}
|
||||
|
||||
export function meta(name: string, collection: MetaCollection) {
|
||||
return `create.meta("${name}", ${generateGlob(name, collection, {
|
||||
function meta(name: string, collection: MetaCollection) {
|
||||
const patterns = getGlobPatterns(collection);
|
||||
const base = getGlobBase(collection);
|
||||
|
||||
return `create.meta("${name}", "${base}", ${generateGlob(name, patterns, {
|
||||
import: 'default',
|
||||
base,
|
||||
})})`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user